Quick question on thread-interrupt! Marc Nieper-Wißkirchen (07 Nov 2022 09:41 UTC)
Re: Quick question on thread-interrupt! Marc Feeley (07 Nov 2022 18:19 UTC)
Re: Quick question on thread-interrupt! Marc Nieper-Wißkirchen (07 Nov 2022 18:43 UTC)
Re: Quick question on thread-interrupt! Marc Feeley (07 Nov 2022 20:08 UTC)
Re: Quick question on thread-interrupt! Marc Feeley (07 Nov 2022 20:28 UTC)
Re: Quick question on thread-interrupt! Marc Nieper-Wißkirchen (08 Nov 2022 07:27 UTC)
Re: Quick question on thread-interrupt! Marc Feeley (08 Nov 2022 15:00 UTC)
Re: Quick question on thread-interrupt! Marc Nieper-Wißkirchen (08 Nov 2022 15:58 UTC)
Re: Quick question on thread-interrupt! Marc Nieper-Wißkirchen (09 Nov 2022 12:15 UTC)
Re: Quick question on thread-interrupt! Marc Nieper-Wißkirchen (11 Nov 2022 11:24 UTC)
Re: Quick question on thread-interrupt! Marc Feeley (07 Nov 2022 20:36 UTC)

Re: Quick question on thread-interrupt! Marc Feeley 07 Nov 2022 18:19 UTC

> On Nov 7, 2022, at 4:41 AM, Marc Nieper-Wißkirchen <xxxxxx@nieper-wisskirchen.de> wrote:
>
> Hey Marc,
>
> could you describe the exact semantics of Gambit's thread-interrupt!
> or give me a link to where it is documented?
>
> Specifically, what happens when the thunk returns normally?
>
> We talked about capturing a continuation inside the thunk, which is
> related to the question.
>
> Thanks,
>
> Marc
>

The API and semantics of thread-interrupt! has evolved over time since the introduction of threads in Gambit v4.0 (~2000).  The basic idea is to force a runnable or blocked thread to immediately execute a call to a thunk, regardless of what that thread is currently doing.  Conceptually each thread executes a series of atomic actions (“atomic” in the sense that they are operations at a certain level of abstraction of the virtual machine, such as a call to “cons”, “car”, “pair?”, etc).  But note that some Scheme predefined procedures, such as “append”, “map”, etc are not atomic and are a series of atomic actions.  The thread interrupt mechanism inserts the call to the thunk between such atomic actions thus ensuring that interrupts happen at “safe places” (for the Scheme virtual machine, which does not mean that it is safe for the logic of the program, which is the programmer’s concern).  Conceptually, if the thread was about to evaluate <expr>, it replaces this by the evaluation of (begin (thunk) <expr>) so that the thunk’s result is ignored and the thunk is called with the thread’s current continuation as a parent, including the dynamic environment.

There are two flavors of interrupts.  They are accessible with these internal primitives of the runtime library:

   (##thread-int! <thread> <thunk>)
   (##thread-call <thread> <thunk>)

They both execute a call to <thunk> in the thread <thread>, but ##thread-int! is asynchronous (the calling thread does not wait for <thread> to be done executing the call to <thunk>) and ##thread-call is synchronous (it waits until <thread> is done executing the call to <thunk> and it returns the value returned by <thunk>).

Note that ##thread-call can be written on top of ##thread-int! (using a mutex to wait for the result).  So ##thread-int! is the fundamental primitive.

As an example, the code attached below will print this:

(started #<thread #2> a)
(started #<thread #3> b)
(called ##thread-int! #<thread #2>)
(called ##thread-int! #<thread #3>)
(ending ##thread-int! #<thread #3> b)
(ending ##thread-int! #<thread #2> a)
(result of ##thread-call #<thread #2> a)
(result of ##thread-call #<thread #3> b)
(done)

The code creates two threads “a” and “b” and interrupts them asynchronously and synchronously.  Each thread dynamically binds the parameter object “p” to the name of the thread to demonstrate how interrupts interact with the dynamic environment.

The ##thread-call procedure is a convenience.  I’m still unsure if a timeout parameter should be added because for those cases where a timeout is required it is probably more expressive to rewrite the code with a ##thread-int! and an explicit synchronization logic (that goes beyond a simple timeout).

Marc

(define p (make-parameter 'init))

(define (start name mut)
 (thread-start!
  (make-thread
   (lambda ()
     (parameterize ((p name))
       (pp (list 'started (current-thread) name))
       (mutex-unlock! mut)
       (let loop ((i 50))
         (if (> i 0)
             (begin
               (thread-sleep! 0.1)
               (loop (- i 1))))))))))

(define (make-locked-mutex)
 (let ((mut (make-mutex)))
   (mutex-lock! mut)
   mut))

(define a-started-mut (make-locked-mutex))
(define b-started-mut (make-locked-mutex))

(define a (start 'a a-started-mut))
(define b (start 'b b-started-mut))

(mutex-lock! a-started-mut) ;; wait for a to start
(mutex-lock! b-started-mut) ;; wait for b to start

(##thread-int! a (lambda ()
                  (thread-sleep! 1)
                  (pp (list 'ending '##thread-int! (current-thread) (p)))))

(pp (list 'called '##thread-int! a))

(##thread-int! b (lambda ()
                  (thread-sleep! 0.5)
                  (pp (list 'ending '##thread-int! (current-thread) (p)))))

(pp (list 'called '##thread-int! b))

(pp (##thread-call a (lambda ()
                      (thread-sleep! 0.5)
                      (list 'result 'of '##thread-call (current-thread) (p)))))

(pp (##thread-call b (lambda ()
                      (thread-sleep! 0.5)
                      (list 'result 'of '##thread-call (current-thread) (p)))))

(pp (list 'done))