srfi-18.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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 exceptions)
  29. #:use-module ((ice-9 threads) #:prefix threads:)
  30. #:use-module (ice-9 match)
  31. #:use-module (srfi srfi-9)
  32. #:export (;; Threads
  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. make-mutex
  44. mutex
  45. mutex-name
  46. mutex-specific
  47. mutex-specific-set!
  48. mutex-state
  49. mutex-lock!
  50. mutex-unlock!
  51. ;; Condition variables
  52. make-condition-variable
  53. condition-variable-name
  54. condition-variable-specific
  55. condition-variable-specific-set!
  56. condition-variable-signal!
  57. condition-variable-broadcast!
  58. ;; Time
  59. current-time
  60. time?
  61. time->seconds
  62. seconds->time
  63. current-exception-handler
  64. join-timeout-exception?
  65. abandoned-mutex-exception?
  66. terminated-thread-exception?
  67. uncaught-exception?
  68. uncaught-exception-reason)
  69. #:re-export (with-exception-handler)
  70. #:re-export-and-replace ((raise-continuable . raise))
  71. #:replace (current-time
  72. current-thread
  73. thread?
  74. make-thread
  75. make-mutex
  76. mutex?
  77. make-condition-variable
  78. condition-variable?))
  79. (unless (provided? 'threads)
  80. (error "SRFI-18 requires Guile with threads support"))
  81. (cond-expand-provide (current-module) '(srfi-18))
  82. (define (check-arg-type pred arg caller)
  83. (if (pred arg)
  84. arg
  85. (scm-error 'wrong-type-arg caller
  86. "Wrong type argument: ~S" (list arg) '())))
  87. (define-exception-type &abandoned-mutex-exception &external-error
  88. make-abandoned-mutex-exception abandoned-mutex-exception?)
  89. (define-exception-type &join-timeout-exception &external-error
  90. make-join-timeout-exception join-timeout-exception?)
  91. (define-exception-type &terminated-thread-exception &external-error
  92. make-terminated-thread-exception terminated-thread-exception?)
  93. (define-exception-type &uncaught-exception &programming-error
  94. make-uncaught-exception uncaught-exception?
  95. (reason uncaught-exception-reason))
  96. (define-record-type <mutex>
  97. (%make-mutex prim name specific owner abandoned?)
  98. mutex?
  99. (prim mutex-prim)
  100. (name mutex-name)
  101. (specific mutex-specific mutex-specific-set!)
  102. (owner mutex-owner set-mutex-owner!)
  103. (abandoned? mutex-abandoned? set-mutex-abandoned?!))
  104. (define-record-type <condition-variable>
  105. (%make-condition-variable prim name specific)
  106. condition-variable?
  107. (prim condition-variable-prim)
  108. (name condition-variable-name)
  109. (specific condition-variable-specific condition-variable-specific-set!))
  110. (define-record-type <thread>
  111. (%make-thread prim name specific start-conds exception)
  112. thread?
  113. (prim thread-prim set-thread-prim!)
  114. (name thread-name)
  115. (specific thread-specific thread-specific-set!)
  116. (start-conds thread-start-conds set-thread-start-conds!)
  117. (exception thread-exception set-thread-exception!))
  118. (define current-thread (make-parameter (%make-thread #f #f #f #f #f)))
  119. (define thread-mutexes (make-parameter #f))
  120. (define (timeout->absolute-time timeout)
  121. "Return an absolute time in seconds corresponding to TIMEOUT. TIMEOUT
  122. can be any value authorized by SRFI-18: a number (relative time), a time
  123. object (absolute point in time), or #f."
  124. (cond ((number? timeout) ;seconds relative to now
  125. (+ ((@ (guile) current-time)) timeout))
  126. ((time? timeout) ;absolute point in time
  127. (time->seconds timeout))
  128. (else timeout))) ;pair or #f
  129. ;; EXCEPTIONS
  130. ;; All threads created by SRFI-18 have an initial handler installed that
  131. ;; will squirrel away an uncaught exception to allow it to bubble out to
  132. ;; joining threads. However for the main thread and other threads not
  133. ;; created by SRFI-18, just let the exception bubble up by passing on
  134. ;; doing anything with the exception.
  135. (define (exception-handler-for-foreign-threads obj)
  136. (values))
  137. (define (current-exception-handler)
  138. (let ((tag (make-prompt-tag)))
  139. (call-with-prompt
  140. tag
  141. (lambda ()
  142. (with-exception-handler
  143. (lambda (exn)
  144. (raise-exception (abort-to-prompt tag) #:continuable? #t))
  145. (lambda ()
  146. (raise-exception #f #:continuable? #t))))
  147. (lambda (k) k))))
  148. ;; THREADS
  149. ;; Create a new thread and prevent it from starting using a condition variable.
  150. ;; Once started, install a top-level exception handler that rethrows any
  151. ;; exceptions wrapped in an uncaught-exception wrapper.
  152. (define (with-thread-mutex-cleanup thunk)
  153. (let ((mutexes (make-weak-key-hash-table)))
  154. (dynamic-wind
  155. values
  156. (lambda ()
  157. (parameterize ((thread-mutexes mutexes))
  158. (thunk)))
  159. (lambda ()
  160. (let ((thread (current-thread)))
  161. (hash-for-each (lambda (mutex _)
  162. (when (eq? (mutex-owner mutex) thread)
  163. (abandon-mutex! mutex)))
  164. mutexes))))))
  165. (define* (make-thread thunk #:optional name)
  166. (let* ((sm (make-mutex 'start-mutex))
  167. (sc (make-condition-variable 'start-condition-variable))
  168. (thread (%make-thread #f name #f (cons sm sc) #f)))
  169. (mutex-lock! sm)
  170. (let ((prim (threads:call-with-new-thread
  171. (lambda ()
  172. (with-exception-handler
  173. (lambda (exn)
  174. (set-thread-exception! thread
  175. (make-uncaught-exception exn)))
  176. (lambda ()
  177. (parameterize ((current-thread thread))
  178. (with-thread-mutex-cleanup
  179. (lambda ()
  180. (mutex-lock! sm)
  181. (condition-variable-signal! sc)
  182. (mutex-unlock! sm sc)
  183. (thunk)))))
  184. #:unwind? #t)))))
  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* ((t (cond ((time? timeout) (- (time->seconds timeout)
  200. (time->seconds (current-time))))
  201. ((number? timeout) timeout)
  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. ;; SRFI-18 has this to say:
  212. ;;
  213. ;; When one of the primitives defined in this SRFI raises an exception
  214. ;; defined in this SRFI, the exception handler is called with the same
  215. ;; continuation as the primitive (i.e. it is a tail call to the
  216. ;; exception handler).
  217. ;;
  218. ;; Therefore we use raise-continuable as appropriate.
  219. ;; A unique value.
  220. (define %cancel-sentinel (list 'cancelled))
  221. (define (thread-terminate! thread)
  222. (threads:cancel-thread (thread-prim thread) %cancel-sentinel)
  223. *unspecified*)
  224. ;; A unique value.
  225. (define %timeout-sentinel (list 1))
  226. (define* (thread-join! thread #:optional (timeout %timeout-sentinel)
  227. (timeoutval %timeout-sentinel))
  228. (let* ((t (thread-prim thread))
  229. (v (if (eq? timeout %timeout-sentinel)
  230. (threads:join-thread t)
  231. (threads:join-thread t timeout %timeout-sentinel))))
  232. (cond
  233. ((eq? v %timeout-sentinel)
  234. (if (eq? timeoutval %timeout-sentinel)
  235. (raise-continuable (make-join-timeout-exception))
  236. timeoutval))
  237. ((eq? v %cancel-sentinel)
  238. (raise-continuable (make-terminated-thread-exception)))
  239. ((thread-exception thread) => raise-continuable)
  240. (else v))))
  241. ;; MUTEXES
  242. (define* (make-mutex #:optional name)
  243. (%make-mutex (threads:make-mutex 'allow-external-unlock) name #f #f #f))
  244. (define (mutex-state mutex)
  245. (cond
  246. ((mutex-abandoned? mutex) 'abandoned)
  247. ((mutex-owner mutex))
  248. ((> (threads:mutex-level (mutex-prim mutex)) 0) 'not-owned)
  249. (else 'not-abandoned)))
  250. (define (abandon-mutex! mutex)
  251. (set-mutex-abandoned?! mutex #t)
  252. (threads:unlock-mutex (mutex-prim mutex)))
  253. (define* (mutex-lock! mutex #:optional timeout (thread (current-thread)))
  254. (let ((mutexes (thread-mutexes)))
  255. (when mutexes
  256. (hashq-set! mutexes mutex #t)))
  257. (cond
  258. ((threads:lock-mutex (mutex-prim mutex)
  259. (timeout->absolute-time timeout))
  260. (set-mutex-owner! mutex thread)
  261. (cond
  262. ((mutex-abandoned? mutex)
  263. (set-mutex-abandoned?! mutex #f)
  264. (raise-continuable (make-abandoned-mutex-exception)))
  265. (else #t)))
  266. (else #f)))
  267. (define %unlock-sentinel (list 'unlock))
  268. (define* (mutex-unlock! mutex #:optional (cond-var %unlock-sentinel)
  269. (timeout %unlock-sentinel))
  270. (let ((timeout (timeout->absolute-time timeout)))
  271. (when (mutex-owner mutex)
  272. (set-mutex-owner! mutex #f)
  273. (cond
  274. ((eq? cond-var %unlock-sentinel)
  275. (threads:unlock-mutex (mutex-prim mutex)))
  276. ((eq? timeout %unlock-sentinel)
  277. (threads:wait-condition-variable (condition-variable-prim cond-var)
  278. (mutex-prim mutex))
  279. (threads:unlock-mutex (mutex-prim mutex)))
  280. ((threads:wait-condition-variable (condition-variable-prim cond-var)
  281. (mutex-prim mutex)
  282. timeout)
  283. (threads:unlock-mutex (mutex-prim mutex)))
  284. (else #f)))))
  285. ;; CONDITION VARIABLES
  286. ;; These functions are all pass-thrus to the existing Guile implementations.
  287. (define* (make-condition-variable #:optional name)
  288. (%make-condition-variable (threads:make-condition-variable) name #f))
  289. (define (condition-variable-signal! cond)
  290. (threads:signal-condition-variable (condition-variable-prim cond))
  291. *unspecified*)
  292. (define (condition-variable-broadcast! cond)
  293. (threads:broadcast-condition-variable (condition-variable-prim cond))
  294. *unspecified*)
  295. ;; TIME
  296. (define current-time gettimeofday)
  297. (define (time? obj)
  298. (and (pair? obj)
  299. (let ((co (car obj))) (and (integer? co) (>= co 0)))
  300. (let ((co (cdr obj))) (and (integer? co) (>= co 0)))))
  301. (define (time->seconds time)
  302. (and (check-arg-type time? time "time->seconds")
  303. (+ (car time) (/ (cdr time) 1000000))))
  304. (define (seconds->time x)
  305. (and (check-arg-type number? x "seconds->time")
  306. (let ((fx (truncate x)))
  307. (cons (inexact->exact fx)
  308. (inexact->exact (truncate (* (- x fx) 1000000)))))))
  309. ;; srfi-18.scm ends here