Closed mraleph closed 6 months ago
Okay solid - interesting situation here - are you able to share a git link to the problematic code? I'm wrapping up named locks tonight and can take a look at this as well (no promises but definitely happy to take a crack at it locally)!
I'm assuming the you're calling something like mailbox.put within the isolate before calling mailbox.take?
The code in question is here:
https://github.com/onepub-dev/dcli/blob/master/dcli/lib/src/process/process/process_in_isolate.dart
After spawning isolate the call to _connectSendPort makes the call to take.
I'm locally playing with some alternate patterns, but if you have any ideas I would be pleased to here them.
I'm assuming the you're calling something like mailbox.put within the isolate before calling mailbox.take?
This is the crux of the problem.
Because we can't 'await' the spawn we can't know if the mailbox has been primed via a call to put. Any sync call (from the primary isolate) to determine if the mailbox is primed, stops the spawn completing.
Catch 22.
On Wed, Apr 3, 2024 at 8:20 AM Tsavo Knott @.***> wrote:
I'm assuming the you're calling something like mailbox.put within the isolate before calling mailbox.take?
— Reply to this email directly, view it on GitHub https://github.com/dart-lang/sdk/issues/52121#issuecomment-2033118791, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG32OHFPT2QHCKIAZTX7ADY3MOIHAVCNFSM6AAAAAAXGRJJN6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMZTGEYTQNZZGE . You are receiving this because you were mentioned.Message ID: @.***>
@bsutton okay solid - I believe named locks can actually help here with something like the following usage.
_startIsolate(settings, channel);
call_startIsolate(settings, channel);
call, we call proceed to call NamedLock.unlock()
from the main thread allowing the isolate to successfully complete its NamedLock.acquire()
call, lock the NamedLock within the Isolate and allow it to proceed and call mailbox.put()
NamedLock.acquire()
ahead of our mailbox.take()
can introduce a small sleep
if needed but this main thread NamedLock.acquire()
will block mailbox.take()
until the isolate calls NamedLock.unlock()
mailbox.put()
it proceeds to call NamedLock.unlock()
or NamedLock.dispose()
unblocking the main threads blocking NamedLock.acquire()
preceding the call to mailbox.take()
6 is problematic. As we can't know if the spawned isolate ever took the lock. So we would have to sleep (which slows launch down) and we still have no guarantees.
But the real problem is that the sleep is sync which will stop the isolate spawning.
I could be wrong but I don't think the isolate gets spawned until some Async code executes. If this is correct the there is no way around the problem.
I think we need some input from the dart Devs.
On Wed, 3 Apr 2024, 9:29 am Tsavo Knott, @.***> wrote:
@bsutton https://github.com/bsutton okay solid - I believe named locks can actually help here with something like the following usage.
- Create a NamedLock instance on the Main Thread and immediately lock it
- Pass in the name of the NamedLock to the Isolate through the _startIsolate(settings, channel); call
- Create a NamedLock instance within the Isolate Thread from the passed name
- The Isolate then tries to acquire the NamedLock with a blocking NamedLock.acquire() call
- Back on the main thread, right after the _startIsolate(settings, channel); call, we call proceed to call NamedLock.unlock() from the main thread allowing the isolate to successfully complete its NamedLock.acquire() call, lock the NamedLock within the Isolate and allow it to proceed and call mailbox.put()
- Again on the main thread, we call NamedLock.acquire() ahead of our mailbox.take() can introduce a small sleep if needed but this main thread NamedLock.acquire() will block mailbox.take() until the isolate calls NamedLock.unlock()
- Lastly on the Isolate side right after it calls mailbox.put() it proceeds to call NamedLock.unlock() or NamedLock.dispose() unblocking the main threads blocking NamedLock.acquire() preceding the call to mailbox.take()
— Reply to this email directly, view it on GitHub https://github.com/dart-lang/sdk/issues/52121#issuecomment-2033208826, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG32OC7UVVQAUEHL4SYO4LY3MWM3AVCNFSM6AAAAAAXGRJJN6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMZTGIYDQOBSGY . You are receiving this because you were mentioned.Message ID: @.***>
Absolutely - looking forward to input from Dart Devs as well!
Last thing to note is that the NamedLocks implementation I've been working on has a native shared memory map/counter to count the number of threads/processes that have a NamedLocks instance with an unresolved NamedLocks.acquire()
call. If the number of expected & unique threads calling NamedLock.acquire()
is deterministic then perhaps we can leverage this to know?
I've raised this issue to create spawnSync as I think it is the only workable solution:
Awesome - good looks 🫡 Looking forward to hearing more from the Dart team!
We'll get there - it's always a bit frustrating in the moment but gonna be solid 🤝
appreciate you @bsutton!
Deleted my comment after further research since I can see you want to interact with the spawned process in a sync matter with stdin and stdout.
Change Intent
waitFor
contradicts Dart's event-loop model in a way that no other old or new feature does: it allows async and synchronous code to interleave. The feature has been marked experimental since the beginning and effectively has only two users: sass and dcli package. It is not available in Flutter or on the Web becausedart:cli
library is not exposed there.See discussion on https://github.com/dart-lang/sdk/issues/39390 for context.
Justification
We would like to reduce VM's technical debt by removing this experimental functionality, which we believe was added hastily and without due thought to the consequences.
We also have some indications (based on some internal code which has been migrated from
waitFor
) that availability ofwaitFor
encourages ineffecient and convoluted coding patterns.If you compare
waitFor
withFinalizer
(which was added in 2.17) you will observe that we choose to makeFinalizer
less powerful by specifying that finalizers are only invoked at the turn of the event loop for the sake of maintaining semantic purity around interleaving of synchronous code.Impact
Current users (which basically amounts to dcli and sass) will have to migrate off
waitFor
which will require them to rewrite their code.Mitigation
Based on our analysis of dcli and sass multiple migration strategies are available:
dart:io
methods can be replaced with synchronous counterparts, eliminating the need forwaitFor
;dart:ffi
primitives;Note that it is an explicit non-goal to provide a completely equivalent replacement for
waitFor
, because we believe the feature itself is incompatible with how Dart's async is designed and should be removed.Synchronous communication over dart:ffi
This code demonstrates how
dart:ffi
can be used to establish an entirely synchronous communication channel between the main (dispatcher) isolate and a worker isolate. This type of communication channel should cover sass needs.To make migration simpler we will provide a portable synchronization package which implements mutexes and conditional variables, though we leave implementation of cross-isolate communication channels to the developers.
Isolate.resolvePackageUri
Some minor users of
waitFor
use it to synchronously unwrapFuture
returned fromIsolate.resolvePackageUri
API. In reality the underlying implementation ofIsolate.resolvePackageUri
is entirely synchronous.While there is migration path for this code using
dart:ffi
and isolates we can simplify things by directly exposingIsolate.resolvePackageUriSync(...)
and deprecating async version of this API.Timeline
The removal will follow this timeline:
waitFor
feature was marked deprecatedwaitFor
is now disabled by default, but can be temporarily enabled by passing the flag--enable_deprecated_wait_for
to the VM /dart
commandwaitFor
and there will be no way of enabling it