Logging, custodians and larger applications

So the simple answer is, the main racket thread doesn't wait for threads you have started unless you tell it to do that, so if you don't and the main thread is done, the program exits even when there are still other threads running. I think this is what causes the error message you are seeing.

Here is an example program demonstrating this:

#lang racket

(define (background-task)
  (thread
   (thunk
    (for ([i (in-range 0 10)])
      (displayln i)
      (sleep 1)))))

(module+ main
  (void (background-task))

  ;; now lets get our work done
  (sleep 1)
  (displayln "not very much work todo...")
  (sleep 1)
  (displayln "just printing a bunch of lines...")
  (sleep 1)
  (displayln "done"))
0
not very much work todo...
1
just printing a bunch of lines...
2
done

We can change this program to explicitly wait for the background task:

#lang racket

(define (background-task)
  (thread
   (thunk
    (for ([i (in-range 0 10)])
      (displayln i)
      (sleep 1)))))

(module+ main
  (define bg-thread (background-task))

  ;; now lets get our work done
  (sleep 1)
  (displayln "not very much work todo...")
  (sleep 1)
  (displayln "just printing a bunch of lines...")
  (sleep 1)
  (displayln "done")

  (thread-wait bg-thread))

With this the program exits after the background-task is done counting.

Now if you have used e.g. #lang racket/gui you may say that there the program reaches the end of my (module+ main ...) but it doesn't exit.

Yes and there is an explanation for that, racket/gui installs an executable-yield-handler that waits for gui related things that are still running (open windows, timers, etc.). 12 Startup Actions

We can do a similar thing to make sure that our background task can finish.
This allows us to make using our background task more convenient for the user, the user doesn't have to manually call thread-wait:

#lang racket

(define (background-task)
  (thread
   (thunk
    (for ([i (in-range 0 10)])
      (displayln i)
      (sleep 1)))))

(define (register-yield-handler/wait-for thread)
  (define orig-handler (executable-yield-handler))
  (executable-yield-handler
   (λ (arg)
     (thread-wait thread)
     (orig-handler arg))))

(define (start-background-task)
  (register-yield-handler/wait-for (background-task)))

(module+ main
  (start-background-task)

  ;; now lets get our work done
  (sleep 1)
  (displayln "not very much work todo...")
  (sleep 1)
  (displayln "just printing a bunch of lines...")
  (sleep 1)
  (displayln "done"))

If you have to wait for many threads you maybe want to change register-yield-handler/wait-for to get a list of threads and make it loop over that and wait for all of them or maybe even to something using sync.

4 Likes