Cheng Shao <[email protected]> writes: > Hi devs, > > To invoke Haskell computation in C, we need to call one of rts_eval* > functions, which enters the scheduler loop, and returns only when the > specified Haskell thread is finished or killed. We'd like to enhance > the scheduler and add async variants of the rts_eval* functions, which > take C callbacks to consume the Haskell thread result, kick off the > scheduler loop, and the loop is allowed to exit when the Haskell > thread is blocked. Sync variants of RTS API will continue to work with > unchanged behavior. > > The main intended use case is async foreign calls for the WebAssembly > target. When an async foreign call is made, the Haskell thread will > block on an MVar to be fulfilled with the call result. But the > scheduler will eventually fail to find work due to empty run queue and > exit with error! We need a way to gracefully exit the scheduler, so > the RTS API caller can process the async foreign call, fulfill that > MVar and resume Haskell computation later. > > Question I: does the idea of adding async RTS API sound acceptable by > GHC HQ? To be honest, it's not impossible to workaround lack of async > RTS API: reuse the awaitEvent() logic in non-threaded RTS, pretend > each async foreign call reads from a file descriptor and can be > handled by the POSIX select() function in awaitEvent(). But it'd > surely be nice to avoid such hacks and do things the principled way. > While the idea here sounds reasonable, I'm not sure I quite understand how this will be used in Asterius's case. Specifically, I would be worried about the lack of fairness in this scheme: no progress will be made on any foreign call until all Haskell evaluation has blocked. Is this really the semantics that you want?
> Question II: how to modify the scheduler loop to implement this
> feature? Straightforward answer seems to be: check some RTS API
> non-blocking flag, if present, allow early exit due to empty run
> queue.
>
`schedule` is already a very large function with loops, gotos,
mutability, and quite complex control flow. I would be reluctant
to add to this complexity without first carrying out some
simplification. Instead of adding yet another bail-out case to the loop,
I would probably rather try to extract the loop body into a new
function. That is, currently `schedule` is of the form:
// Perform work until we are asked to shut down.
Capability *schedule (Capability *initialCapability, Task *task) {
Capability *cap = initialCapability;
while (1) {
scheduleYield(&cap, task);
if (emptyRunQueue(cap)) {
continue;
}
if (shutting_down) {
return cap;
}
StgTSO *t = popRunQueue(cap);
if (! t.can_run_on_capability(cap)) {
// Push back on the run queue and loop around again to
// yield the capability to the appropriate task
pushOnRunQueue(cap, t);
continue;
}
runMutator(t);
if (needs_gc) {
scheduleDoGC();
}
}
}
I might rather extract this into something like:
enum ScheduleResult {
NoWork, // There was no work to do
PerformedWork, // Ran precisely one thread
Yield, // The next thread scheduled to run cannot run on the
// given capability; yield.
ShuttingDown, // We were asked to shut down
}
// Schedule at most one thread once
ScheduleResult scheduleOnce (Capability **cap, Task *task) {
if (emptyRunQueue(cap)) {
return NoWork;
}
if (shutting_down) {
return ShuttingDown;
}
StgTSO *t = popRunQueue(cap);
if (! t.can_run_on_capability(cap)) {
pushOnRunQueue(cap, t);
return Yield;
}
runMutator(t);
if (needs_gc) {
scheduleDoGC();
}
return PerformedWork;
}
This is just a sketch but I hope it's clear that with something like
this this you can easily implement the existing `schedule` function, as
well as your asynchronous variant.
Cheers,
- Ben
signature.asc
Description: PGP signature
_______________________________________________ ghc-devs mailing list [email protected] http://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-devs
