I’m working on Project Loom, a project in OpenJDK to add support for fibers (essentially user mode threads) and delimited continuations to the Java platform. Project Loom is in prototyping and exploration phase right now.
In Project Loom we are exploring many of the concepts that you are discussing here, Nathaniel and Martin’s blogs are excellent references. We hope to have something written up on the Project Loom wiki page soon.
The current prototype has the notion of a scope in which fibers are scheduled. There is basic support for cancellation, deadlines, and reaping of terminated fibers (to collect the results of tasks and exceptions). Terminology is a problem and “scope” is already overused.
Here’s the equivalent of the example of #1 where you have a server accepting connections:
ServerSocket listener = ...
try (var scope = FiberScope.cancellable()) {
while (...) {
Socket s = listener.accept();
scope.schedule(() -> handle(s));
}
}
scope.schedule(task)
schedules a fiber to execute a task, in this case it will handle a socket connection. The thread (or fiber) executing in the scope cannot exit until all fibers scheduled in the scope have terminated.
For #2, scopes are just objects so they live in the heap. You can pass them as parameters if you want but it may be more prudent to guard the reference (there a several discussion points there). A thread/fiber exited a scope causing it to be closed, no further fibers can be scheduled in the scope.
For #4, timeouts, a thread or fiber can enter a scope with a deadline (an instant in time) or a timeout (a maximum duration). If the deadline is reached or the timeout expires then all fibers scheduled in the scope are cancelled. Cancellation is a huge topic and there are significant challenges to retrofitting existing APIs.
The following is a more complete example that might be useful for the discussion here. It’s a method that returns the result of the first “successful” task. One a task completes successfully then all the outstanding tasks are cancelled. If no task succeeds it returns the exception from the first task to fail. The method enters a scope with a deadline so that all fibers are cancelled if the deadline expires before a result is returned:
<V> V anySuccessful(Callable<? extends V>[] tasks, Instant deadline) throws Throwable {
try (var scope = FiberScope.withDeadline(deadline)) {
var queue = new FiberScope.TerminationQueue<>();
Arrays.stream(tasks).forEach(task -> scope.schedule(task, queue));
Throwable firstException = null;
int remaining = tasks.length;
while (remaining > 0) {
try {
V result = queue.take().join();
// cancel any fibers that are still running
scope.fibers().forEach(Fiber::cancel);
return result;
} catch (CompletionException e) {
if (firstException == null) {
firstException = e.getCause();
}
}
remaining--;
}
throw firstException;
}
}
This example uses a termination queue to collect fibers as they terminate. This is a different approach to automatically propagating exceptions.
There are a few other concepts such as nesting and a “non-cancellable” scope to shield fibers from cancellable during recovery, cleanup or critical operations. This may be relevant to some of the discussion here.