srfi-18.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. ;;; srfi-18.scm --- Multithreading support
  2. ;; Copyright (C) 2008, 2009, 2010, 2012, 2014 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 (srfi srfi-34)
  29. :export (
  30. ;;; Threads
  31. ;; current-thread <= in the core
  32. ;; thread? <= in the core
  33. make-thread
  34. thread-name
  35. thread-specific
  36. thread-specific-set!
  37. thread-start!
  38. thread-yield!
  39. thread-sleep!
  40. thread-terminate!
  41. thread-join!
  42. ;;; Mutexes
  43. ;; mutex? <= in the core
  44. make-mutex
  45. mutex-name
  46. mutex-specific
  47. mutex-specific-set!
  48. mutex-state
  49. mutex-lock!
  50. mutex-unlock!
  51. ;;; Condition variables
  52. ;; condition-variable? <= in the core
  53. make-condition-variable
  54. condition-variable-name
  55. condition-variable-specific
  56. condition-variable-specific-set!
  57. condition-variable-signal!
  58. condition-variable-broadcast!
  59. condition-variable-wait!
  60. ;;; Time
  61. current-time
  62. time?
  63. time->seconds
  64. seconds->time
  65. current-exception-handler
  66. with-exception-handler
  67. raise
  68. join-timeout-exception?
  69. abandoned-mutex-exception?
  70. terminated-thread-exception?
  71. uncaught-exception?
  72. uncaught-exception-reason
  73. )
  74. :re-export (current-thread thread? mutex? condition-variable?)
  75. :replace (current-time
  76. make-thread
  77. make-mutex
  78. make-condition-variable
  79. raise))
  80. (if (not (provided? 'threads))
  81. (error "SRFI-18 requires Guile with threads support"))
  82. (cond-expand-provide (current-module) '(srfi-18))
  83. (define (check-arg-type pred arg caller)
  84. (if (pred arg)
  85. arg
  86. (scm-error 'wrong-type-arg caller
  87. "Wrong type argument: ~S" (list arg) '())))
  88. (define abandoned-mutex-exception (list 'abandoned-mutex-exception))
  89. (define join-timeout-exception (list 'join-timeout-exception))
  90. (define terminated-thread-exception (list 'terminated-thread-exception))
  91. (define uncaught-exception (list 'uncaught-exception))
  92. (define object-names (make-weak-key-hash-table))
  93. (define object-specifics (make-weak-key-hash-table))
  94. (define thread-start-conds (make-weak-key-hash-table))
  95. (define thread-exception-handlers (make-weak-key-hash-table))
  96. ;; EXCEPTIONS
  97. (define raise (@ (srfi srfi-34) raise))
  98. (define (initial-handler obj)
  99. (srfi-18-exception-preserver (cons uncaught-exception obj)))
  100. (define thread->exception (make-object-property))
  101. (define (srfi-18-exception-preserver obj)
  102. (if (or (terminated-thread-exception? obj)
  103. (uncaught-exception? obj))
  104. (set! (thread->exception (current-thread)) obj)))
  105. (define (srfi-18-exception-handler key . args)
  106. ;; SRFI 34 exceptions continue to bubble up no matter who handles them, so
  107. ;; if one is caught at this level, it has already been taken care of by
  108. ;; `initial-handler'.
  109. (and (not (eq? key 'srfi-34))
  110. (srfi-18-exception-preserver (if (null? args)
  111. (cons uncaught-exception key)
  112. (cons* uncaught-exception key args)))))
  113. (define (current-handler-stack)
  114. (let ((ct (current-thread)))
  115. (or (hashq-ref thread-exception-handlers ct)
  116. (hashq-set! thread-exception-handlers ct (list initial-handler)))))
  117. (define (with-exception-handler handler thunk)
  118. (let ((ct (current-thread))
  119. (hl (current-handler-stack)))
  120. (check-arg-type procedure? handler "with-exception-handler")
  121. (check-arg-type thunk? thunk "with-exception-handler")
  122. (hashq-set! thread-exception-handlers ct (cons handler hl))
  123. ((@ (srfi srfi-34) with-exception-handler)
  124. (lambda (obj)
  125. (hashq-set! thread-exception-handlers ct hl)
  126. (handler obj))
  127. (lambda ()
  128. (call-with-values thunk
  129. (lambda res
  130. (hashq-set! thread-exception-handlers ct hl)
  131. (apply values res)))))))
  132. (define (current-exception-handler)
  133. (car (current-handler-stack)))
  134. (define (join-timeout-exception? obj) (eq? obj join-timeout-exception))
  135. (define (abandoned-mutex-exception? obj) (eq? obj abandoned-mutex-exception))
  136. (define (uncaught-exception? obj)
  137. (and (pair? obj) (eq? (car obj) uncaught-exception)))
  138. (define (uncaught-exception-reason exc)
  139. (cdr (check-arg-type uncaught-exception? exc "uncaught-exception-reason")))
  140. (define (terminated-thread-exception? obj)
  141. (eq? obj terminated-thread-exception))
  142. ;; THREADS
  143. ;; Create a new thread and prevent it from starting using a condition variable.
  144. ;; Once started, install a top-level exception handler that rethrows any
  145. ;; exceptions wrapped in an uncaught-exception wrapper.
  146. (define make-thread
  147. (let ((make-cond-wrapper (lambda (thunk lcond lmutex scond smutex)
  148. (lambda ()
  149. (lock-mutex lmutex)
  150. (signal-condition-variable lcond)
  151. (lock-mutex smutex)
  152. (unlock-mutex lmutex)
  153. (wait-condition-variable scond smutex)
  154. (unlock-mutex smutex)
  155. (with-exception-handler initial-handler
  156. thunk)))))
  157. (lambda (thunk . name)
  158. (let ((n (and (pair? name) (car name)))
  159. (lm (make-mutex 'launch-mutex))
  160. (lc (make-condition-variable 'launch-condition-variable))
  161. (sm (make-mutex 'start-mutex))
  162. (sc (make-condition-variable 'start-condition-variable)))
  163. (lock-mutex lm)
  164. (let ((t (call-with-new-thread (make-cond-wrapper thunk lc lm sc sm)
  165. srfi-18-exception-handler)))
  166. (hashq-set! thread-start-conds t (cons sm sc))
  167. (and n (hashq-set! object-names t n))
  168. (wait-condition-variable lc lm)
  169. (unlock-mutex lm)
  170. t)))))
  171. (define (thread-name thread)
  172. (hashq-ref object-names (check-arg-type thread? thread "thread-name")))
  173. (define (thread-specific thread)
  174. (hashq-ref object-specifics
  175. (check-arg-type thread? thread "thread-specific")))
  176. (define (thread-specific-set! thread obj)
  177. (hashq-set! object-specifics
  178. (check-arg-type thread? thread "thread-specific-set!")
  179. obj)
  180. *unspecified*)
  181. (define (thread-start! thread)
  182. (let ((x (hashq-ref thread-start-conds
  183. (check-arg-type thread? thread "thread-start!"))))
  184. (and x (let ((smutex (car x))
  185. (scond (cdr x)))
  186. (hashq-remove! thread-start-conds thread)
  187. (lock-mutex smutex)
  188. (signal-condition-variable scond)
  189. (unlock-mutex smutex)))
  190. thread))
  191. (define (thread-yield!) (yield) *unspecified*)
  192. (define (thread-sleep! timeout)
  193. (let* ((ct (time->seconds (current-time)))
  194. (t (cond ((time? timeout) (- (time->seconds timeout) ct))
  195. ((number? timeout) (- timeout ct))
  196. (else (scm-error 'wrong-type-arg "thread-sleep!"
  197. "Wrong type argument: ~S"
  198. (list timeout)
  199. '()))))
  200. (secs (inexact->exact (truncate t)))
  201. (usecs (inexact->exact (truncate (* (- t secs) 1000000)))))
  202. (and (> secs 0) (sleep secs))
  203. (and (> usecs 0) (usleep usecs))
  204. *unspecified*))
  205. ;; A convenience function for installing exception handlers on SRFI-18
  206. ;; primitives that resume the calling continuation after the handler is
  207. ;; invoked -- this resolves a behavioral incompatibility with Guile's
  208. ;; implementation of SRFI-34, which uses lazy-catch and rethrows handled
  209. ;; exceptions. (SRFI-18, "Primitives and exceptions")
  210. (define (wrap thunk)
  211. (lambda (continuation)
  212. (with-exception-handler (lambda (obj)
  213. ((current-exception-handler) obj)
  214. (continuation))
  215. thunk)))
  216. ;; A pass-thru to cancel-thread that first installs a handler that throws
  217. ;; terminated-thread exception, as per SRFI-18,
  218. (define (thread-terminate! thread)
  219. (define (thread-terminate-inner!)
  220. (let ((current-handler (thread-cleanup thread)))
  221. (if (thunk? current-handler)
  222. (set-thread-cleanup! thread
  223. (lambda ()
  224. (with-exception-handler initial-handler
  225. current-handler)
  226. (srfi-18-exception-preserver
  227. terminated-thread-exception)))
  228. (set-thread-cleanup! thread
  229. (lambda () (srfi-18-exception-preserver
  230. terminated-thread-exception))))
  231. (cancel-thread thread)
  232. *unspecified*))
  233. (thread-terminate-inner!))
  234. (define (thread-join! thread . args)
  235. (define thread-join-inner!
  236. (wrap (lambda ()
  237. (let ((v (apply join-thread thread args))
  238. (e (thread->exception thread)))
  239. (if (and (= (length args) 1) (not v))
  240. (raise join-timeout-exception))
  241. (if e (raise e))
  242. v))))
  243. (call/cc thread-join-inner!))
  244. ;; MUTEXES
  245. ;; These functions are all pass-thrus to the existing Guile implementations.
  246. (define make-mutex
  247. (lambda name
  248. (let ((n (and (pair? name) (car name)))
  249. (m ((@ (guile) make-mutex)
  250. 'unchecked-unlock
  251. 'allow-external-unlock
  252. 'recursive)))
  253. (and n (hashq-set! object-names m n)) m)))
  254. (define (mutex-name mutex)
  255. (hashq-ref object-names (check-arg-type mutex? mutex "mutex-name")))
  256. (define (mutex-specific mutex)
  257. (hashq-ref object-specifics
  258. (check-arg-type mutex? mutex "mutex-specific")))
  259. (define (mutex-specific-set! mutex obj)
  260. (hashq-set! object-specifics
  261. (check-arg-type mutex? mutex "mutex-specific-set!")
  262. obj)
  263. *unspecified*)
  264. (define (mutex-state mutex)
  265. (let ((owner (mutex-owner mutex)))
  266. (if owner
  267. (if (thread-exited? owner) 'abandoned owner)
  268. (if (> (mutex-level mutex) 0) 'not-owned 'not-abandoned))))
  269. (define (mutex-lock! mutex . args)
  270. (define mutex-lock-inner!
  271. (wrap (lambda ()
  272. (catch 'abandoned-mutex-error
  273. (lambda () (apply lock-mutex mutex args))
  274. (lambda (key . args) (raise abandoned-mutex-exception))))))
  275. (call/cc mutex-lock-inner!))
  276. (define (mutex-unlock! mutex . args)
  277. (apply unlock-mutex mutex args))
  278. ;; CONDITION VARIABLES
  279. ;; These functions are all pass-thrus to the existing Guile implementations.
  280. (define make-condition-variable
  281. (lambda name
  282. (let ((n (and (pair? name) (car name)))
  283. (m ((@ (guile) make-condition-variable))))
  284. (and n (hashq-set! object-names m n)) m)))
  285. (define (condition-variable-name condition-variable)
  286. (hashq-ref object-names (check-arg-type condition-variable?
  287. condition-variable
  288. "condition-variable-name")))
  289. (define (condition-variable-specific condition-variable)
  290. (hashq-ref object-specifics (check-arg-type condition-variable?
  291. condition-variable
  292. "condition-variable-specific")))
  293. (define (condition-variable-specific-set! condition-variable obj)
  294. (hashq-set! object-specifics
  295. (check-arg-type condition-variable?
  296. condition-variable
  297. "condition-variable-specific-set!")
  298. obj)
  299. *unspecified*)
  300. (define (condition-variable-signal! cond)
  301. (signal-condition-variable cond)
  302. *unspecified*)
  303. (define (condition-variable-broadcast! cond)
  304. (broadcast-condition-variable cond)
  305. *unspecified*)
  306. ;; TIME
  307. (define current-time gettimeofday)
  308. (define (time? obj)
  309. (and (pair? obj)
  310. (let ((co (car obj))) (and (integer? co) (>= co 0)))
  311. (let ((co (cdr obj))) (and (integer? co) (>= co 0)))))
  312. (define (time->seconds time)
  313. (and (check-arg-type time? time "time->seconds")
  314. (+ (car time) (/ (cdr time) 1000000))))
  315. (define (seconds->time x)
  316. (and (check-arg-type number? x "seconds->time")
  317. (let ((fx (truncate x)))
  318. (cons (inexact->exact fx)
  319. (inexact->exact (truncate (* (- x fx) 1000000)))))))
  320. ;; srfi-18.scm ends here