[racket] FFI question: a callback that will be called by some foreign thread

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Mon Jul 12 08:24:33 EDT 2010

At Sun, 11 Jul 2010 09:58:39 -0500, Chongkai Zhu wrote:
> Using FFI, one can use _cprocedure to pass Scheme procedures to foreign 
> functions as C callback. For my case, if the callback function is called 
> from a different C thread (unknow to PLT FFI), it crashes. Adding 
> #:atomic? #t doesn't help. Is there a workaround, or could I conclude 
> that FFI can't pass a Scheme procedure to be used as a C callback from 
> foreign threads?

There was no way to use a Racket procedure as a C callback that is
invoked a foreign thread. That possibility seems useful, though, and it
requires an extension to the FFI, so I've added something minimal.

A new `#:async-apply' optional argument to `_fun' lets you handle the
case that a callback is invoked in a foreign thread. The callback still
has to run in the OS thread that runs Racket, but the foreign thread
blocks until the callback produces a result.

The `#:async-apply' function introduces one level of indirection: it
receives a thunk that encapsulates the callback invocation from a
foreign thread. The job of the `#:async-apply' function is to schedule
the callback to run in some suitable thread; the `#:async-apply'
function itself runs in an unspecified Racket thread and in atomic
mode, which may be too constrained an environment for the callback
function. When the thunk-encapsulated callback completes, the foreign
thread receives the result and continues (i.e., the `#:async-apply'
function doesn't have to do anything afterward).

This new mode doesn't provide a way to run Racket code in multiple OS
threads. It just provides a way to have callbacks in foreign threads
trigger evaluation in the Racket thread.

An example use (that does nothing useful other than demonstrate
`#:async-apply') is below. It should work for Linux or Mac OS X. You
can double-check that commenting out the `#:async-apply' part of the
`_callback' definition produces a crash.

;----------------------------------------

#lang racket
(require ffi/unsafe
         ffi/unsafe/atomic)

;; A queue that implements locking by atomic actions,
;; since an async-apply function cannot block on a lock.
(define sema (make-semaphore))
(define queue null)
(define (enqueue thunk)
  (set! queue (append queue (list thunk)))
  (semaphore-post sema))
(define (dequeue)
  (semaphore-wait sema)
  (start-atomic)
  (let ([v (car queue)])
    (set! queue (cdr queue))
    (end-atomic)
    v))

;; Thread to run async calls in the background:
(thread (lambda ()
          (let loop ()
            (let ([thunk (dequeue)])
              (thunk)
              (loop)))))

(define _callback (_fun #:async-apply enqueue
                        _pointer -> _pointer))

(define _pthread (_cpointer 'pthread))

(define pthread-create
  (get-ffi-obj 'pthread_create #f
               (_fun (t : (_ptr o _pthread))
                     (_pointer = #f)
                     _callback
                     (_pointer = #f)
                     -> (r : _int)
                     -> (if (zero? r)
                            t
                            (error "thread create failed")))))

(define done (make-semaphore))
(define t (pthread-create (lambda ignored 
                            (printf "running\n")
                            (semaphore-post done)
                            #f)))
(sync done)



Posted on the users mailing list.