if a lambda captures a class member, and the lambd...
# kotlin-native
k
if a lambda captures a class member, and the lambda is frozen, why does it need to freeze the member's parent object?
b
The class member will already be frozen, so it won't?
k
i am observing that it is
but I thought it was supposed to down the object graph, not up
b
I mean freeze will be called probably, but i'd imagine it's a no-op
that technically is down the object graph, since the lambda is referencing that class member, no?
k
I expect the class member to be frozen, not the parent object
b
Hm… Do you have a code sample?
k
i'd have to strip what I am working on down to the relevant bits
b
okay. can you clarify more about the "member's parent object" part? that's where i'm getting lost
d
Sadly captures are done by reference instead of by value. Since Kotlin doesn't (really) support pointers, it has to take the parents object and use that as a reference of sorts.
So the parent's parent won't be frozen.
k
i've got a unit test class which has a member that is an atomic reference to a lambda function. during my setup function I pass a configuration lambda that captures a reference to the member lambda, which is frozen. this freezes the unit test class.
b
if you do
val memberLambda = this.memberLambda
and freeze the val, that should get you out of that no?
d
If you copy the member into a local variable and then capture that, a new wrapper (parent) will be made to make the capture work, and that can be safely frozen.
Lol, what he said.
😆 1
k
hah
fair enough, I'll give it a try
i've been in invalid immutability hell all morning, i'll try anything at this point 🙃
😅 1
k
When crossing thread boundaries I generally create new methods and pass values as args to avoid freezing
☝🏼 1
Copy code
suspend fun updateFavorite(breedId: Long, favorite: Boolean) = withContext(Dispatchers.Default) {
        dbRef.tableQueries.updateFavorite(favorite.toLong(), breedId)
    }
k
calling an extension function within a frozen lamda has the same affect
k
Working on an intellij plugin to help with this stuff.
k
oh my lord my unit tests are passing on both platforms
d
\o/
k
i think i'm going to take the rest of the week off
i still need to figure out how to mix @ThreadLocal with
Closeable
objects
k
Managing thread-local lifecycle isn’t necessarily straightforward. Have some code on the particular use case?
k
in this case I am managing Ktor HttpClient instances
using Coroutine Worker to make requests off the main thread
Copy code
@ThreadLocal object HttpClientProvider {
    val client = HttpClient()
}
basically
d
Does it haaaaaaave to be (thread) global?
k
what I don't want is to instantiate a new one for every request
this will be called a lot, and intuition leads me to believe they're not cheap to create
k
Why not just keep that in the main thread? When you make calls on
client
, they initiate an async call on the underlying network provider, then suspend. Unless you’re doing heavy processing on the response, I don’t think initiating calls on a background thread does much.
Having said that, I think there’s going to be a need for a sync call networking client, but maybe not.
Anyway, the need for
Closable
is to shut down the client after you’re dong using it?
k
yes
I am not confident enough in the frameworks to trust that having this on the main thread will be acceptable for both native and JVM
k
Why?
As for the closing, it’s a global object. When is it “done”?
k
when the thread to which it has affinity finishes
that's controlled by the underlying frameworks
k
You’re looking for a destructor to be called on a thread local when the thread finishes, essentially?
k
yeah that pretty much sums it up
k
If you need a pool of ktors, why not just make a pool of ktors I guess? I don’t think there’s a way to get a destructor to anything, much less a thread local. I’d still argue that because of the way ktor works, you don’t get much by having them called from multiple threads.
I might be very wrong, but I’m pretty sure you can’t rely on Java destructors either.
k
i don't need a pool per say - you can only use a client object on the thread in which it's created
well, when it executes the underlying request on iOS and suspends, is the main event loop still able to process?
the guarantees are still unclear there
k
You’re asking if a suspending coroutine call blocks it’s thread? I would presume not unless you run it as a blocking call, yes?
k
i'm just hesitant because our apps tend to be fairly performance sensitive, and these calls will be made frequently
k
I haven’t tried ktor in a multiple thread situation, so can’t comment
k
i am more asking if the dispatcher on iOS is using the run loop internally
given that
suspend
functions end up getting removed from the native targets
k
I’ve only initiated ktor from the main thread. In that context, the coroutine dispatcher uses the iOS queue to run things. So, initiate call, NSUrlSession call initiated (async), kotlin/ktor call suspends, NSUrlSession call returns, schedules kotlin/ktor call resume
The coroutine dispatcher is scheduled with iOS queues, essentially.
k
that may be feasible in that case
b
Creating multiple httpclients on iOS will likely create multiple URLSessions, which means multiple TLS pools. IMO, ktor should make that class freeze-safe
we avoided all this with an
interface Network
that each platform implements natively and provides and implementation for at library init
1
a
Ktor is not freezable at the moment, so not sharable. We are using expect/actual instead of Ktor.
k
k
We’ve done that too. If you only ever call it from one thread, or a fixed number of threads, it’s not an issue
If you’re calling from arbitrary threads, and need a destructor (which KN doesn’t have, and Java explicitly says you can’t rely on), you shouldn’t
@ThreadLocal often just means “don’t freeze”. The context is important
If you really want to stay off main thread, even though ktor just suspends immediatly on calls, I’d create a single thread to delegate these things to. If using the MT coroutines preview, it’s pretty simple to accomplish. The thread you’re calling from can block if needed.
k
yeah. in my case it all depends on how CoroutineWorker, and thus worker, handles the threads
k
I’m less experienced with CoroutineWorker
b
Happy to answer CoroutineWorker questions
k
the question really boils down to - how are the threads managed in the underlying implementation?
b
.execute
or
.withContext
will get you a thread in the thread pool. which one isn't guaranteed. suspending while on one of those threads and then resuming will return you back to that thread. Does that help?
k
are the threads in the thread pool "stable", or is there an idle policy where they can be shutdown and created new?
b
the threads aren't shutdown, if that's what you mean. here's the general flow of a thread in the pool: 1. attempt to process a job in the queue 2. if no job, return (i.e. Worker lambda ends) 3. if job, run job 4. when job is complete, go back to 1
k
👌