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 20:08 UTC

> On Nov 7, 2022, at 1:43 PM, Marc Nieper-Wißkirchen <xxxxxx@nieper-wisskirchen.de> wrote:
>
> Thank you!
>
> Am Mo., 7. Nov. 2022 um 19:19 Uhr schrieb Marc Feeley <xxxxxx@iro.umontreal.ca>:
>>
>>
>>> 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>).
>
> So, at least in the case of ##thread-call, the continuation of the
> call to thunk is not precisely the continuation that will evaluate
> <expr> next but a continuation that sends the return values of <thunk>
> to the calling thread before continuing with <expr>, right?

For ##thread-call yes, but for ##thread-int! it could be the exact continuation that evaluates <expr>.  For both, the thread’s current continuation (at the moment of the interrupt) will be a parent.

>
> Does Gambit handle signals through thread-int!, e.g. as in the following?
>
> (##thread-int! <thread> (lambda () (raise-continuable <signal-condition>)))
>
> or
>
> (##thread-int! <thread> (lambda () (signal-interrupt <info>)))
>
> where
>
> (define (signal-interrupt info)
>  ((current-signal-interrupt-handler) info))

It depends what you mean by “a signal”…  If you mean something related to POSIX signals then no it is not implemented that way because POSIX signals are executed asynchronously (with respect to the Gambit virtual machine) so they could happen in the middle of some VM operation that should not be interrupted because the VM state is temporarily inconsistent.  So instead a POSIX signal will register the signal in a bit set, and then raise an “interrupt flag” that is checked regularly at “safe points” and the handler checks the bit set.  This is a very low overhead polling mechanism that piggybacks on the stack overflow detection logic.  See my paper:

   Polling Efficiently on Stock Hardware, FPCA93 (http://www.iro.umontreal.ca/~feeley/papers/FeeleyFPCA93.pdf)

Fun fact: that paper has achieved a certain notoriety because it is one of the few references in the book “The Java Language Specification” in the section 11.3.2 “Handling Asynchronous Exceptions”.

Marc

>
>>
>> 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))