Grigorii Yurkov
05/22/2022, 10:42 AM0
. Because i
can be modified from different threads and kotlin compiler doesn't mark it as volatile
under the hoodJoffrey
05/22/2022, 10:47 AMswitchThread()
, and which dispatcher are you running check()
in? Are you using Unconfined
?Grigorii Yurkov
05/22/2022, 10:48 AMDispatcher
from std lib, I also could get this code run on different threads, because `Dispatcher`s have many threads under the hooduli
05/22/2022, 11:06 AMJoffrey
05/22/2022, 11:06 AMi
. Each of the 3 sections of the check
method are run during 3 different invocations of that method, each of which defining their own local i
. The first section sets it to 0 and stores it in the continuation, the other 2 invocations initialize their own i
from the continuation object.ephemient
05/22/2022, 11:11 AMswitchThread()
synchronizes, i
is safeuli
05/22/2022, 11:11 AMephemient
05/22/2022, 11:13 AMswitchThread()
does not involve any synchronization or volatile operations, then indeed an older value of non-volatile i
could be observedGrigorii Yurkov
05/22/2022, 11:13 AMi
, because kotlin compiler optimizes code and create just 1 instance of Continuation
where our i
is storedephemient
05/22/2022, 11:15 AMJoffrey
05/22/2022, 11:17 AMi
, but a single continuation to/from which the value of the local `i`s are read/written. So marking the local i
volatile would be pointless. I might be wrong in concluding that solves the problem though (I just checked that the synthetic Continuation
fields are not marked volatile either)Grigorii Yurkov
05/22/2022, 11:18 AMswitchThread()
so it becomes thread safe? Because I don't see how it's possible. And I don't understand how std lib makes it thread safeephemient
05/22/2022, 11:20 AMGrigorii Yurkov
05/22/2022, 11:21 AMephemient
05/22/2022, 11:24 AMi
to be observed in the right order as well, even with its field in Continuation
being non-volatileGrigorii Yurkov
05/22/2022, 11:28 AMDispatcher
sources I will find synchronization there?ephemient
05/22/2022, 11:29 AMkotlinx.atomicfu
but yesuli
05/22/2022, 11:34 AMyield
serve your purpose?Grigorii Yurkov
05/22/2022, 11:39 AMuli
05/22/2022, 11:40 AMephemient
05/22/2022, 11:40 AMIf x and y are actions of the same thread and x comes before y in program order, then hb(x, y).(regardless of whether x and y are volatile or not, if x occurs before y on a single thread, then they are ordered)
If an action x synchronizes-with a following action y, then we also have hb(x, y).(is what defines happens-before edges across threads) so while non-volatile reads and writes between threads have no defined ordering relative to each other, once you add any synchronization then everything that was ordered before it, is also ordered before anything after it
Grigorii Yurkov
05/22/2022, 11:51 AMephemient
05/22/2022, 12:38 PMGrigorii Yurkov
05/22/2022, 12:45 PMephemient
05/22/2022, 12:49 PMstreetsofboston
05/22/2022, 12:51 PMvar i
is safe from race conditions. Coroutines guarantee that you can call suspend funs sequentially safely and not worry about race conditions and volatility.Grigorii Yurkov
05/22/2022, 12:52 PMqueue
safe, but I don't see why reading and writing from i
becomes safe😞ephemient
05/22/2022, 12:52 PMvar i
actually becomes part of the heap (in the generated Continuation
). we are discussing how Kotlin makes that safe, because it's actually not the same safety as you have with a local variablei
to be committed, and reading from a synchronized queue forces the updated i
to be observedhb(i++, queue.put())
on one thread, hb(queue.put(), queue.take())
across threads, and hb(queue.take(), i)
on the new threadGrigorii Yurkov
05/22/2022, 12:58 PMephemient
05/22/2022, 12:59 PM