Affect and Eio fibers together in a single application

I was experimenting few things with Eio Fibers and Affect Fibers.


let main () =
let  comp () =
    Eio_linux.run ( fun _env ->
    ignore (Fiber.run (fun () ->
    Eio.Std.Switch.run @@ fun sw ->
    (   
        Eio.Fiber.fork ~sw (
            fun () -> 
                    printf "\nEio Fiber 1 %!"
        );
        
        (* printf "\nReaching in between two threads%!";*)

       let _ = Fiber.spawn (
            fun () -> 
                printf "\nAffect Fiber 1 %!"
        ) in ();


        Eio.Fiber.fork ~sw (
            fun () -> 
            printf "\nEio Fiber 2 %!"
        );
    )
        )
            ) 
                )

    in
      match_with comp ()
      { retc = (fun () -> ());
        exnc = (function
          | Exit ->  ()
           );
        effc = fun (type a) (e : a Effect.t) ->
          match e with
          | _ -> None
       }    

let _ = main ()

When I ran the above program, I was expecting the output to be

Eio Fiber 1
Affect Fiber 1
Eio Fiber 2

Instead, I got the output as

Eio Fiber 1
Eio Fiber 2
Affect Fiber 1

With all permutations of Affect and Eio Fibers, I found that Affect fibers are always printed after Eio fibers. Affect fibers will not take action until all Eio fibers are executed. To check it further, I added a few print statements in Affect’s spawn functionality to understand its flow. From its output, it can be seen that control definitely reaches to spawn function, but it does not execute the input f at that time.

As a developer, I want to maintain sequentiality in my application. I want fibers to execute one after another. How can I achieve this in the above program? Which approach should I take to tackle this problem? I want to understand why Eio Fibers are taking priority over Affect fibers.

I just had a quick look at the Affect code. It’s probably because Affect’s spawn schedules the spawned code to run later and continues with the calling fiber (fiber.ml\src - affect - Composable concurrency primitives with OCaml effects handlers) whereas Eio’s fork immediately switches to the new fiber and pushes the caller back to the head of the run-queue:

See eio/rationale.md at main · ocaml-multicore/eio · GitHub for the rationale behind Eio’s behaviour.

However, I still wouldn’t expect this to work properly. You’ll need to do more work to integrate the two different event loops. For examples of this, see e.g.

If you do get Affect working with Eio, be sure to add some Affect code to GitHub - talex5/async-eio-lwt-chimera: Proof-of-concept for Async/Eio/Lwt in one process :wink:

1 Like