I recently tried to implement effects and fibers in Lua (using Lua’s coroutines). I stumbled upon the problem that when implementing fibers through effects with a naïve implementation, then spawning a new fiber would cause the fiber to be executed in the outer context of the scheduler rather than being subject to local effect handlers.
I was curious how OCaml solves this problem, but it looks like it doesn’t address the issue. Thus a fiber does not obey any effect handlers that are in between starting the main event loop and spawning the fiber.
Consider the following example (which uses the libraries eio
and eio_main
):
type _ Effect.t += Log : string -> unit Effect.t
let log message = Effect.perform (Log message)
let do_not_log f =
Effect.Deep.try_with f ()
{
effc =
(fun (type a) (eff : a Effect.t) ->
match eff with
| Log _ ->
Some
(fun (k : (a, _) Effect.Deep.continuation) ->
Effect.Deep.continue k ())
| _ -> None);
}
let log_to_stdout f =
Effect.Deep.try_with f ()
{
effc =
(fun (type a) (eff : a Effect.t) ->
match eff with
| Log message ->
Some
(fun (k : (a, _) Effect.Deep.continuation) ->
Printf.printf "Log: %s" message;
print_newline ();
Effect.Deep.continue k ())
| _ -> None);
}
let foo () = log "doing foo"
let do_twice_in_parallel f = Eio.Fiber.both f f
let bar () =
foo ();
do_twice_in_parallel foo
let main _ =
print_endline "### Executing foo with logging";
log_to_stdout foo;
print_endline "### Executing foo without logging";
do_not_log foo;
print_endline "### Executing bar with logging";
log_to_stdout bar
let () = Eio_main.run main
Output:
### Executing foo with logging
Log: doing foo
### Executing foo without logging
### Executing bar with logging
Log: doing foo
Fatal error: exception Stdlib.Effect.Unhandled(Dune__exe__Fiber_test.Log("doing foo"))
Both in regard to OCaml and in regard to my own experiments in Lua, I’m curiuous whether this behavior is well-known and/or desired.