srfi-18.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. ;;; srfi-18.scm --- Multithreading support
  2. ;; Copyright (C) 2008, 2009, 2010, 2012, 2014, 2018 Free Software Foundation, Inc.
  3. ;;
  4. ;; This library is free software; you can redistribute it and/or
  5. ;; modify it under the terms of the GNU Lesser General Public
  6. ;; License as published by the Free Software Foundation; either
  7. ;; version 3 of the License, or (at your option) any later version.
  8. ;;
  9. ;; This library is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;; Lesser General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU Lesser General Public
  15. ;; License along with this library; if not, write to the Free Software
  16. ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. ;;; Author: Julian Graham <julian.graham@aya.yale.edu>
  18. ;;; Date: 2008-04-11
  19. ;;; Commentary:
  20. ;; This is an implementation of SRFI-18 (Multithreading support).
  21. ;;
  22. ;; All procedures defined in SRFI-18, which are not already defined in
  23. ;; the Guile core library, are exported.
  24. ;;
  25. ;; This module is fully documented in the Guile Reference Manual.
  26. ;;; Code:
  27. (define-module (srfi srfi-18)
  28. #:use-module ((ice-9 threads) #:prefix threads:)
  29. #:use-module (ice-9 match)
  30. #:use-module (srfi srfi-9)
  31. #:use-module ((srfi srfi-34) #:prefix srfi-34:)
  32. #:use-module ((srfi srfi-35) #:select (define-condition-type
  33. &error
  34. condition))
  35. #:export (;; Threads
  36. make-thread
  37. thread-name
  38. thread-specific
  39. thread-specific-set!
  40. thread-start!
  41. thread-yield!
  42. thread-sleep!
  43. thread-terminate!
  44. thread-join!
  45. ;; Mutexes
  46. make-mutex
  47. mutex
  48. mutex-name
  49. mutex-specific
  50. mutex-specific-set!
  51. mutex-state
  52. mutex-lock!
  53. mutex-unlock!
  54. ;; Condition variables
  55. make-condition-variable
  56. condition-variable-name
  57. condition-variable-specific
  58. condition-variable-specific-set!
  59. condition-variable-signal!
  60. condition-variable-broadcast!
  61. ;; Time
  62. current-time
  63. time?
  64. time->seconds
  65. seconds->time
  66. current-exception-handler
  67. with-exception-handler
  68. join-timeout-exception?
  69. abandoned-mutex-exception?
  70. terminated-thread-exception?
  71. uncaught-exception?
  72. uncaught-exception-reason)
  73. #:re-export ((srfi-34:raise . raise))
  74. #:replace (current-time
  75. current-thread
  76. thread?
  77. make-thread
  78. make-mutex
  79. mutex?
  80. make-condition-variable
  81. condition-variable?))
  82. (unless (provided? 'threads)
  83. (error "SRFI-18 requires Guile with threads support"))
  84. (cond-expand-provide (current-module) '(srfi-18))
  85. (define (check-arg-type pred arg caller)
  86. (if (pred arg)
  87. arg
  88. (scm-error 'wrong-type-arg caller
  89. "Wrong type argument: ~S" (list arg) '())))
  90. (define-condition-type &abandoned-mutex-exception &error
  91. abandoned-mutex-exception?)
  92. (define-condition-type &join-timeout-exception &error
  93. join-timeout-exception?)
  94. (define-condition-type &terminated-thread-exception &error
  95. terminated-thread-exception?)
  96. (define-condition-type &uncaught-exception &error
  97. uncaught-exception?
  98. (reason uncaught-exception-reason))
  99. (define-record-type <mutex>
  100. (%make-mutex prim name specific owner abandoned?)
  101. mutex?
  102. (prim mutex-prim)
  103. (name mutex-name)
  104. (specific mutex-specific mutex-specific-set!)
  105. (owner mutex-owner set-mutex-owner!)
  106. (abandoned? mutex-abandoned? set-mutex-abandoned?!))
  107. (define-record-type <condition-variable>
  108. (%make-condition-variable prim name specific)
  109. condition-variable?
  110. (prim condition-variable-prim)
  111. (name condition-variable-name)
  112. (specific condition-variable-specific condition-variable-specific-set!))
  113. (define-record-type <thread>
  114. (%make-thread prim name specific start-conds exception)
  115. thread?
  116. (prim thread-prim set-thread-prim!)
  117. (name thread-name)
  118. (specific thread-specific thread-specific-set!)
  119. (start-conds thread-start-conds set-thread-start-conds!)
  120. (exception thread-exception set-thread-exception!))
  121. (define current-thread (make-parameter (%make-thread #f #f #f #f #f)))
  122. (define thread-mutexes (make-parameter #f))
  123. (define (timeout->absolute-time timeout)
  124. "Return an absolute time in seconds corresponding to TIMEOUT. TIMEOUT
  125. can be any value authorized by SRFI-18: a number (relative time), a time
  126. object (absolute point in time), or #f."
  127. (cond ((number? timeout) ;seconds relative to now
  128. (+ ((@ (guile) current-time)) timeout))
  129. ((time? timeout) ;absolute point in time
  130. (time->seconds timeout))
  131. (else timeout))) ;pair or #f
  132. ;; EXCEPTIONS
  133. ;; All threads created by SRFI-18 have an initial handler installed that
  134. ;; will squirrel away an uncaught exception to allow it to bubble out to
  135. ;; joining threads. However for the main thread and other threads not
  136. ;; created by SRFI-18, just let the exception bubble up by passing on
  137. ;; doing anything with the exception.
  138. (define (exception-handler-for-foreign-threads obj)
  139. (values))
  140. (define current-exception-handler
  141. (make-parameter exception-handler-for-foreign-threads))
  142. (define (with-exception-handler handler thunk)
  143. (check-arg-type procedure? handler "with-exception-handler")
  144. (check-arg-type thunk? thunk "with-exception-handler")
  145. (srfi-34:with-exception-handler
  146. (let ((prev-handler (current-exception-handler)))
  147. (lambda (obj)
  148. (parameterize ((current-exception-handler prev-handler))
  149. (handler obj))))
  150. (lambda ()
  151. (parameterize ((current-exception-handler handler))
  152. (thunk)))))
  153. ;; THREADS
  154. ;; Create a new thread and prevent it from starting using a condition variable.
  155. ;; Once started, install a top-level exception handler that rethrows any
  156. ;; exceptions wrapped in an uncaught-exception wrapper.
  157. (define (with-thread-mutex-cleanup thunk)
  158. (let ((mutexes (make-weak-key-hash-table)))
  159. (dynamic-wind
  160. values
  161. (lambda ()
  162. (parameterize ((thread-mutexes mutexes))
  163. (thunk)))
  164. (lambda ()
  165. (let ((thread (current-thread)))
  166. (hash-for-each (lambda (mutex _)
  167. (when (eq? (mutex-owner mutex) thread)
  168. (abandon-mutex! mutex)))
  169. mutexes))))))
  170. (define* (make-thread thunk #:optional name)
  171. (let* ((sm (make-mutex 'start-mutex))
  172. (sc (make-condition-variable 'start-condition-variable))
  173. (thread (%make-thread #f name #f (cons sm sc) #f)))
  174. (mutex-lock! sm)
  175. (let ((prim (threads:call-with-new-thread
  176. (lambda ()
  177. (catch #t
  178. (lambda ()
  179. (parameterize ((current-thread thread))
  180. (with-thread-mutex-cleanup
  181. (lambda ()
  182. (mutex-lock! sm)
  183. (condition-variable-signal! sc)
  184. (mutex-unlock! sm sc)
  185. (thunk)))))
  186. (lambda (key . args)
  187. (set-thread-exception!
  188. thread
  189. (condition (&uncaught-exception
  190. (reason
  191. (match (cons key args)
  192. (('srfi-34 obj) obj)
  193. (obj obj))))))))))))
  194. (set-thread-prim! thread prim)
  195. (mutex-unlock! sm sc)
  196. thread)))
  197. (define (thread-start! thread)
  198. (match (thread-start-conds thread)
  199. ((smutex . scond)
  200. (set-thread-start-conds! thread #f)
  201. (mutex-lock! smutex)
  202. (condition-variable-signal! scond)
  203. (mutex-unlock! smutex))
  204. (#f #f))
  205. thread)
  206. (define (thread-yield!) (threads:yield) *unspecified*)
  207. (define (thread-sleep! timeout)
  208. (let* ((t (cond ((time? timeout) (- (time->seconds timeout)
  209. (time->seconds (current-time))))
  210. ((number? timeout) timeout)
  211. (else (scm-error 'wrong-type-arg "thread-sleep!"
  212. "Wrong type argument: ~S"
  213. (list timeout)
  214. '()))))
  215. (secs (inexact->exact (truncate t)))
  216. (usecs (inexact->exact (truncate (* (- t secs) 1000000)))))
  217. (when (> secs 0) (sleep secs))
  218. (when (> usecs 0) (usleep usecs))
  219. *unspecified*))
  220. ;; Whereas SRFI-34 leaves the continuation of a call to an exception
  221. ;; handler unspecified, SRFI-18 has this to say:
  222. ;;
  223. ;; When one of the primitives defined in this SRFI raises an exception
  224. ;; defined in this SRFI, the exception handler is called with the same
  225. ;; continuation as the primitive (i.e. it is a tail call to the
  226. ;; exception handler).
  227. ;;
  228. ;; Therefore arrange for exceptions thrown by SRFI-18 primitives to run
  229. ;; handlers with the continuation of the primitive call, for those
  230. ;; primitives that throw exceptions.
  231. (define (with-exception-handlers-here thunk)
  232. (let ((tag (make-prompt-tag)))
  233. (call-with-prompt tag
  234. (lambda ()
  235. (with-exception-handler (lambda (exn) (abort-to-prompt tag exn))
  236. thunk))
  237. (lambda (k exn)
  238. ((current-exception-handler) exn)))))
  239. ;; A unique value.
  240. (define %cancel-sentinel (list 'cancelled))
  241. (define (thread-terminate! thread)
  242. (threads:cancel-thread (thread-prim thread) %cancel-sentinel)
  243. *unspecified*)
  244. ;; A unique value.
  245. (define %timeout-sentinel (list 1))
  246. (define* (thread-join! thread #:optional (timeout %timeout-sentinel)
  247. (timeoutval %timeout-sentinel))
  248. (let ((t (thread-prim thread)))
  249. (with-exception-handlers-here
  250. (lambda ()
  251. (let* ((v (if (eq? timeout %timeout-sentinel)
  252. (threads:join-thread t)
  253. (threads:join-thread t timeout %timeout-sentinel))))
  254. (cond
  255. ((eq? v %timeout-sentinel)
  256. (if (eq? timeoutval %timeout-sentinel)
  257. (srfi-34:raise (condition (&join-timeout-exception)))
  258. timeoutval))
  259. ((eq? v %cancel-sentinel)
  260. (srfi-34:raise (condition (&terminated-thread-exception))))
  261. ((thread-exception thread) => srfi-34:raise)
  262. (else v)))))))
  263. ;; MUTEXES
  264. (define* (make-mutex #:optional name)
  265. (%make-mutex (threads:make-mutex 'allow-external-unlock) name #f #f #f))
  266. (define (mutex-state mutex)
  267. (cond
  268. ((mutex-abandoned? mutex) 'abandoned)
  269. ((mutex-owner mutex))
  270. ((> (threads:mutex-level (mutex-prim mutex)) 0) 'not-owned)
  271. (else 'not-abandoned)))
  272. (define (abandon-mutex! mutex)
  273. (set-mutex-abandoned?! mutex #t)
  274. (threads:unlock-mutex (mutex-prim mutex)))
  275. (define* (mutex-lock! mutex #:optional timeout (thread (current-thread)))
  276. (let ((mutexes (thread-mutexes)))
  277. (when mutexes
  278. (hashq-set! mutexes mutex #t)))
  279. (with-exception-handlers-here
  280. (lambda ()
  281. (cond
  282. ((threads:lock-mutex (mutex-prim mutex)
  283. (timeout->absolute-time timeout))
  284. (set-mutex-owner! mutex thread)
  285. (when (mutex-abandoned? mutex)
  286. (set-mutex-abandoned?! mutex #f)
  287. (srfi-34:raise
  288. (condition (&abandoned-mutex-exception))))
  289. #t)
  290. (else #f)))))
  291. (define %unlock-sentinel (list 'unlock))
  292. (define* (mutex-unlock! mutex #:optional (cond-var %unlock-sentinel)
  293. (timeout %unlock-sentinel))
  294. (let ((timeout (timeout->absolute-time timeout)))
  295. (when (mutex-owner mutex)
  296. (set-mutex-owner! mutex #f)
  297. (cond
  298. ((eq? cond-var %unlock-sentinel)
  299. (threads:unlock-mutex (mutex-prim mutex)))
  300. ((eq? timeout %unlock-sentinel)
  301. (threads:wait-condition-variable (condition-variable-prim cond-var)
  302. (mutex-prim mutex))
  303. (threads:unlock-mutex (mutex-prim mutex)))
  304. ((threads:wait-condition-variable (condition-variable-prim cond-var)
  305. (mutex-prim mutex)
  306. timeout)
  307. (threads:unlock-mutex (mutex-prim mutex)))
  308. (else #f)))))
  309. ;; CONDITION VARIABLES
  310. ;; These functions are all pass-thrus to the existing Guile implementations.
  311. (define* (make-condition-variable #:optional name)
  312. (%make-condition-variable (threads:make-condition-variable) name #f))
  313. (define (condition-variable-signal! cond)
  314. (threads:signal-condition-variable (condition-variable-prim cond))
  315. *unspecified*)
  316. (define (condition-variable-broadcast! cond)
  317. (threads:broadcast-condition-variable (condition-variable-prim cond))
  318. *unspecified*)
  319. ;; TIME
  320. (define current-time gettimeofday)
  321. (define (time? obj)
  322. (and (pair? obj)
  323. (let ((co (car obj))) (and (integer? co) (>= co 0)))
  324. (let ((co (cdr obj))) (and (integer? co) (>= co 0)))))
  325. (define (time->seconds time)
  326. (and (check-arg-type time? time "time->seconds")
  327. (+ (car time) (/ (cdr time) 1000000))))
  328. (define (seconds->time x)
  329. (and (check-arg-type number? x "seconds->time")
  330. (let ((fx (truncate x)))
  331. (cons (inexact->exact fx)
  332. (inexact->exact (truncate (* (- x fx) 1000000)))))))
  333. ;; srfi-18.scm ends here