macOS: Racket threads do not run while dragging a slider%
Re. https://github.com/Bogdanp/racket-gui-easy/issues/63#issuecomment-3225228993.
As the title says, when dragging a slider, threads other than the eventspace thread are suspended. So, the threads in this example end up not running while dragging the slider (or while initiating a drag and holding the slider):
#lang racket/gui
(define f
(new frame%
[label "Test"]))
(define thd
(thread
(lambda ()
(let loop ()
(println `(value ,(thread-receive)))
(loop)))))
(define tick-thd
(thread
(lambda ()
(let loop ()
(println 'tick)
(sleep 1)
(loop)))))
(define timer
(new timer%
[notify-callback
(lambda ()
(println 'timer-tick))]
[interval 1000]))
(define s
(new slider%
[parent f]
[label "Test"]
[init-value 0]
[min-value 0]
[max-value 100]
[callback (lambda (self _event)
(thread-send thd (send self get-value)))]))
(send f show #t)
Surprisingly, the timer% doesn't run either.
As a hack, I've tried setting up a display link to post dummy events between drag start and end (the display link is necessary because changed: does not fire if you just hold the slider after dragging), but that doesn't seem to help. Probably the right solution is a run loop source in queue.rkt for the event tracking mode, but I'm not sure how to do that.
I don't have a general solution here, but there's more potential to solve this problem in Racket CS than there was with Racket BC.
The problem with BC was that thread switching copies the C stack in and out. A run loop's state is not ok being moved off the stack and back like that, and so swapping threads is never safe.
CS can switch Racket threads without directly changing the C stack, and so it's possible to swap threads during a run-loop-triggered callback. If a swapped-in thread makes its own foreign calls, they just add to the same C stack — and that works out as long as the thread's foreign calls are all completed before returning to the run loop. Of course, it doesn't work in general to have callbacks from different threads return in a different order then they started, and so the rule in CS right now is that callbacks are always in atomic mode. That's why a thread swap will never happen during a callback, currently.
It might work (for CS) in a run-loop-triggered callback to explicitly exit atomic mode and yield to the thread scheduler with (sleep) as long as all other callbacks are still running atomically. One problem, then, is knowing how many times to sleep/yield versus returning to handle more events. The main run loop is woken up for I/O or timeouts (including timer% events) by blocking on the run loop a non-main thread; that's probably more manual management than a control-managed run loop is willing to allow.