# Help test via snapshots: parallel threads

**URL:** <https://racket.discourse.group/t/help-test-via-snapshots-parallel-threads/3920>\
**Category:** General\
**Created:** [August 25, 2025, 1:03pm UTC](https://racket.discourse.group/t/help-test-via-snapshots-parallel-threads/3920 "2025-08-25T13:03:48Z")\
**Posts on this page:** 5\
**Page:** 2

<div class="post-metadata">

**Author:** ![jjsimpso](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/jjsimpso/32/602_2.png) [@jjsimpso](https://racket.discourse.group/u/jjsimpso)\
**Post date:** [September 23, 2025, 9:55pm UTC](https://racket.discourse.group/t/help-test-via-snapshots-parallel-threads/3920/21 "2025-09-23T21:55:08Z")

</div>

> [@mflatt](#):
>
> PLT\_FUTURE\_TRACE\_DEPTH

Oh, that is VERY helpful. Thanks for the explanation! It seems like parallel threads have a number of advantages and the code to use futures vs. parallel threads is almost identical.

---

<div class="post-metadata">

**Author:** ![LiberalArtist](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/liberalartist/32/151_2.png) [@LiberalArtist](https://racket.discourse.group/u/LiberalArtist)\
**Post date:** [October 13, 2025, 12:09am UTC](https://racket.discourse.group/t/help-test-via-snapshots-parallel-threads/3920/22 "2025-10-13T00:09:44Z")

</div>

The `#:keep 'results` argument makes `thread` very similar to `delay/thread` (and, by extension, `delay/sync` or `delay/idle`). The big difference I see is that the promises catch exceptions and raise them when forces, whereas `thread` uses the exception handler, and a thread that ended in an exception will have `(void)` as its result, like a `#:keep #f` thread:

```scheme
> (list (thread-wait (thread #:keep 'results (lambda () (+ "oops")))))
+: contract violation
  expected: number?
  given: "oops"
 [,bt for context]
'(#<void>)

```

Maybe this makes sense: catching exceptions might be expensive, and maybe it's useful to have this as a lower-level mechanism. On the other hand, I'd guess catching exceptions is a relatively common desideratum, and maybe the implementation of `thread` is in a position to implement it especially efficiently, or just conveniently. Potentially a `#:keep result/exn` option could be added later.

It doesn't seem like there's a way to tell currently (without controlling the thread's thunk) if a thread terminated normally, with an exception, or by being killed.

Eventually, it would be nice to support parallelism for `delay/thread` and friends.

Finally, the existence of `delay/idle` led me to notice that the docs for `system-idle-evt` probably need an update for parallel threads:

> Returns an event that is [ready for synchronization](https://users.cs.utah.edu/plt/snapshots/current/doc/reference/sync.html#%28tech._ready._for._synchronization%29) when the system is otherwise idle: if the result event were replaced by [`never-evt`](https://users.cs.utah.edu/plt/snapshots/current/doc/reference/sync.html#%28def._%28%28quote._~23~25kernel%29._never-evt%29%29), no thread in the system would be available to run. In other words, all threads must be suspended or blocked on events with timeouts that have not yet expired.

From this experiment, it looks like parallel threads must also be blocked, but I'm not sure that behavior is desirable:

```scheme
> (define stop? #f)
> (define n 0)
> (thread #:pool 'own (lambda ()                                 
                        (let loop ()                           
                          (if stop?                           
                              (displayln n)      
                              (begin (set! n (add1 n))
                                     (loop))))))
#<thread>
> (list n (sync (system-idle-evt)) n (set! stop? #t) n)
^Cuser break [,bt for context]

```

On the other hand, syncing on `(system-idle-evt)` in a parallel thread seems to work fine:

```scheme
> (sync (thread #:pool 'own
                #:keep 'results
                (lambda ()
                  (sync (system-idle-evt))
                  1)))
#<thread>

```

---

<div class="post-metadata">

**Author:** ![mflatt](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/mflatt/32/6_2.png) [@mflatt](https://racket.discourse.group/u/mflatt)\
**Post date:** [October 17, 2025, 9:48pm UTC](https://racket.discourse.group/t/help-test-via-snapshots-parallel-threads/3920/23 "2025-10-17T21:48:10Z")

</div>

> [@LiberalArtist](#):
>
> Potentially a `#:keep 'result/exn` option could be added later.

Yes, I agree. I intended to add that, but haven't gotten to it.

> [@LiberalArtist](#):
>
> It doesn't seem like there's a way to tell currently (without controlling the thread's thunk) if a thread terminated normally, with an exception, or by being killed.

The updated `thread-wait` does support this with an extra fail-thunk argument:

```scheme
> (list (thread-wait (thread #:keep 'results (lambda () (+ "oops"))) 
                     (lambda () 'failed)))
+: contract violation
  expected: number?
  given: "oops"
 [,bt for context]
'(failed)

```

> [@LiberalArtist](#):
>
> From this experiment, it looks like parallel threads must also be blocked, but I'm not sure that behavior is desirable:

The intent is definitely that `(system-idle-evt)` waits until no parallel thread can make progress, the same a coroutine threads. That's important for the only use that I know for `system-idle-evt`, which is testing.

---

<div class="post-metadata">

**Author:** ![LiberalArtist](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/liberalartist/32/151_2.png) [@LiberalArtist](https://racket.discourse.group/u/LiberalArtist)\
**Post date:** [October 18, 2025, 6:29pm UTC](https://racket.discourse.group/t/help-test-via-snapshots-parallel-threads/3920/24 "2025-10-18T18:29:23Z")

</div>

> [@mflatt](#):
>
> > [@LiberalArtist](#):
> >
> > It doesn't seem like there's a way to tell currently (without controlling the thread's thunk) if a thread terminated normally, with an exception, or by being killed.
> 
> The updated `thread-wait` does support this with an extra fail-thunk argument:

Thanks, I'd missed the change to `thread-wait`!

The docs made it clear how to distinguish normal termination from an exception, but I wasn't sure until trying the following experiment that the failure thunk is also called if the thread is killed. Is being killed an instance of “or otherwise aborted to the thread’s initial prompt”, or is it a different category? I'll try to propose a change to the docs.

```scheme
$ ./bin/racket 
Welcome to Racket v8.18.0.19-2025-10-12-72b9c65d3f [cs].
> (define sema (make-semaphore))
> (define thd   
    (thread
     #:keep 'results
     (lambda ()     
       (sync sema)
       'normal)))
> (kill-thread thd)
> (thread-wait thd (lambda () 'exn))
'exn

```

* * *

> [@mflatt](#):
>
> > [@LiberalArtist](#):
> >
> > From this experiment, it looks like parallel threads must also be blocked, but I'm not sure that behavior is desirable:
> 
> The intent is definitely that `(system-idle-evt)` waits until no parallel thread can make progress, the same a coroutine threads. That's important for the only use that I know for `system-idle-evt`, which is testing.

I have low confidence that my understanding and use of `system-idle-evt` was ever correct, but one way I have used it is for low-priority periodic background tasks. Instead of just repeating the job every N seconds, I'd try, within some bounds, to have it run when the system wasn't otherwise doing anything. Something like this:

```scheme
#lang racket
(define (launch-background-task min-delay max-delay thunk)
  (thread (λ ()
            (let loop ()
              (sleep min-delay)
              (sync/timeout (- max-delay min-delay)
                (system-idle-evt))
              (thunk)
              (loop)))))

```

In this case, I only care that no coroutine threads time-sharing with this one can make progress, since blocking the background task wouldn't make any more resources available to other OS threads. I've used this pattern in programs that also use places, and I definitely would have been surprised if anything happening in a different place mattered to `(system-idle-evt)`, but I haven't checked that understanding. I have never used futures beyond toy experiments, so I'd never given much thought to how they would interact with `(system-idle-evt)`, but the fact that its documentation talks only about “threads” gave me the impression that a future being able to run would not matter.

It's also possible that some other construct, maybe thread groups, could be a better way to express what I wanted. Thread groups are another feature I've never seriously used. The model of a tree of threads/groups sharing equal priority at each level of nesting is conceptually elegant, and I can see how it maps very neatly onto e.g. programs running in DrRacket, but it hasn't corresponded in an obvious way to problems I've worked on, where there aren't clear “groups” so much as a job, or a few, that is especially low priority. Maybe, if an application anticipated a several such jobs, a library could provide a `low-priority-thread-group` that components could put their background jobs into, but it would rely on components to explicitly cooperate (since putting just one thread in the group wouldn't deprioritize it), which seems less than ideal from a modularity perspective.

---

<div class="post-metadata">

**Author:** ![mflatt](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/mflatt/32/6_2.png) [@mflatt](https://racket.discourse.group/u/mflatt)\
**Post date:** [October 25, 2025, 12:19pm UTC](https://racket.discourse.group/t/help-test-via-snapshots-parallel-threads/3920/25 "2025-10-25T12:19:28Z")

</div>

Yes, when a thread is killed, `thread-wait` treats it like one that aborted to the initial prompt. I'll update the documentation.

As for `system-idle-evt`, I agree that you're looking for something like thread priorities, which Racket doesn't currently provide. And I agree that you've managed to get something like low-priority scheduling out of `(system-idle-evt)` by knowing how all the threads within your place behave. You're also right that futures do not affect whether `(system-idle-evt)` is ready.

But `system-idle-evt` is not intended for low-priority scheduling, and it achieves that goal only in a limited sense (e.g., `(thunk)` runs with the same priority as everything else in your example). Instead, the `system-idle-evt` constructor is intended for testing related to thread thread synchronization API, where the test needs to wait for threads in flight to reach a stopping point. For that purpose, it's most useful to thread all threads the same (since it's about the thread API), even if there are futures and places running.

I'm not sure whether it would be generally useful to have a different primitive that is like `system-idle-evt` but, say, specific to a thread group. I'm pretty sure that it would be useful for testing purposes to have something like `system-idle-evt` that also covers futures and places, but that need is small enough that it has never seemed worthwhile. More support for prioritization also seems useful to add eventually.

[Previous page](https://racket.discourse.group/t/help-test-via-snapshots-parallel-threads/3920.md?page=1)
