Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yet another polling system sketch #3296

Closed

Conversation

armanbilge
Copy link
Member

The idea of this sketch is to define a trait EventLoop[Registrar] extends ExecutionContext that provides access to a "registrar" (e.g. a Selector or io_uring) for registering callbacks on sockets etc., but only if you are currently running on that ExecutionContext.

Then, every Network is bound to a specific EventLoop and must .evalOn(...) to that loop whenever it needs to interact with the registrar. In the ideal scenario this evalOn will be a no-op (i.e. already running on the WSTP which the desired polling system installed).

My goals were to:

  1. Provide a unified interface for working with polling systems, that can be implemented by both the WSTP and an I/O dispatcher thread. This saves FS2 from having to maintain two separate implementations depending on whether the polling system is available in the WSTP or not.
  2. Address the problem of being on the wrong EC when trying to interact with the polling system, by providing a straightforward mechanism to get on the right EC.

Usual caveats: rough sketch, names subject to bikeshed, bla bla :)

Example usage:

object Network {
  def apply[F[_]](implicit F: Async[F]): Resource[F, Network[F]] =
    Resource.eval(F.executionContext).flatMap {
      case EventLoop[Selector](loop) =>
        // we're on an EC with the desired polling system
        Resource.pure(loop)
      case _ =>
        Resource.make {
          // make a single-thread I/O dispatcher for the desired polling system
          F.delay(EventLoop.fromPollingSystem(SelectorSystem))
        } { case (_, release) => F.delay(release()) }
       .map { case (loop, _) => loop }
    }.map { loop =>
       new Network[F] {
         // ...
         def doSomeIO(): F[Unit] =
           F.delay {
             // do stuff ...
             val selector = loop.registrar()
             selector.registerCallback(...)
           }.evalOn(loop) // but do it on the loop, no matter who calls us
       }
    }
}

Note: although here I've demoed it at the Network level, this can also be done at the Socket level, which would be more friendly to FS2's current encoding of Network as a global implicit.

@armanbilge armanbilge marked this pull request as draft December 1, 2022 05:39
Comment on lines +41 to +43
def fromPollingSystem(
name: String,
system: PollingSystem): (EventLoop[system.Poller], () => Unit) = {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A single-thread dispatcher from a polling system. I just tossed this together, I doubt it's any good.

I suspect that there will be objections to including this in CE. My argument for why we should have this is:

  1. implementing a good event dispatcher thread is non-trivial
  2. in the case when the WSTP does not have the desired polling system installed, to provide a fallback implementation a dispatcher thread will be necessary.

@djspiewak
Copy link
Member

Haven't had a chance to read through this yet, but just at a high level, I do think that evalOn is probably the right approach to resolving the "you might be on the wrong thing". It doesn't fully solve the problem unless you also include a LiftIO constraint, but it helps.

Comment on lines +34 to +39
object EventLoop {
def unapply[R](loop: EventLoop[Any])(ct: ClassTag[R]): Option[EventLoop[R]] =
if (ct.runtimeClass.isAssignableFrom(loop.registrarTag.runtimeClass))
Some(loop.asInstanceOf[EventLoop[R]])
else
None
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So instead of this pattern match maybe we should expose this as IO.eventLoop or something.

@armanbilge armanbilge mentioned this pull request Dec 11, 2022
2 tasks
@djspiewak djspiewak closed this Dec 25, 2022
@armanbilge armanbilge mentioned this pull request Dec 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants