From 494b6f4fa15c4c1176282762f47ae0677061d052 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Tue, 22 Apr 2014 08:51:20 -0600 Subject: [PATCH] ffi documentation: add overview section on places and thread-safety --- .../scribblings/foreign/intro.scrbl | 71 ++++++++++++++++++- .../scribblings/foreign/types.scrbl | 3 +- .../racket-doc/scribblings/foreign/utils.rkt | 4 ++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/pkgs/racket-pkgs/racket-doc/scribblings/foreign/intro.scrbl b/pkgs/racket-pkgs/racket-doc/scribblings/foreign/intro.scrbl index 4428a8d19f..dae951f996 100644 --- a/pkgs/racket-pkgs/racket-doc/scribblings/foreign/intro.scrbl +++ b/pkgs/racket-pkgs/racket-doc/scribblings/foreign/intro.scrbl @@ -12,6 +12,8 @@ (lambda (stx) #'@schemeidfont{_WINDOW-pointer}))) @(define-syntax _mmask_t (make-element-id-transformer (lambda (stx) #'@schemeidfont{_mmask_t}))) +@(define-syntax _string/immobile (make-element-id-transformer + (lambda (stx) #'@schemeidfont{_string/immobile}))) @title[#:tag "intro"]{Overview} @@ -294,7 +296,7 @@ not enough space for an @cpp{MEVENT} struct. @section{Pointers and Manual Allocation} -To get text from the user instead of a mouse click, @racket{libcurses} +To get text from the user instead of a mouse click, @filepath{libcurses} provides @racket[wgetnstr]: @verbatim[#:indent 2]{ @@ -509,6 +511,73 @@ reference.scrbl]{custodian} management is more appropriate than mere finalization as implemented by @racket[allocator]. See the @racketmodname[ffi/unsafe/custodian] library. +@; -------------------------------------------------- + +@section{Threads and Places} + +Although older versions of @filepath{libcurses} are not thread-safe, +Racket threads do not correspond to OS-level threads, so using Racket +threads to call @filepath{libcurses} functions creates no particular +problems. + +Racket @tech-place[]s, however, correspond to OS-level threads. Using +a foreign library from multiple places works when the library is +thread-safe. Calling a non-thread-safe library from multiple places +requires more care. + +The simplest way to use a non-thread-safe library from multiple places +is to specify the @racket[#:in-original-place? #t] option of +@racket[_fun], which routes every call to the function through the +original Racket place instead of the calling place. Most of the +functions that we initially used from @filepath{libcurses} can be made +thread-safe simply: + +@racketblock[ +(define-curses initscr + (_fun #:in-original-place? #t -> _WINDOW-pointer)) +(define-curses wrefresh + (_fun #:in-original-place? #t _WINDOW-pointer -> _int)) +(define-curses endwin + (_fun #:in-original-place? #t -> _int)) +] + +The @racket[waddstr] function is not quite as straightforward. The +problem with + +@racketblock[ +(define-curses waddstr + (_fun #:in-original-place? #t _WINDOW-pointer _string -> _int)) +] + +is that the string argument to @racket[waddstr] might move in the +calling place before the @racket[waddstr] call completes in the +original place. To safely call @racket[waddstr], we can use a +@racket[_string/immobile] type that allocates bytes for the string +argument with @racket['atomic-interior]: + +@racketblock[ +(define _string/immobile + (make-ctype _pointer + (lambda (s) + (define bstr (cast s _string _bytes)) + (define len (bytes-length bstr)) + (define p (malloc 'atomic-interior len)) + (memcpy p bstr len) + p) + (lambda (p) + (cast p _pointer _string)))) + +(define-curses waddstr + (_fun #:in-original-place? #t _WINDOW-pointer _string/immobile -> _int)) +] + +Beware that passing memory allocated with @racket['interior] (as +opposed to @racket['interior-atomic]) is safe only for functions that +read the memory without writing. A foreign function must not write to +@racket['interior]-allocated memory from a place other than the one +where the memory is allocated, due a place-specific treatment of +writes to implement generational garbage collection. + @; ------------------------------------------------------------ @section{More Examples} diff --git a/pkgs/racket-pkgs/racket-doc/scribblings/foreign/types.scrbl b/pkgs/racket-pkgs/racket-doc/scribblings/foreign/types.scrbl index 1f8ecd72c0..c0d4190cd4 100644 --- a/pkgs/racket-pkgs/racket-doc/scribblings/foreign/types.scrbl +++ b/pkgs/racket-pkgs/racket-doc/scribblings/foreign/types.scrbl @@ -530,8 +530,7 @@ For @tech{callouts} to foreign functions with the generated type: @item{If @racket[in-original-place?] is true, then when a foreign @tech{callout} procedure with the generated type is called in - any Racket @tech[#:doc '(lib - "scribblings/reference/reference.scrbl")]{place}, the procedure + any Racket @tech-place[], the procedure is called from the original Racket place. Use this mode for a foreign function that is not thread-safe at the C level, which means that it is not place-safe at the Racket diff --git a/pkgs/racket-pkgs/racket-doc/scribblings/foreign/utils.rkt b/pkgs/racket-pkgs/racket-doc/scribblings/foreign/utils.rkt index 858f8985cc..ea51ae4c31 100644 --- a/pkgs/racket-pkgs/racket-doc/scribblings/foreign/utils.rkt +++ b/pkgs/racket-pkgs/racket-doc/scribblings/foreign/utils.rkt @@ -18,6 +18,7 @@ guide.scrbl reference.scrbl ->> + tech-place (all-from-out scribble/manual) (for-label (all-from-out racket/base racket/contract @@ -40,3 +41,6 @@ (make-element-id-transformer (lambda (stx) #'(racketlink ->> #:style "plainlink" (racketkeywordfont "->"))))) + +(define (tech-place) + (tech "place" #:doc '(lib "scribblings/reference/reference.scrbl")))