Previously the Task API had separate functions for cleanup and for handling cancellation. These functions were defined outside of the Task computation, which means Task had to return references to the allocated resources so those functions could do their jobs.
This obviously didn't work for places where resolution happened synchronously, as they'd have to be called before those references were returned. Calling them asynchronously meant that race conditions on when exactly "cancellation/cleanup" and pending resolutions happen were possible. Which is not a good thing.
Merely making all resolutions asynchronous wouldn't really fix the issue, although it would have made it less likely to happen.
Anyway, I decided to have the computation itself add handlers for cancellation and cleanup events, and those can be just called synchronously, as they'd have access to the resources in the task's scope — testing for the existence of a resource is still required, as cancellation may happen before a particular resource is properly initialised.
What this means is basically:
// BEFORE
task(
resolver => {
const res = doThings(() => {
resolver.resolve(value);
});
return res;
},
res => { cleanup... },
res => { cancellation... }
);
// AFTER
task(
resolver => {
const res = doThings(() => {
resolver.resolve(value);
});
return res;
}
);
Cancellation and cleanup handlers must be attached before a task resolves. It's an error to try attaching them after a task is resolved.
Previously the Task API had separate functions for cleanup and for handling cancellation. These functions were defined outside of the Task computation, which means Task had to return references to the allocated resources so those functions could do their jobs.
This obviously didn't work for places where resolution happened synchronously, as they'd have to be called before those references were returned. Calling them asynchronously meant that race conditions on when exactly "cancellation/cleanup" and pending resolutions happen were possible. Which is not a good thing.
Merely making all resolutions asynchronous wouldn't really fix the issue, although it would have made it less likely to happen.
Anyway, I decided to have the computation itself add handlers for cancellation and cleanup events, and those can be just called synchronously, as they'd have access to the resources in the task's scope — testing for the existence of a resource is still required, as cancellation may happen before a particular resource is properly initialised.
What this means is basically:
Cancellation and cleanup handlers must be attached before a task resolves. It's an error to try attaching them after a task is resolved.
This should fix #83… hopefully…