You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a condensed description of the issue Space is suffering, so some details (e.g. the actual CPU impact, CPU snapshots, etc.) are omitted.
For coroutines, in order to keep track of ThreadContextElement, each undispatched (the one that doesn't change current coroutine dispatcher) withContext, a special thread-local variable is spawned in order to preserve and restore execution context in the corresponding thread.
It leads to multiple problems:
If the coroutine does not use thread-local elements, the corresponding system ThreadLocal is instantiated and queried, while being later reset to null
If system-wide ThreadContextElement is used in the application, then application spawns as much thread locals as there are calls to `withContext.
The rest is ThreadLocal implementation peculiarities:
ThreadLocal is actually stored in Thread's local map with open addressing by weak reference
ThreadLocal map entry is allocated on the first access (e.g. get()) even if it's null or wasn't set and has no initializer
If ThreadLocal is unreachable, it's collected, but its weak-reference slot is not
When thread-local map has reached its fill factor or encountered "stale" (GC-collected) entry, it attempts to clean it up with potential rehash
If coroutine lives long enough, it is promoted to old generation along with its ThreadLocal object
Because of that, entry can only be collected after an old generation GC which is rare enough event
Until then, any thread-local op that encounters rehash/stale entry will burn quite a lot of CPU processing 4k+ open-addressing hashmap
The text was updated successfully, but these errors were encountered:
…outine
* It addresses the problem with ThreadLocalMap.entries that may outlive the coroutine lifecycle and interfere with CPU consumption of other thread-locals on the same thread
* No test provided as this is a non-functioanl change. The only reasonable way to check it is to reflectively walk over Thread class which is prohibited by Java since 11+. The only way is to eyeball Thread.currentThread().threadLocals size in debugger in the properly crafted unit test
Fixes#3592
…outine
* It addresses the problem with ThreadLocalMap.entries that may outlive the coroutine lifecycle and interfere with CPU consumption of other thread-locals on the same thread
* No test provided as this is a non-functioanl change. The only reasonable way to check it is to reflectively walk over Thread class which is prohibited by Java since 11+. The only way is to eyeball Thread.currentThread().threadLocals size in debugger in the properly crafted unit test
Fixes#3592
…outine
* It addresses the problem with ThreadLocalMap.entries that may outlive the coroutine lifecycle and interfere with CPU consumption of other thread-locals on the same thread
* No test provided as this is a non-functioanl change. The only reasonable way to check it is to reflectively walk over Thread class which is prohibited by Java since 11+. The only way is to eyeball Thread.currentThread().threadLocals size in debugger in the properly crafted unit test
Fixes#3592
This is a condensed description of the issue Space is suffering, so some details (e.g. the actual CPU impact, CPU snapshots, etc.) are omitted.
For coroutines, in order to keep track of
ThreadContextElement
, each undispatched (the one that doesn't change current coroutine dispatcher)withContext
, a special thread-local variable is spawned in order to preserve and restore execution context in the corresponding thread.It leads to multiple problems:
ThreadLocal
is instantiated and queried, while being later reset tonull
ThreadContextElement
is used in the application, then application spawns as much thread locals as there are calls to `withContext.The rest is
ThreadLocal
implementation peculiarities:ThreadLocal
is actually stored in Thread's local map with open addressing by weak referenceThreadLocal
map entry is allocated on the first access (e.g.get()
) even if it'snull
or wasn'tset
and has no initializerThreadLocal
is unreachable, it's collected, but its weak-reference slot is notThreadLocal
objectThe text was updated successfully, but these errors were encountered: