Feedback on the API
Alan Bateman
Alan.Bateman at oracle.com
Fri Feb 22 10:00:07 UTC 2019
On 21/02/2019 22:17, Volkan Yazıcı wrote:
> Hello,
>
> Even though the current prototype interfaces are not finalized yet, I would
> like to share some feedback in no particular order.
Thanks for taking time to try it out.
>
> - Fiber.schedule() ergonomics is not aligned with Executor#invoke*() and
> Executor#submit().
Once we get more confidence with fiber scopes then I think we can
looking at replacing the Fiber.schedule methods (there is an API note
about this in the javadoc but might not be obvious).
> - Maybe it is just me, but I anticipate people will try to reach out for
> the fiber counterpart of Thread.sleep(). (I am very well aware of
> Thread.sleep() does not block the fiber scheduler thread.)
TBD, for now just call Thread.sleep (which will park the fiber rather
than the carrier thread as you have found).
> - Propagating InterruptedExceptions via
> Thread.currentThread().interrupt() is a pretty common practice. That said,
> typing Thread.currentThread().interrupt() in a fiber body feels like I am
> doing something wrong.
We are exploring cancellation of fibers but with different semantics
that avoids the error prone catching of InterruptedException and
re-asserting of the interrupt status. I hope to have something on the
Loom wiki about this soon.
> - FiberScope interface feels like a black box. Is it supposed to be
> extended? Am I constrained to the default provided ones? What does a
> FiberScope technically deliver? It is not really intention revealing.
> - Please don't pollute FiberScope with static accessors of particular
> implementations, e.g., FiberScope.currentDeadline(). FiberScope.current()
> would be good enough. Then each implementation can introduce their own
> accessors.
There is deliberately no FiberScope.current() at this time, this is
partly because the current prototype uses AutoCloseable and we need to
reduce the temptation to call close in random places.
The currentDeadline() method is to make it easy to get the current
deadline, which may have been set further up the stack in some enclosing
scope. More on this below.
> - Isn't there a garbage-free way to access the result of a Fiber? Do we
> always need to convert it into a Future?
Did you find the Fiber::join method?
> - It would be handy if users could pass their own BlockingQueue
> implementations to FiberScopeImpl. (This is a pain point in
> ScheduledThreadPoolExecutor, IMHO.)
TerminationQueue is deliberately final at this time - this is because it
would amount to arbitrary code executing when a fiber terminates. For
now, the implementation gets away with queuing the terminated fiber from
the carrier thread where the fiber was mounted when it terminated. There
are several subtle issues around this that we are avoiding by avoiding BYOQ.
> - In its current form, FiberScope needs to be explicitly passed to
> Fiber.schedule().
They were temporary for the initial push, they are gone now. In the
current prototype you can do this:
try (var scope = FiberScope.withDeadline(...)) {
Fiber<String> fiber1 = scope.schedule(() -> "foo");
Fiber<String> fiber2 = scope.schedule(() -> "bar");
}
> If you schedule a fiber within a scope and that fiber
> directly or indirectly schedules a new fiber discarding the scope in use,
> your beautiful scope serves no purpose anymore. And I think this leakage of
> fibers totally contradicts with the goal of structured concurrency. I can
> understand fixing this incurs a well thought new concept, e.g., special-,
> FiberScoped-, TaskLocal-, etc. variables. Though unless one can implicitly
> employ the active scope for the upcoming fiber schedulings, FiberScope is
> no better than hand crafted deadline facilities, which I believe what we
> are trying to avoid.
Ah, I suspect you might have missed that they nest. Here's an example
that might help:
void foo() {
try (var scope1 = FiberScope.cancellable()) {
var fiber1 = scope1.schedule(() -> bar());
}
}
void bar() {
try (var scope2 = FiberScope.cancellable()) {
var fiber2 = scope2.schedule(...);
}
}
A thread (or fiber) invoking foo enters scope1, schedules fiber1, and
then exits. Exiting scope1 waits for fiber1 to terminate (if it hasn't
terminated already)
fiber1, initially in scope1, enters scope2, schedules fiber2, and then
exits. Existing scope2 waits for fiber2 to terminate, at which point
fiber1 is back in scope1.
So no leakage and no need to pass the scope around. There will be cases
where you might need to schedule a fiber that outlives the context where
it is created. For now this is the "detached" scope, e.g.
FiberScope.detached().schedule(() -> nixer());
"detached" is a lousy name, it's really the primordial scope (which
cannot be closed/exited). I'm sure we'll converge on something better in
time.
You mention locals/variables. There isn't anything yet but this is an
important topic for exploration.
> - While "scope" term in FiberScope makes perfect sense, I find
> "ContinuationScope" confusing. It rather feels like a "state" than a
> "scope".
The continuation scope is important nested continuations. But yes, we
have too many concepts using the term "scope" right now. I'm sure that
better names will be found once the various areas of exploration are
further along.
-Alan
More information about the loom-dev
mailing list