Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fibers does not support wait-operation being interrupted, even when on a non-fibers thread #99

Open
abcdw opened this issue Oct 6, 2023 · 6 comments

Comments

@abcdw
Copy link

abcdw commented Oct 6, 2023

I was experimenting with system-async-mark and found the problem with condition variables. I guess it happens because when async does non-local exit, the thread can not be removed from condition variable waiters and signaling fiber is hanging indefinitely.

The following code hangs on signal-condition! invocation:

(begin
  (format #t "\n\n====================\n")
  (use-modules (fibers) (fibers conditions) (ice-9 threads)
               (fibers channels))
  (define cnd (make-condition))
  (define ch (make-channel))
  (define mutex (make-mutex 'allow-external-unlock))
  (lock-mutex mutex)
  (define prompt (make-prompt-tag "tmp"))
  (define th (call-with-new-thread
              (lambda ()
                (call-with-prompt prompt
                  (lambda ()
                    (format #t "-> inside prompt\n")
                    ;; (lock-mutex mutex)
                    (wait cnd)
                    (format #t "-> condition unlcoked\n"))
                  (lambda (k . args)
                    (apply values args)))
                (format #t "<- after prompt\n")
                ;; (wait cnd)
                ;; (lock-mutex mutex)
                (format #t "<- finishing\n"))))
  (sleep 1)
  (format #t "substituting computation\n")
  (system-async-mark
   (lambda ()
     (put-message ch 'hello)
     (abort-to-prompt prompt)) th)

  (run-fibers (lambda ()
                (format #t "message: ~a\n" (get-message ch))
                ;; (unlock-mutex mutex)
                (signal-condition! cnd)
                (format #t "!condition signalled\n"))
              #:drain? #f)
  (sleep 1)
  (format #t "after lock\n")
  (format #t "--------------------\n")
  (sleep 3) "")

The output is following:

====================
-> inside prompt
substituting computation
message: hello
<- after prompt
<- finishing
@emixa-d
Copy link
Collaborator

emixa-d commented Dec 18, 2023

That code is broken in the first place -- the async from system-async-mark can be run inside another fiber (or even not in a fiber at all, e.g. Fiber's scheduling code), in particular it could be somewhere outside the call-with-prompt.

So, what's this code supposed to implement in the first place?

@abcdw
Copy link
Author

abcdw commented Dec 25, 2023

The lambda from system-async-mark will be run in a separate thread, not in a fiber.

The original goal is to implement interruptable evaluation and here is a working implementation:
https://git.sr.ht/~abcdw/guile-ares-rs/tree/ee807444833a19bf3d7d281ab1afa5225147b72c/item/src/nrepl/server/evaluation.scm#L158

@emixa-d
Copy link
Collaborator

emixa-d commented Dec 25, 2023

(system-async-mark
(lambda ()
(put-message ch 'hello)
(abort-to-prompt prompt)) th)

I did not notice the th here ...

That seems much more reasonable. I am, however, quoting what I wrote in #29 (comment) (not all of it applies here, but enough does):

Also, performing Fibers operations inside an async is super skeevy. The Fibers stuff might be in an intermediate state, which usually is invisible to Fibers users but potentially not from asyncs, so now it might be messing things up. Or the async might be run inside a fiber that currently is in the progress of doing a put-message of its own, which put-message was not designed to handle, so that might mess things up. Or perhaps it is even run inside the process monitor, while it is doing (get-message (current-process-monitor)) -- I don't know what would happen then, but I doubt it is anything good.

Really, unless it's a pure operation or something ultra-basic like vector-set! or something that's actually documented to be allowed/supported, you shouldn't be assuming you can simply do things from an async.

Please remove this process-monitor stuff or actually implement & document the prerequisites in Fibers first.

(This applies more generally, beyond Scheme and Fibers, to any APIs with concurrency.)

@emixa-d emixa-d changed the title signal-condition! breaks, when waiting thread interrupted by asyncs fibers does not support wait-operation being interrupted, even when on a non-fibers thread Dec 25, 2023
@emixa-d
Copy link
Collaborator

emixa-d commented Dec 25, 2023

(Also, pipe trick would be convenient here.)

@abcdw
Copy link
Author

abcdw commented Dec 27, 2023

@emixa-d What do you mean by pipe trick?

@emixa-d
Copy link
Collaborator

emixa-d commented Dec 29, 2023

See, e.g., https://web.stanford.edu/class/cs110l/slides/lecture-11.pdf:

Avoiding signal handling
● Anything substantial should not be done in a signal handler
● How can we handle signals, then?
● The “self-pipe” trick was invented in the early 90s:
● Create a pipe
● When you’re awaiting a signal, read from the pipe (this will block until
something is written to it)
● In the signal handler, write a single byte to the pipe

Pipes have a positive, but finite, buffer size, so make sure not to write too much to the pipe.
For waiting until the pipe is readable, there is wait-until-port-readable-operation, but beware of spurious wakeups, so when reading from the pipe you may need to parameterize current-read-waiter appropriately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants