synchronization with ffi/unsafe/os-thread

34 views
Skip to first unread message

Ryan Culpepper

unread,
Jan 26, 2021, 4:25:56 AM1/26/21
to Racket Users
I'm trying to figure out how to use ffi/unsafe/os-thread to call a long-running foreign function in an OS thread to avoid blocking other Racket threads. I want to communicate the result of the foreign call to the original Racket thread and have it wake up when the call completes. Normally I could use a channel, but OS threads are not allowed to use Racket synchronization primitives (channels, semaphores, etc). I can't use a spinlock, because the docs for call-in-os-thread say that mutations are allowed but their visibility is unspecified except as synchronized by os-semaphores. The docs for os-semaphore-wait say that if it is called by a Racket thread (ie, not a restricted OS thread), then waiting blocks all Racket threads, which I want to avoid.

Places already have to solve the problem of bridging OS threads and Racket synchronization. So here's my best idea so far: use a variable (or box) protected by an os-semaphore for communicating the result, but use a place channel for the synchronization. The code looks like this:

    (define result #f)
    (define os-sema (make-os-semaphore))
    (define-values (c1 c2) (place-channel))
    (call-in-os-thread
     (lambda ()
       (set! result (...))
       (os-semaphore-post os-sema)
       (place-channel-put c1 'ready)))
    (void (sync c2))
    (os-semaphore-wait os-sema)
    result

This "works", but is it reliably safe to use place-channel-put from an OS thread? Or is there a better way to do this?

Ryan

Matthew Flatt

unread,
Jan 26, 2021, 7:23:37 AM1/26/21
to ry...@racket-lang.org, Racket Users
At Tue, 26 Jan 2021 10:25:42 +0100, Ryan Culpepper wrote:
> This "works", but is it reliably safe to use place-channel-put from an OS
> thread?

No. It's not intended to work from an arbitrary OS thread, and because
`place-channel-put` touches the thread scheduler to enter atomic mode,
I can imagine that it might go wrong either now or with some small
future change.

> Or is there a better way to do this?

Probably the only way currently is to use `unsafe-poller`. See
"rktrl.rkt" in "readline" for an example. It would make sense to make
that part of `ffi/unsafe/thread` or a new `ffi/unsafe` library. (It
would also be good to add `unsafe-make-signal-received` to
`ffi/unsafe/schedule`.)


Matthew

Ryan Culpepper

unread,
Jan 26, 2021, 8:49:35 AM1/26/21
to Matthew Flatt, Ryan Culpepper, Racket Users
Thanks for the pointer! Those sound useful, but in the spirit of maximum caution, is there a guarantee that the write to the box from the new OS thread will be visible to the original Racket OS thread when the poller tries to read it? Is `box-cas!` or one of the memory-order operations needed?

Ryan

Matthew Flatt

unread,
Jan 26, 2021, 9:06:01 AM1/26/21
to ry...@racket-lang.org, Ryan Culpepper, Racket Users
At Tue, 26 Jan 2021 14:49:22 +0100, Ryan Culpepper wrote:
> Thanks for the pointer! Those sound useful, but in the spirit of maximum
> caution, is there a guarantee that the write to the box from the new OS
> thread will be visible to the original Racket OS thread when the poller
> tries to read it? Is `box-cas!` or one of the memory-order operations
> needed?

I think enough synchronization is implied by `(signal-received)` and
the way it interacts with the scheduler. That is, there's no guarantee
that the waiting thread sees a box change right away, but signaling the
waiting thread will imply a barrier on both the signaling side and
waiting side, so that the next poll iteration after receiving the
signal will definitely see the update at the latest. If that sounds
right, we could make that a guarantee for signaling and polling.

Ryan Culpepper

unread,
Jan 26, 2021, 10:29:03 AM1/26/21
to Matthew Flatt, Ryan Culpepper, Racket Users
That sounds reasonable. Thanks!

Ryan

Reply all
Reply to author
Forward
0 new messages