Some questions about concurrency (mostly)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

Some questions about concurrency (mostly)

Fredrik Appelberg

Hi all,

I learned Scheme back in my university days, but that was mostly
computer sciency, SICP stuff. Now I'm trying to get actual, practical
work done, and have to read up on threads, I/O and all that jazz. So I
have some newbie questions:

1. Are there any problems with creating a condition (as in exception,
   not srfi-18 condition variable) in one thread and raising it in one
   or more other threads?

2. In my application I'm running a thread that reads from a TCP port and
   dispatches frames back to the main thread using a mailbox (from the
   mailbox egg). I'm also running a separate thread that sends a
   heartbeat frame back over the TCP connecion every 30s. This works
   fine, but I've noticed that after a while (this seems to happen
   consistently after the first heartbeat has been sent) I cannot press
   Ctrl-C in the terminal to stop the application. However, as soon as
   the dispatcher thread reads an incoming frame, the process is
   interrupted. I assume this has something to do with blocking threads?
   Is there something I can do about it?

3. I'm new to dynamic-wind. If I wanted to create a general form for
   executing a thunk protected by a mutex, would this be a good idea?

     (define (with-lock mutex thunk)
       (dynamic-wind
             (lambda () (mutex-lock! mutex))
            thunk
                 (lambda () (mutex-unlock! mutex)))))

   I read somewhere that the before- and after-guards might execute
   multiple times, but then again I'm not really sure under what
   circumstances so I might be way off.

4. The srfi-18 thread scheduler seems to behave slightly differently
   when running compiled code and in csi. Is that correct?

Cheers,
-- Fredrik


Reply | Threaded
Open this post in threaded view
|

Re: Some questions about concurrency (mostly)

felix.winkelmann
> 1. Are there any problems with creating a condition (as in exception,
>    not srfi-18 condition variable) in one thread and raising it in one
>    or more other threads?

No problem here, I'd say, data can be freely exchanged between threads,
since they share a global address space. A condition object is just a
data structure.

> 2. In my application I'm running a thread that reads from a TCP port and
>    dispatches frames back to the main thread using a mailbox (from the
>    mailbox egg). I'm also running a separate thread that sends a
>    heartbeat frame back over the TCP connecion every 30s. This works
>    fine, but I've noticed that after a while (this seems to happen
>    consistently after the first heartbeat has been sent) I cannot press
>    Ctrl-C in the terminal to stop the application. However, as soon as
>    the dispatcher thread reads an incoming frame, the process is
>    interrupted. I assume this has something to do with blocking threads?
>    Is there something I can do about it?

Is this in a compiled application or inside csi? If the former, do you install
a handler for SIGINT?

> 3. I'm new to dynamic-wind. If I wanted to create a general form for
>    executing a thunk protected by a mutex, would this be a good idea?
>
>      (define (with-lock mutex thunk)
>        (dynamic-wind
>     (lambda () (mutex-lock! mutex))
>   thunk
> (lambda () (mutex-unlock! mutex)))))
>
>    I read somewhere that the before- and after-guards might execute
>    multiple times, but then again I'm not really sure under what
>    circumstances so I might be way off.

Whenever the continuation is re-invoked, the before/after thunks
will be called on entering the dynamic context in which the 2nd
thunk above will be executed, and the before/after thunks will
always be called in pairs, so your approach looks fine to me.

For example, load this into csi:

(define k0 #f)
(define k1 #f)

(define (foo)
  (call/cc
    (lambda (k)
      (dynamic-wind
        (cut print 'before)
        (lambda ()
          (set! k0 k)
          (call/cc
            (lambda (k2)
              (set! k1 k2)
              #f))
          (k 99))
        (cut print 'after)))))

#;1> (foo)
before
after
99
#;2> (k0 100)
100
#;3> (k1 101)
before
after
99

dynamic-wind is powerful, but can be very confusing, expecially
if you mix in threads and exceptions. Better try to keep the
control-flow of your code simple.

>
> 4. The srfi-18 thread scheduler seems to behave slightly differently
>    when running compiled code and in csi. Is that correct?

This depends on what you mean with "slightly". Interruption
of running threads (because the time-slice is used up) will happen
at different points in interpreted vs. compiled code - compiled code
is more compact and has of course completely different timing
behaviour. Can you elaborate on what you mean with "differently"?


felix


Reply | Threaded
Open this post in threaded view
|

Re: Some questions about concurrency (mostly)

Fredrik Appelberg
[hidden email] writes:

Hi, and thanks for the reply :)

>> 1. Are there any problems with creating a condition (as in exception,
>>    not srfi-18 condition variable) in one thread and raising it in one
>>    or more other threads?
>
> No problem here, I'd say, data can be freely exchanged between threads,
> since they share a global address space. A condition object is just a
> data structure.

Cool. I was thinking there might be some thread-dependent stack
information in there.

>> 2. In my application I'm running a thread that reads from a TCP port and
>>    dispatches frames back to the main thread using a mailbox (from the
>>    mailbox egg). I'm also running a separate thread that sends a
>>    heartbeat frame back over the TCP connecion every 30s. This works
>>    fine, but I've noticed that after a while (this seems to happen
>>    consistently after the first heartbeat has been sent) I cannot press
>>    Ctrl-C in the terminal to stop the application. However, as soon as
>>    the dispatcher thread reads an incoming frame, the process is
>>    interrupted. I assume this has something to do with blocking threads?
>>    Is there something I can do about it?
>
> Is this in a compiled application or inside csi? If the former, do you install
> a handler for SIGINT?

This is when running under csi. I hadn't thought about testing the
compiled version, but when I do it works as expected (Ctr-C interrupts
the application).

I haven't tried installing a signal handler since it kinda works for a
short time and I was convinced I was doing something wrong with the
threads. After testing some more I'm starting to think this maybe has
something to do with csi buffering input. I'll have to do some
experimentation here.

>> 3. I'm new to dynamic-wind. If I wanted to create a general form for
>>    executing a thunk protected by a mutex, would this be a good idea?
>>
>>      (define (with-lock mutex thunk)
>>        (dynamic-wind
>>     (lambda () (mutex-lock! mutex))
>>   thunk
>> (lambda () (mutex-unlock! mutex)))))
>>
>>    I read somewhere that the before- and after-guards might execute
>>    multiple times, but then again I'm not really sure under what
>>    circumstances so I might be way off.
>
> Whenever the continuation is re-invoked, the before/after thunks
> will be called on entering the dynamic context in which the 2nd
> thunk above will be executed, and the before/after thunks will
> always be called in pairs, so your approach looks fine to me.
>
> For example, load this into csi:
>
> (define k0 #f)
> (define k1 #f)
>
> (define (foo)
>   (call/cc
>     (lambda (k)
>       (dynamic-wind
>         (cut print 'before)
>         (lambda ()
>           (set! k0 k)
>           (call/cc
>             (lambda (k2)
>               (set! k1 k2)
>               #f))
>           (k 99))
>         (cut print 'after)))))
>
> #;1> (foo)
> before
> after
> 99
> #;2> (k0 100)
> 100
> #;3> (k1 101)
> before
> after
> 99
>
> dynamic-wind is powerful, but can be very confusing, expecially
> if you mix in threads and exceptions. Better try to keep the
> control-flow of your code simple.
>

That part of my code contains a large number of pretty similar
functions, all which require a mutex lock to run. In order to make the
code cleaner I could either try dynamic-wind or create a macro.

I'm not doing any call/cc or non-local exit shenanigans, but the code
uses srfi-18 threads and does I/O over TCP. As I understand it, srfi-18
is implemented using continuations. Will that cause problems with my
with-lock function? I'm thinking that a thread that has aquired the lock
in the before-thunk and running the main thunk might hang waiting for
I/O; will that cause it to exit the dynamic environment, run the
after-thunk and release the lock?

>>
>> 4. The srfi-18 thread scheduler seems to behave slightly differently
>>    when running compiled code and in csi. Is that correct?
>
> This depends on what you mean with "slightly". Interruption
> of running threads (because the time-slice is used up) will happen
> at different points in interpreted vs. compiled code - compiled code
> is more compact and has of course completely different timing
> behaviour. Can you elaborate on what you mean with "differently"?

I noticed that while running in csi one thread would execute three read
operations on a TCP connection in a row without being interrupted, but
the same code running under csc would mix in a write operation from
another thread. Which was probably a good thing, since that made me
realize I had a race condition :) It's probably down to the different
timing behaviour you mention.

Thanks again for the input! (and for Chicken Scheme!)

Cheers,
-- Fredrik

Reply | Threaded
Open this post in threaded view
|

Re: Some questions about concurrency (mostly)

Jörg F. Wittenberger
In reply to this post by Fredrik Appelberg
Am Thu, 05 Nov 2020 23:22:09 +0100
schrieb Fredrik Appelberg <[hidden email]>:

> 3. I'm new to dynamic-wind. If I wanted to create a general form for
>    executing a thunk protected by a mutex, would this be a good idea?
>
>      (define (with-lock mutex thunk)
>        (dynamic-wind
>     (lambda () (mutex-lock! mutex))
>   thunk
> (lambda () (mutex-unlock! mutex)))))
>
>    I read somewhere that the before- and after-guards might execute
>    multiple times, but then again I'm not really sure under what
>    circumstances so I might be way off.

This approach is bound to fail badly.

It works just as long as there are a) no exceptions raised in `thunk`
b) no code, not even in a library does any `call/cc`.  Including
`call/cc` hidden in exception handlers (srfi-12, srfi-34 etc.)

NEVER DO THIS!

You need this:

   (define with-lock mutex thunk
      (handle-exceptions
        exn
        (begin
          find out whether you can safely unlock the mutex or
          clean up the damage done by partially updating the resource
          (mutex-unlock! mutex) ;; if appropriate
          either `(raise exn)` or return some value)
        (mutex-lock! mutex)
        modify resource
        (mutex-unlock! mutex)))


Reply | Threaded
Open this post in threaded view
|

Re: Some questions about concurrency (mostly)

felix.winkelmann
In reply to this post by Fredrik Appelberg
> I'm not doing any call/cc or non-local exit shenanigans, but the code
> uses srfi-18 threads and does I/O over TCP. As I understand it, srfi-18
> is implemented using continuations. Will that cause problems with my
> with-lock function? I'm thinking that a thread that has aquired the lock
> in the before-thunk and running the main thunk might hang waiting for
> I/O; will that cause it to exit the dynamic environment, run the
> after-thunk and release the lock?

Each thread has its own dynamic-wind chain, so unless you start
threads in the before/after threads, you should be fine.


felix


Reply | Threaded
Open this post in threaded view
|

Re: Some questions about concurrency (mostly)

Jörg F. Wittenberger
Am Fri, 06 Nov 2020 17:48:26 +0100
schrieb [hidden email]:

> > I'm not doing any call/cc or non-local exit shenanigans, but the
> > code uses srfi-18 threads and does I/O over TCP. As I understand
> > it, srfi-18 is implemented using continuations. Will that cause
> > problems with my with-lock function? I'm thinking that a thread
> > that has aquired the lock in the before-thunk and running the main
> > thunk might hang waiting for I/O; will that cause it to exit the
> > dynamic environment, run the after-thunk and release the lock?  
>
> Each thread has its own dynamic-wind chain, so unless you start
> threads in the before/after threads, you should be fine.

As I wrote before: beware of subtile interference between a) srfi-18
and exception handling regarding the `abandoned` state of the mutex or
even better b) srfi-154's idea of a `current-dynamic-extent`.

Both are great ways to create showcases how to confuse code, which calls
mutex-(un)lock! from within `dynamic-wind`-installed handlers.

I'd LOVE to be able to tag procedures to be "dynamic-wind-safe" to the
extent that calling them from such a context raises an exception!

Having though more in the past hours about this - actually frequently
recurring issue, which I, admittedly got personally wrong for several
years - I'd even take back my recommendation to EVER call
`mutex-unlock!` from unwind-handlers.  Just figure out in the handler
whether or not to handle the exception and defer `mutex-unlock!` until
after the exception handler completed (or don't call it if the handler
re-raises the exception).

This topic is labeled: "dragons ahead" for me.

Just consider `mutex-`* operations NOT safe to be called from
`dynamic-wind` "before" and "after" thunks.

Reply | Threaded
Open this post in threaded view
|

Re: Some questions about concurrency (mostly)

Chris Vine-2
In reply to this post by Jörg F. Wittenberger
On Fri, 6 Nov 2020 17:20:04 +0100
"Jörg F. Wittenberger" <[hidden email]> wrote:

> Am Thu, 05 Nov 2020 23:22:09 +0100
> schrieb Fredrik Appelberg <[hidden email]>:
>
> > 3. I'm new to dynamic-wind. If I wanted to create a general form for
> >    executing a thunk protected by a mutex, would this be a good idea?
> >
> >      (define (with-lock mutex thunk)
> >        (dynamic-wind
> >     (lambda () (mutex-lock! mutex))
> >   thunk
> > (lambda () (mutex-unlock! mutex)))))
> >
> >    I read somewhere that the before- and after-guards might execute
> >    multiple times, but then again I'm not really sure under what
> >    circumstances so I might be way off.
>
> This approach is bound to fail badly.
>
> It works just as long as there are a) no exceptions raised in `thunk`
> b) no code, not even in a library does any `call/cc`.  Including
> `call/cc` hidden in exception handlers (srfi-12, srfi-34 etc.)

For my elucidation, why?  The indentation of the code isn't ideal but
the whole purpose of dynamic-wind is to handle code leaving the thunk in
case of exception, application of a continuation object or regular
return.

Admittedly it would be more usual for this to be implemented as a macro
rather than a function with a thunk, but that is a minor matter.

Reply | Threaded
Open this post in threaded view
|

Re: Some questions about concurrency (mostly)

John Cowan


On Sat, Nov 7, 2020 at 5:51 PM Chris Vine <[hidden email]> wrote:
 
For my elucidation, why?  The indentation of the code isn't ideal but
the whole purpose of dynamic-wind is to handle code leaving the thunk in
case of exception, application of a continuation object or regular
return.

You're right about the second and third cases, but not the first.  Raising an exception *simply* calls the current exception handler, which is then free to return to the point of call like any other procedure.  In such a case, the after-thunk is not run, since dynamically you are still inside the main thunk.  This is a fundamental distinction between Lisp exceptions and every other language's exceptions.

Only if the exception handler uses call/cc to escape back to the dynamic context where the handler was set up is the after-thunk run.  This is done for you automatically if you use `handle-exceptions` or `chicken-case` or R7RS `guard`.

If you raise an exception with `abort` or `error`, then if the handler returns, another exception "unexpected return from handler" is raised.

Note also that when the thread scheduler switches to a new thread, any before- or after-thunks are not run, as the thread is supposed to be unaware that the switch happened.



John Cowan          http://vrici.lojban.org/~cowan        [hidden email]
Would your name perchance be surname Puppet, given name Sock?
                --Rick Moen

Reply | Threaded
Open this post in threaded view
|

Re: Some questions about concurrency (mostly)

Chris Vine-2
On Sat, 7 Nov 2020 20:02:12 -0500
John Cowan <[hidden email]> wrote:

> On Sat, Nov 7, 2020 at 5:51 PM Chris Vine <[hidden email]> wrote:
> >On Fri, 6 Nov 2020 17:20:04 +0100
> >"Jörg F. Wittenberger" <[hidden email]> wrote:
> > > This approach is bound to fail badly.
> > >
> > > It works just as long as there are a) no exceptions raised in `thunk`
> > > b) no code, not even in a library does any `call/cc`.  Including
> > > `call/cc` hidden in exception handlers (srfi-12, srfi-34 etc.)
> >
> > For my elucidation, why?  The indentation of the code isn't ideal but
> > the whole purpose of dynamic-wind is to handle code leaving the thunk in
> > case of exception, application of a continuation object or regular
> > return.
>
> You're right about the second and third cases, but not the first.  Raising
> an exception *simply* calls the current exception handler, which is then
> free to return to the point of call like any other procedure.  In such a
> case, the after-thunk is not run, since dynamically you are still inside
> the main thunk.  This is a fundamental distinction between Lisp exceptions
> and every other language's exceptions.
>
> Only if the exception handler uses call/cc to escape back to the dynamic
> context where the handler was set up is the after-thunk run.  This is done
> for you automatically if you use `handle-exceptions` or `chicken-case` or
> R7RS `guard`.
>
> If you raise an exception with `abort` or `error`, then if the handler
> returns, another exception "unexpected return from handler" is raised.
>
> Note also that when the thread scheduler switches to a new thread, any
> before- or after-thunks are not run, as the thread is supposed to be
> unaware that the switch happened.

Sure, thread context switches would be unusable if they triggered
dynamic winds.  A good job they don't.

Although there is little in it, I think I was right about the first
case.  If an exception does not result in the dynamic environment
of the raise being left, then this could only occur in the case of a
duly handled continuable exception, and in that case the exception
would not result, using my words, in "leaving the thunk"[1].  Instead,
all that would have happened is that the exception handler would have
executed in the thunk as if on a function application.

As it happens, that case is unusual: where for example the R7RS guard
and similar macros are involved the dynamic environment of the thunk
would be left in order to examine the relevant cond clauses, so the out
handler would be invoked, and for R7RS guard if no cond clause matches
it would invoke the in handler in order to re-raise the exception,
followed by the invocation of the out handler again after the re-raise
(unless the exception is continuable and is handled elsewhere on the
next attempt).  For such a case the proposed with-lock function would
be highly useful: it would guarantee the correct result whatever the
outcome.

I wonder therefore why you think that this renders the proposed
with-lock function inappropriate, if that is what you were implying?
Perhaps you weren't and were making some other point.  Macros for
handling resources such as mutex invoking dynamic-wind are commonplace.

[1] I am somewhat perplexed with your post because you seem to be
agreeing with me when disagreeing with me: you say that in the case
mentioned "dynamically you are still inside the main thunk".

Reply | Threaded
Open this post in threaded view
|

Re: Some questions about concurrency (mostly)

Jörg F. Wittenberger
Am Sun, 8 Nov 2020 02:17:58 +0000
schrieb Chris Vine <[hidden email]>:

> I wonder therefore why you think that this renders the proposed
> with-lock function inappropriate, if that is what you were implying?
> Perhaps you weren't and were making some other point.  Macros for
> handling resources such as mutex invoking dynamic-wind are
> commonplace.

Sorry, short on time.  Two experiments for you kind consideration in
the appended `test.scm`

Best
/Jörg

test.scm (1K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Some questions about concurrency (mostly)

Fredrik Appelberg

Thanks everyone, this thread has been enlightening :) I understand
dynamic-wind better now, but for my purposes a handle-exceptions macro
is probably sufficient.

Cheers,
-- Fredrik