Skip to content

Instantly share code, notes, and snippets.

@jwiegley
Created July 3, 2025 03:56
Show Gist options
  • Save jwiegley/095eb7190b25e079fd673b1cd9bdbe8d to your computer and use it in GitHub Desktop.
Save jwiegley/095eb7190b25e079fd673b1cd9bdbe8d to your computer and use it in GitHub Desktop.
;;; -*- lexical-binding: t -*-
(require 'cl-lib)
(let* ((mutex (make-mutex))
(cond-var (make-condition-variable mutex))
waiting
(thread-a
(make-thread
#'(lambda ()
(message "a0")
(with-mutex mutex
(message "a1")
(setq waiting t)
(condition-wait cond-var)
(setq waiting nil)
(message "a2"))
(message "a3"))))
(thread-b
(make-thread
#'(lambda ()
(message "b0")
(with-mutex mutex
(message "b1")
(condition-notify cond-var)
(message "b2"))
(message "b3")))))
(message "main1")
(let ((countdown 100))
(while (or (thread-live-p thread-a)
(thread-live-p thread-b))
(when (and (not (thread-live-p thread-b))
(= 0 (setq countdown (1- countdown))))
(with-mutex mutex
(if waiting
(thread-signal thread-a 'blocked t))))
(thread-yield)))
(message "main2: %S" (thread-last-error t))
(message "main3: %S" (thread-join thread-a))
(message "main4: %S" (thread-join thread-b))
(message "main5: %S" (all-threads)))
;; This has a deadlock scenario, because notifications are not "queued", but
;; rather sent to the current list of waiting threads at call time:
;;
;; main1
;; b0
;; b1
;; b2
;; b3
;; a0
;; a1
;; ...
;;
;; If I reverse the order in which the threads above are created, it ends with
;; a `blocked' except the majority of the time. Using the order above it
;; succeeds the majority of the time, but the deadlock scenario is still
;; possible.
;;
;; Thus, code must be defensive when using `condition-wait': Waiting threads
;; must be satisfied or killed if all notifying threads have completed and the
;; waiting thread is still running. However, it's also possible that when the
;; waiting thread is the only one remaining, it received the notification and
;; is now executing Lisp beyond the wait point. So, to be safely and properly
;; defensive, we need to use a global flag under the condition variable's
;; mutex AND a timeout to give the waiting thread a chance to naturally finish
;; executing if it wasn't in fact blocked.
;;
;; Another question: Is this also possible? I would be a bit surprised if so,
;; but the manual implies this to be the case:
;;
;; main1
;; a0
;; a1
;; b0
;; b1
;; a2
;; a3
;; b2
;; b3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment