mit-scheme-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [MIT-Scheme-devel] multi-threading problem: Unassigned variable: roo


From: Matt Birkholz
Subject: Re: [MIT-Scheme-devel] multi-threading problem: Unassigned variable: root-continuation-default
Date: Fri, 27 Apr 2012 18:40:16 -0700

> From: "Micah Brodsky" <address@hidden>
> Date: Thu, 26 Apr 2012 22:36:21 -0400
> 
> [...] entirely irrelevant to MIT Scheme, except inasmuch as the same
> problems reappear and are much more of a pain to deal with without
> them. I.e., as far as I can tell, everyone who wants to abort a
> blocking call like a socket read has to roll their own abstraction
> to handle safe cross-thread signaling.

Hey, we are already in the weeds.  Let's not joke about rolling our
own... :-)

... and let's get concrete with the existing thread-event mechanism.
To throw an error/abort in a thread blocked in channel-read
(e.g. read-line), you should be able to do this:

    (signal-thread-event thread (lambda () (error "Abort!")))

Thread would be the blocked reader, perhaps

    (thread-mutex-owner (port/thread-mutex socket))

Here's our example again, spiffed up with some messages, minus the
curious nested create-thread.

    (let ((socket (open-tcp-server-socket 54321)))
      (create-thread
       #f (lambda ()
            (let ((port (tcp-server-connection-accept socket #t #f)))
              (display ";Connection accepted.\n")
              (for-each display
                        (list ";Read: "
                              (ignore-errors (lambda () (read-line port)))
                              "\n"))
              (display "ok\n" port)
              (close-port port)
              (display ";Connection disconnected.\n")))))

Entering that into the console REPL, I get the REPL prompt back and I
can connect via `nc localhost 54321'.  When I connect ";Connection
accepted." appears on my console:

    ;Value 11: #[thread 11]

    1 ]=> ;Connection accepted.

I then enter at the REPL the charm already mentioned:

    (signal-thread-event address@hidden (lambda () (error "Abort!")))

and I see this:

    ;Unspecified return value

    1 ]=> ;Read: #[condition 12 simple-error]
    ;Connection disconnected.
    (pp address@hidden)
    #[thread 11]
    (execution-state dead)
    ...

My nc command emits "ok" and exits without error.  If I had kept
#[thread 11] out of the REPL's hash table, it would be garbage
collected too.

When I punt the ignore-errors wrapper I see this:

    ;Unspecified return value

    1 ]=> 
    ;The thread #[thread 11] signalled an error:
    Abort!
    ;To continue, call RESTART with an option number:
    ; (RESTART 1) => Return to read-eval-print level 1.

    2 error> 

The situation is quite sane.  Here is the output of the debugger's
history command:

    SL#  Procedure-name          Expression

    0                            (begin (event) (set-interrupt-enables! int ...
    1                            (begin (if event (let ((block? (%record-re ...
    2                            (let ((any-events? (handle-thread-events t ...
    3                            (begin (%suspend-current-thread) result)
    4                            (let ((value (let ((result (quote interrup ...
    5                            (let ((result (TEST-FOR-IO-ON-CHANNEL chan ...
    6                            (let ((value (thunk))) (set-interrupt-enab ...
    7                            (let ((n (do-read 0))) (if n (begin (%reco ...
    8    fill-input-buffer       (let ((n (read-bytes ib))) (if n (if (grea ...
    9                            (let ((r (fill-input-buffer ib))) (let ((t ...
    10                           (let ((char (defer port))) (transcribe-inp ...
    11   loop                    (let ((char (read-char port))) (cond ((eq? ...
    12                           (list ";Read: " (read-line port) "\n")
    13                           (for-each display (list ";Read: " (read-li ...
    14                           (begin (for-each display (list ";Read: " ( ...
    15                           (exit-current-thread (thunk))
    16   loop                    (loop (bind-abort-restart cmdl (lambda ()  ...

    3 debug> 

Actually I upcased test-for-io-on-channel because I think it is cool
that I can see where my #[thread 11] was hanging out when it got the
bad news.  That's Bodacious, dude.

> [...] Imagine if you couldn't safely do debug prints from a worker thread!

I can only imagine doing it safely if the debug-print procedure grabs
the port/mutex for the duration of the message output (write-line?)
process.  A write-line made up of tiny atomic write-chars is no more
"safe" except for the precious buffer pointers.  I would call them a
costly set of suspenders IF they kept the pants from falling down.
Thread-safe and child-proof are marketing terms.

> I have not looked at the spec or code in the case of glibc, but I
> know for certain MS's stdio and iostream protect their buffers with
> locks.

Marketing term.  Definitely.  Tiny atomic ops won't keep your pants
up!

> MIT Scheme has some particularly bad failure modes when you rub its
> sockets the wrong way from multiple threads, i.e. producing an
> infinite explosion of interrupt-triggered errors that surfaces on
> socket close, well after the offending code has run, and almost
> un-debuggable because all you can interact through is the interrupt
> menu. The only way I figured that one out is by asking Taylor to
> tell me what I did wrong. :P

Fascinating (as my hero would say, without a trace of sarcasm).

What did you do wrong?



reply via email to

[Prev in Thread] Current Thread [Next in Thread]