Conversation
|
I think I like this. The part that has been tripping me up is how to go from the scheduler to the dispatcher, but by scheduling a function that then resumes the continuation asynchronously we achieve that, as you explain. That means we have to document that what we pass to By the way, you can do |
|
Yeah, when you schedule a function through One area I'm not sure about is the |
only 6 left
|
We'll definitely have to be careful with the sync/async thing here, but I do think synchronous succeeding and failing has a purpose. Here in CoroBaseTask for example: public function awaitContinuation(cont:IContinuation<T>) {
switch (state.load()) {
case Completed:
cont.succeedSync(result);
case Cancelled:
cont.failSync(error);
case _:
awaitingContinuations ??= [];
awaitingContinuations.push(cont);
start();
}
}In this case we want to stay on the synchronous path because our task is already completed, so there's nothing to resume asynchronously. |
|
Oh, just when I thought I understood the basic idea you revert the exact part that I thought made it all work... Also, we need a way to set up a working Haxe/hxcoro combination with two PRs. I thought that we could just open a Haxe PR from your branch in order to get a Haxe build and use it here, but Haxe CI is going to fail without this PR too. @kLabz Could you modify the setup on the Haxe side so that we can point to a specific hxcoro branch in a single place? That way we could make Haxe point to the branch here in order to get a build, and then use that build here. ... of course we only get a build once the tests here are actually passing, but I suppose that's part of the challenge. |
This reverts commit cecaff7.
|
I only changed that to see if it changed one of the failing tests (it did not), so I've reverted it as I think it does make sense. The last few failing tests seem to be around cancellation "ordering", such as #47's tests. final cancelCause2 = new CancellationException();
task.cancel(cancelCause2);
Assert.isTrue(task.isActive());
|
|
I think that test is bad, I see no reason why the task would still need to be considered active after that |
|
I'm not sure if we should specify the isFalse either. Maybe it's better to make assumptions about the activity state of a task only after an |
|
I've updated that test to expect the active state to be false instead, I've also ignored two unbounded channel tests related to prompt cancellation. Looking at why those tests are failing reminded me that I meant to open an issue about cancellation polling (#67). With that the tests are passing when combined with the haxe side changes. |
|
That does make sense, I've removed the asserts which were occuring before the await call. My one reservation with this is how the user interacts with it, take the sample I posted in #63, the user now need to create a thread pool dispatcher instead of a thread pool scheduler. final channel = Channel.createUnbounded();
final dispatcher = new ThreadPoolDispatcher();
@:coroutine function foo(node) {
// pretend this coroutine is running on some frameworks UI thread
// the current scheduler would be MyUiFrameworkScheduler or whatever
scope(node -> {
// Spin up a couple of coroutines to compress the file bytes on the task pool
for (i in 0...4) {
node.with(scheduler).async(_ -> {
final out = new Out();
while (channel.reader.waitForRead()) {
if (channel.reader.tryRead(out)) {
final data = File.readAllBytes(out.get()); // suspension
final compressed = Zip.compress(data) // blocking
File.writeAllBytes(out.get(), compressed); // suspension
}
}
});
}
});
}If the dispatcher were replaced but not the scheduler then anything which calls |
|
@:coroutine @:coroutine.nothrow public static function yield():Void {
suspend(cont -> cont.callAsync());
}There are two major changes with this though, first, yield is no longer cancellable, second is that depending on the dispatcher it may not actually do anything "async", e.g. with our default TrampolineDispatcher. Some tests around cancellation also hang with this change. |
|
I think yield should remain cancellable because that's what's expected of built-in suspension points. As for the scheduler thing, I'm still not convinced that this should be a distinct context element. What's the use case for custom schedulers if we have have custom dispatchers? |
Uh. The setup modification sounds easy enough, but not sure what to do with that chicken and egg situation.. 🤔 Maybe we could proceed with |
|
I was worried about that too, but isn't it the case that the Haxe side will just work if it points to the correct hxcoro branch? In that case what we set as the Haxe version here doesn't matter because this CI isn't involved. And then once Haxe CI succeeds we can update the Haxe version here to make it work. |
|
Are tests expected to fail atm with this branch? Maybe I need a specific Haxe branch too? https://github.com/HaxeFoundation/haxe/actions/runs/21140877335/job/60794478583#step:10:341 |
|
Yes, the branch is this one: https://github.com/HaxeFoundation/haxe/tree/coro_dispatcher |
|
Thanks! Still failing, but less errors: https://github.com/HaxeFoundation/haxe/actions/runs/21141310817/job/60796000585#step:10:332 Party tests are not happy either 🤔 (wrong package?) |
|
Indeed, I have updated the package. Which also made me realize that And yes, utest, argh... I guess that needs another custom branch for now. |
|
Renaming
Are you thinking that we shouldn't expose changing the scheduler or merge the two concepts back together somehow? |
|
My current thinking is that the scheduler should be a property of the dispatcher, so that we can do |
|
That looks quite good to me now! I kind of forgot that Either way, I'm happy to proceed with this approach if you have no further reservations. It means that I'll have to update some documentation mentioning schedulers, but I'll survive that. |
|
I just ran the benchmarks because I expect this to be faster (higher is better):
It is indeed faster across the board, which makes sense because these dispatch calls no longer have to go through the heap. This also makes me realize that |
|
Yield is a weird one, having it be a schedule 0 feels weird but with our trampoline dispatcher that while true does bounce forever. But it being dispatch based would probably make more sense on other dispatchers, e.g. I was thinking an SDL dispatcher would push the function onto the event loop which wouldn't have this "bounce forever" issue. |
|
Wouldn't the new SDL dispatcher push everything onto the event loop anyway? In that case I don't see much value in bypassing the scheduler for this specific case. If anything always going through the scheduler for delay and yield gives us more consistency. |
|
Another thing I just remembed that needs to be done, the trampoline needs to be thread local. Each thread needs it's own trampoline so different threads can't throw stuff onto a different threads trampoline. This is another thing I took from Rx and in those libraries you don't create a trampoline scheduler, you get it through calls like |
# Conflicts: # src/hxcoro/CoroRun.hx
|
The utest situation has been resolved with #54 and haxe-utest/utest#136. It now uses Now we only make the two branches work with each other. |
|
js tests now pass https://github.com/HaxeFoundation/haxe/actions/runs/21176832134 Party tests still have an error https://github.com/HaxeFoundation/haxe/actions/runs/21176832134/job/60908043960#step:10:2098 Should I push to HaxeFoundation/haxe#12522 ? |
but don't use it because LuvScheduler needs an update too
|
I've updated And yes, please push to that PR's branch! |
|
LuvScheduler doesn't work anymore at the moment, somehow these |
|
8787aab made Eval hang on |
|
Found it already. That's probably the only test where we have a nested CoroRun entrypoint, and with the static value the dispatchers then shared the same trampoline. I'm not sure about the details but I think with |
I don't think this is completely correct yet because we likely need a better synchronization between AsyncDeque add/close, but it's enough to get the tests working again.
I think I understand why, but let's deal with this later.
|
This should be good to go. We can have fun with trampolines and the luv scheduler in a separate PR. I'll look into merging things in the correct order. |
Closes #63
Here's a bit of a dispatcher experiment, this removes the
scheduleObjectout of the scheduler. Using the blockingrunbuilders the default is the usualEventLoopSchedulerand aTrampolineDispatcher(This is a slightly fancierSelfDispatcherwhich avoids stack overflow / recursion issues).The
Asyncconvenience functions now use the dispatcher for the async-ness, so when something likedelayuses the scheduler to schedule execution in the future and then resumes asynchronously it's doing a scheduler -> dispatcher execution. In my mind the scheduler is the "when does this run" and the dispatcher decides "where this runs".I'm not sure this is useful or what it solves. Maybe if you have some complex situations where you have one thread running a libuv loop and another running a SDL loop you could have a Libuv scheduler which then dispatches onto the SDL event loop?