Integrating Racket into existing single-threaded event-loop c++ app

41 views
Skip to first unread message

Robert D Kocisko

unread,
Aug 4, 2020, 2:31:20 AM8/4/20
to Racket Users
I have an existing c++ app that is entirely event-loop driven with epoll and I am trying to figure out how to integrate Racket in the same thread and allow the existing code to work as-is.  I have read the docs about the C api and the FFI but so far a straightforward and clean option is not apparent to me.  The 'embedding' examples that I see appear rather opaque to me and at best seem to be geared towards an external loop based on select() rather than epoll(), so I'm assuming that the more straightforward approach would be to add the existing event handlers onto Racket's event system.  To that end, it seems there should be a way to register the fd's of interest with Racket and receive a callback when they are read-ready but I can't see a way to do that.  For example, how would I maintain a list of fds to pass to (sync) if I went that route?  Or if it's better to work it the other way, how would I call (sync) from c code and apply the list of fds to it?  I vaguely can see a way towards it but it seems at best super inefficient.  Any thoughts or suggestions would be greatly appreciated!

Thanks,
Bob Kocisko

Matthew Flatt

unread,
Aug 4, 2020, 10:08:45 AM8/4/20
to Robert D Kocisko, Racket Users
Fusing event loops is always tricky, but if you have the option of
exposing individual file descriptors to Racket, then `ffi/unsafe/port`
is probably the way to go. You can turn a file descriptor into an event
using `unsafe-file-descriptor->semaphore` or `unsafe-fd->evt`, and then
you can block on a set using `sync`, etc.

Matthew
> --
> You received this message because you are subscribed to the Google Groups
> "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to racket-users...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/racket-users/46fb3804-396c-4921-849f-71fe5a6f
> ac7ao%40googlegroups.com.

Robert D Kocisko

unread,
Aug 4, 2020, 2:55:11 PM8/4/20
to Matthew Flatt, Racket Users
Thanks Matthew!  That was my thought but given the number of fds in play and the frequency of adding and removing interest in them (they are added dynamically as needed) it seems like that option would be rather inefficient.  

Is there by chance any 'magic back door' which would allow me to register fds directly with Racket's underlying event loop so that I can bypass the extra hops of the thread scheduler and the ffi boundaries? Or if not what would need to be taken into consideration if I wanted to add such a back door via modifying Racket's source?  Are there any concerns with starving other threads or other timing issues? 

Also I can't help but be intrigued by (unsafe-poller).  I'm wondering what benefits it might give (if any) in comparison with the approach you suggested.

Thanks,
Bob

Matthew Flatt

unread,
Aug 4, 2020, 3:34:56 PM8/4/20
to Robert D Kocisko, Racket Users
You're right: I had momentarily confused epoll with poll. You can just
use the single file descriptor for an epoll object with things like
`unsafe-file-descriptor->semaphore`, or you can use `unsafe-poller`.

I'll note that `unsafe-file-descriptor->semaphore` scales to a large
number of file descriptors as long as you create a thread to block on
each semaphore. Under the hood, creating and triggering fd-semaphores
is implemented by incremental operations on an epoll object (or kqueue
object, etc.), and each blocking thread will be descheduled until a
semaphore post re-schedules it. But if you already have an epoll object
and the code to handle it, then you don't need the semaphore-based
machinery.

Matthew

Robert D Kocisko

unread,
Aug 4, 2020, 4:02:20 PM8/4/20
to Matthew Flatt, Racket Users
Genius! I had forgotten that the FD returned from epoll_create can itself be polled to indicate when any of the currently registered fds are ready to be processed.  That simplifies everything for sure. 

Given all the above I'm planning to pass the epoll FD to unsafe-fd->evt and then sync on it in a single thread which then calls the C code to process all the fd changes registered with that epoll instance.  

My only concern with this is whether that single thread might get mildly starved compared to other racket threads given that it technically represents hundreds of 'green threads' inside itself all implemented in C whereas every other racket thread represents one green thread.  Is there any way to hint to the thread scheduler that a particular thread needs higher scheduling priority than others? 

Also, in this scenario would unsafe-poller give any underlying performance benefit compared to using unsafe-fd->evt and sync?

Matthew Flatt

unread,
Aug 4, 2020, 4:14:17 PM8/4/20
to Robert D Kocisko, Racket Users
At Tue, 4 Aug 2020 14:01:20 -0600, Robert D Kocisko wrote:
> My only concern with this is whether that single thread might get mildly
> starved compared to other racket threads given that it technically
> represents hundreds of 'green threads' inside itself all implemented in C
> whereas every other racket thread represents one green thread. Is there
> any way to hint to the thread scheduler that a particular thread needs
> higher scheduling priority than others?

If you can arrange for all other threads to be in a separate group,
then all those threads together will have the same scheduling weight as
your one thread. I think that's the only mechanism for adjusting
weights, currently.

> Also, in this scenario would unsafe-poller give any underlying
> performance benefit compared to using unsafe-fd->evt and sync?

Probably not, since the `unsafe-fd->sync` uses `unsafe-poller` fairly
directly.


Matthew

Robert D Kocisko

unread,
Aug 4, 2020, 5:43:48 PM8/4/20
to Racket Users
Awesome, I didn't know about thread groups.  I'll check into that as an option.  I appreciate all your help!
Reply all
Reply to author
Forward
0 new messages