srfi-18.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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 ((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. ;; EXCEPTIONS
  124. ;; All threads created by SRFI-18 have an initial handler installed that
  125. ;; will squirrel away an uncaught exception to allow it to bubble out to
  126. ;; joining threads. However for the main thread and other threads not
  127. ;; created by SRFI-18, just let the exception bubble up by passing on
  128. ;; doing anything with the exception.
  129. (define (exception-handler-for-foreign-threads obj)
  130. (values))
  131. (define current-exception-handler
  132. (make-parameter exception-handler-for-foreign-threads))
  133. (define (with-exception-handler handler thunk)
  134. (check-arg-type procedure? handler "with-exception-handler")
  135. (check-arg-type thunk? thunk "with-exception-handler")
  136. (srfi-34:with-exception-handler
  137. (let ((prev-handler (current-exception-handler)))
  138. (lambda (obj)
  139. (parameterize ((current-exception-handler prev-handler))
  140. (handler obj))))
  141. (lambda ()
  142. (parameterize ((current-exception-handler handler))
  143. (thunk)))))
  144. ;; THREADS
  145. ;; Create a new thread and prevent it from starting using a condition variable.
  146. ;; Once started, install a top-level exception handler that rethrows any
  147. ;; exceptions wrapped in an uncaught-exception wrapper.
  148. (define (with-thread-mutex-cleanup thunk)
  149. (let ((mutexes (make-weak-key-hash-table)))
  150. (dynamic-wind
  151. values
  152. (lambda ()
  153. (parameterize ((thread-mutexes mutexes))
  154. (thunk)))
  155. (lambda ()
  156. (let ((thread (current-thread)))
  157. (hash-for-each (lambda (mutex _)
  158. (when (eq? (mutex-owner mutex) thread)
  159. (abandon-mutex! mutex)))
  160. mutexes))))))
  161. (define* (make-thread thunk #:optional name)
  162. (let* ((sm (make-mutex 'start-mutex))
  163. (sc (make-condition-variable 'start-condition-variable))
  164. (thread (%make-thread #f name #f (cons sm sc) #f)))
  165. (mutex-lock! sm)
  166. (let ((prim (threads:call-with-new-thread
  167. (lambda ()
  168. (catch #t
  169. (lambda ()
  170. (parameterize ((current-thread thread))
  171. (with-thread-mutex-cleanup
  172. (lambda ()
  173. (mutex-lock! sm)
  174. (condition-variable-signal! sc)
  175. (mutex-unlock! sm sc)
  176. (thunk)))))
  177. (lambda (key . args)
  178. (set-thread-exception!
  179. thread
  180. (condition (&uncaught-exception
  181. (reason
  182. (match (cons key args)
  183. (('srfi-34 obj) obj)
  184. (obj obj))))))))))))
  185. (set-thread-prim! thread prim)
  186. (mutex-unlock! sm sc)
  187. thread)))
  188. (define (thread-start! thread)
  189. (match (thread-start-conds thread)
  190. ((smutex . scond)
  191. (set-thread-start-conds! thread #f)
  192. (mutex-lock! smutex)
  193. (condition-variable-signal! scond)
  194. (mutex-unlock! smutex))
  195. (#f #f))
  196. thread)
  197. (define (thread-yield!) (threads:yield) *unspecified*)
  198. (define (thread-sleep! timeout)
  199. (let* ((ct (time->seconds (current-time)))
  200. (t (cond ((time? timeout) (- (time->seconds timeout) ct))
  201. ((number? timeout) (- timeout ct))
  202. (else (scm-error 'wrong-type-arg "thread-sleep!"
  203. "Wrong type argument: ~S"
  204. (list timeout)
  205. '()))))
  206. (secs (inexact->exact (truncate t)))
  207. (usecs (inexact->exact (truncate (* (- t secs) 1000000)))))
  208. (when (> secs 0) (sleep secs))
  209. (when (> usecs 0) (usleep usecs))
  210. *unspecified*))
  211. ;; Whereas SRFI-34 leaves the continuation of a call to an exception
  212. ;; handler unspecified, SRFI-18 has this to say:
  213. ;;
  214. ;; When one of the primitives defined in this SRFI raises an exception
  215. ;; defined in this SRFI, the exception handler is called with the same
  216. ;; continuation as the primitive (i.e. it is a tail call to the
  217. ;; exception handler).
  218. ;;
  219. ;; Therefore arrange for exceptions thrown by SRFI-18 primitives to run
  220. ;; handlers with the continuation of the primitive call, for those
  221. ;; primitives that throw exceptions.
  222. (define (with-exception-handlers-here thunk)
  223. (let ((tag (make-prompt-tag)))
  224. (call-with-prompt tag
  225. (lambda ()
  226. (with-exception-handler (lambda (exn) (abort-to-prompt tag exn))
  227. thunk))
  228. (lambda (k exn)
  229. ((current-exception-handler) exn)))))
  230. ;; A unique value.
  231. (define %cancel-sentinel (list 'cancelled))
  232. (define (thread-terminate! thread)
  233. (threads:cancel-thread (thread-prim thread) %cancel-sentinel)
  234. *unspecified*)
  235. ;; A unique value.
  236. (define %timeout-sentinel (list 1))
  237. (define* (thread-join! thread #:optional (timeout %timeout-sentinel)
  238. (timeoutval %timeout-sentinel))
  239. (let ((t (thread-prim thread)))
  240. (with-exception-handlers-here
  241. (lambda ()
  242. (let* ((v (if (eq? timeout %timeout-sentinel)
  243. (threads:join-thread t)
  244. (threads:join-thread t timeout %timeout-sentinel))))
  245. (cond
  246. ((eq? v %timeout-sentinel)
  247. (if (eq? timeoutval %timeout-sentinel)
  248. (srfi-34:raise (condition (&join-timeout-exception)))
  249. timeoutval))
  250. ((eq? v %cancel-sentinel)
  251. (srfi-34:raise (condition (&terminated-thread-exception))))
  252. ((thread-exception thread) => srfi-34:raise)
  253. (else v)))))))
  254. ;; MUTEXES
  255. (define* (make-mutex #:optional name)
  256. (%make-mutex (threads:make-mutex 'allow-external-unlock) name #f #f #f))
  257. (define (mutex-state mutex)
  258. (cond
  259. ((mutex-abandoned? mutex) 'abandoned)
  260. ((mutex-owner mutex))
  261. ((> (threads:mutex-level (mutex-prim mutex)) 0) 'not-owned)
  262. (else 'not-abandoned)))
  263. (define (abandon-mutex! mutex)
  264. (set-mutex-abandoned?! mutex #t)
  265. (threads:unlock-mutex (mutex-prim mutex)))
  266. (define* (mutex-lock! mutex #:optional timeout (thread (current-thread)))
  267. (let ((mutexes (thread-mutexes)))
  268. (when mutexes
  269. (hashq-set! mutexes mutex #t)))
  270. (with-exception-handlers-here
  271. (lambda ()
  272. (cond
  273. ((threads:lock-mutex (mutex-prim mutex) timeout)
  274. (set-mutex-owner! mutex thread)
  275. (when (mutex-abandoned? mutex)
  276. (set-mutex-abandoned?! mutex #f)
  277. (srfi-34:raise
  278. (condition (&abandoned-mutex-exception))))
  279. #t)
  280. (else #f)))))
  281. (define %unlock-sentinel (list 'unlock))
  282. (define* (mutex-unlock! mutex #:optional (cond-var %unlock-sentinel)
  283. (timeout %unlock-sentinel))
  284. (when (mutex-owner mutex)
  285. (set-mutex-owner! mutex #f)
  286. (cond
  287. ((eq? cond-var %unlock-sentinel)
  288. (threads:unlock-mutex (mutex-prim mutex)))
  289. ((eq? timeout %unlock-sentinel)
  290. (threads:wait-condition-variable (condition-variable-prim cond-var)
  291. (mutex-prim mutex))
  292. (threads:unlock-mutex (mutex-prim mutex)))
  293. ((threads:wait-condition-variable (condition-variable-prim cond-var)
  294. (mutex-prim mutex)
  295. timeout)
  296. (threads:unlock-mutex (mutex-prim mutex)))
  297. (else #f))))
  298. ;; CONDITION VARIABLES
  299. ;; These functions are all pass-thrus to the existing Guile implementations.
  300. (define* (make-condition-variable #:optional name)
  301. (%make-condition-variable (threads:make-condition-variable) name #f))
  302. (define (condition-variable-signal! cond)
  303. (threads:signal-condition-variable (condition-variable-prim cond))
  304. *unspecified*)
  305. (define (condition-variable-broadcast! cond)
  306. (threads:broadcast-condition-variable (condition-variable-prim cond))
  307. *unspecified*)
  308. ;; TIME
  309. (define current-time gettimeofday)
  310. (define (time? obj)
  311. (and (pair? obj)
  312. (let ((co (car obj))) (and (integer? co) (>= co 0)))
  313. (let ((co (cdr obj))) (and (integer? co) (>= co 0)))))
  314. (define (time->seconds time)
  315. (and (check-arg-type time? time "time->seconds")
  316. (+ (car time) (/ (cdr time) 1000000))))
  317. (define (seconds->time x)
  318. (and (check-arg-type number? x "seconds->time")
  319. (let ((fx (truncate x)))
  320. (cons (inexact->exact fx)
  321. (inexact->exact (truncate (* (- x fx) 1000000)))))))
  322. ;; srfi-18.scm ends here