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
IDEA-352355 Asynchronous stack traces for flows in the IDEA debugger
Patched during cherry-pick to 1.10.1. File with conflicts: BufferedChannel.kt
Squashed commits as of version 1.8.0-intellij-11
Don't make unnecessary NULL unboxings
(cherry picked from commit 0267812)
Don't wrap suspend function
(cherry picked from commit 8684cad)
Get rid of changes in the public API -- bring back CoroutineChannel support
(cherry picked from commit 9836a6b)
Get rid of changes in the public API
(cherry picked from commit f852554)
Support SelectImplementation
This brings support for select-based flow operators, such as `timeout`.
(cherry picked from commit db906d8)
Support BufferedChannel
Before, `buffer` operations and such were only supported partially, and in some cases async stack traces were working only in 50% collects. For example, when several flows are merged with `flattenMerge` and emit values simultaneously.
This change seems large, changing a lot of lines in BufferedChannel.kt, but most of them are effectively refactoring (propagation of a wrapped value).
(cherry picked from commit 8be4def)
Discard strict double-wrapping check
We decided not to go with it, as it may dump a lot of error messages to a clueless user's console.
(cherry picked from commit 0efa558)
Enhance support for async stack traces in flows
* simplify instrumentation by making a single insertion point source instead of having one in every class
* handle a double-wrapping case which leads to errors; allow agent to choose how to handle it
* support more commonly used operators (such as `scan`, `buffer`, `debounce` with dynamic timeout)
Unfortunately, this change doesn't cover all possible scenarios of using flows, as many of them interoperate with `Channel`s, and it should be addressed separately.
(cherry picked from commit 00cb4e5)
Prepare shared flows for the debugger agent to support async stack traces
The agent needs three entities to establish a proper asynchronous stack traces connection:
- a capture point -- method that indicates the stack trace that precedes the current stack trace;
- an insertion point -- method within the current stack trace;
- a key -- an object that is present in both points and is unique enough to bridge two points properly.
This change tweaks the code a bit to introduce the three entities in MutableSharedFlow and MutableStateFlow.
The key for MutableSharedFlow is the element itself. For MutableSharedFlow, the element is wrapped into a unique object to prevent bridging mistakes when two equal elements are emitted from different places.
(cherry picked from commit 75107bc)
# Conflicts:
# kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt
# Conflicts:
# IntelliJ-patches.md
Copy file name to clipboardExpand all lines: IntelliJ-patches.md
+39Lines changed: 39 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -50,3 +50,42 @@ exceeding the parallelism limit would eliminate this (likely expected) side effe
50
50
associated coroutine dispatcher when it decides to park the thread
51
51
-`CoroutineDispatcher.softLimitedParallelism` – an analogue of `.limitedParallelism` which supports
52
52
parallelism compensation
53
+
54
+
## Asynchronous stack traces for flows in the IDEA debugger
55
+
56
+
The agent needs three entities to establish a proper asynchronous stack traces connection:
57
+
- a capture point — method that indicates the stack trace that precedes the current stack trace;
58
+
- an insertion point — method within the current stack trace;
59
+
- a key — an object that is present in both points and is unique enough to bridge two stack traces properly.
60
+
61
+
The key for MutableStateFlow is the element itself. For MutableSharedFlow, the element is wrapped into a unique object to prevent bridging mistakes when two equal elements are emitted from different places.
62
+
63
+
Most of the operators applicable to flows (such as `map`, `scan`, `debounce`, `timeout`, `buffer`) are supported. As some of them use an intermediary flow inside, the transferred values are wrapped and unwrapped the same way as in MutableSharedFlow.
64
+
It means there may be all-library async stack traces between a stack trace containing `emit` and a stack trace containing `collect`.
65
+
66
+
### API
67
+
68
+
Some logic related to instrumentation was extracted to separate methods so that the debugger agent could instrument it properly:
69
+
70
+
-`kotlinx.coroutines.flow.internal.FlowValueWrapperInternal` -- wrapper class used to create a unique object for the debugger agent
71
+
-`kotlinx.coroutines.flow.internal.FlowValueWrapperInternalKt.wrapInternal` -- returns passed argument by default; the agent instruments it to call `wrapInternalDebuggerCapture` instead
72
+
-`kotlinx.coroutines.flow.internal.FlowValueWrapperInternalKt.wrapInternalDebuggerCapture` -- wraps passed arguments into a `FlowValueWrapperInternal`; only used after transformation.
73
+
-`kotlinx.coroutines.flow.internal.FlowValueWrapperInternalKt.unwrapInternal` -- returns passed argument by default; the agent instruments it to call `unwrapInternalDebuggerCapture` instead
74
+
-`kotlinx.coroutines.flow.internal.FlowValueWrapperInternalKt.unwrapInternalDebuggerCapture` -- unwraps passed argument so it returns the original value; only used after transformation
75
+
-`kotlinx.coroutines.flow.internal.FlowValueWrapperInternalKt.unwrapTyped` -- utility function served to ease casting to a real underlying type
76
+
-`kotlinx.coroutines.flow.internal.FlowValueWrapperInternalKt.emitInternal(FlowCollector, value)` -- alternative of a regular `FlowCollector.emit` that supports insertion points; if there is a `FlowCollector`, its `emit` call can be replaced with `emitInternal` so this case would also be supported for constructing async stack traces
77
+
-`kotlinx.coroutines.flow.internal.FlowValueWrapperInternalKt.debuggerCapture` -- common insertion point for a debugger agent; simplifies instrumentation; the value is always being unwrapped inside.
78
+
79
+
One internal method was added to `BufferedChannel`: `emitAllInternal`. This method ensures the value will be unwrapped in an insertion point.
80
+
81
+
One internal method was added to `flow/Channels.kt`: `emitAllInternal`. It emits all values, like usual, but also considers wrapping/unwrapping supported in `BufferedChannel`.
82
+
83
+
One internal method was added to `ChannelCoroutine`: `emitAllInternal` serves to bridge its delegate and the method above.
84
+
85
+
One internal method was added to `BufferedChannelIterator`: `nextInternal` -- same as `next` but may return a wrapped value. It should only be used with a function that is capable of unwrapping the value (see `BufferedChannel.emitAll` and `BufferedChannelIterator.next`), so there's a guarantee a wrapped value will always unwrap before emitting.
86
+
87
+
Why not just let `next` return a maybe wrapped value? That's because it is heavily used outside a currently supported scope. For example, one may just indirectly call it from a for-loop. In this case, unwrapping will never happen, and a user will get a handful of `ClassCastException`s.
88
+
89
+
Changes were made to lambda parameter `onElementRetrieved` in `BufferedChannel<E>` methods: now they accept `Any?` instead of `E` because now they may be given a wrapped value.
90
+
91
+
`SelectImplementation.complete` now uses `debuggerCapture` to properly propagate value that might come from flows.
0 commit comments