operations.scm 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. ;; Parallel Concurrent ML for Guile
  2. ;;;; Copyright (C) 2016 Andy Wingo <wingo@pobox.com>
  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. ;;; An implementation of Parallel Concurrent ML following the 2009
  18. ;;; ICFP paper "Parallel Concurrent ML" by John Reppy, Claudio
  19. ;;; V. Russo, and Yingqui Xiao.
  20. ;;;
  21. ;;; This implementation differs from the paper in a few ways:
  22. ;;;
  23. ;;; * Superficially, We use the term "operation" instead of "event".
  24. ;;; We say "wrap-operation" instead of "wrap", "choice-operation"
  25. ;;; instead of "choose", and "perform-operation" instead of "sync".
  26. ;;;
  27. ;;; * For the moment, this is an implementation of "primitive CML"
  28. ;;; only. This may change in the future.
  29. ;;;
  30. ;;; * The continuation handling is a little different; in Manticore
  31. ;;; (or at least in the paper), it appears that suspended threads
  32. ;;; are represented in a quite raw way, whereas in Guile there are
  33. ;;; wrapper <fiber> objects. Likewise unlike in CML, the
  34. ;;; continuations in Fibers are delimited and composable, so things
  35. ;;; are a little different. Suspended computations expect to be
  36. ;;; passed a thunk as the resume value, and that thunk gets invoked
  37. ;;; in the context of the fiber. For this reason we represent
  38. ;;; wrappers explicitly in events, using them to wrap the resume
  39. ;;; thunks. As in the C# implementation, we delay continuation
  40. ;;; creation / fiber suspension until after a failed "doFn" phase.
  41. ;;;
  42. ;;; * In Fibers we do away with the "poll" phase, instead merging it
  43. ;;; with the "try" phase. (Our "try" phase is more like what CML
  44. ;;; calls "do". In Fibers, there is no do; there is only try.)
  45. ;;;
  46. (define-module (fibers operations)
  47. #:use-module (srfi srfi-9)
  48. #:use-module (srfi srfi-9 gnu)
  49. #:use-module (ice-9 atomic)
  50. #:use-module (ice-9 match)
  51. #:use-module ((ice-9 threads)
  52. #:select (current-thread
  53. make-mutex make-condition-variable
  54. lock-mutex unlock-mutex
  55. wait-condition-variable signal-condition-variable))
  56. #:use-module (fibers scheduler)
  57. #:export (wrap-operation
  58. choice-operation
  59. perform-operation
  60. make-base-operation))
  61. ;; Three possible values: W (waiting), C (claimed), or S (synched).
  62. ;; The meanings are as in the Parallel CML paper.
  63. (define-inlinable (make-op-state) (make-atomic-box 'W))
  64. (define-record-type <base-op>
  65. (make-base-operation wrap-fn try-fn block-fn)
  66. base-op?
  67. ;; ((arg ...) -> (result ...)) | #f
  68. (wrap-fn base-op-wrap-fn)
  69. ;; () -> (thunk | #f)
  70. (try-fn base-op-try-fn)
  71. ;; (op-state sched resume-k) -> ()
  72. (block-fn base-op-block-fn))
  73. (define-record-type <choice-op>
  74. (make-choice-operation base-ops)
  75. choice-op?
  76. (base-ops choice-op-base-ops))
  77. (define (wrap-operation op f)
  78. "Given the operation @var{op}, return a new operation that, if and
  79. when it succeeds, will apply @var{f} to the values yielded by
  80. performing @var{op}, and yield the result as the values of the wrapped
  81. operation."
  82. (match op
  83. (($ <base-op> wrap-fn try-fn block-fn)
  84. (make-base-operation (match wrap-fn
  85. (#f f)
  86. (_ (lambda args
  87. (call-with-values (lambda ()
  88. (apply wrap-fn args))
  89. f))))
  90. try-fn
  91. block-fn))
  92. (($ <choice-op> base-ops)
  93. (let* ((count (vector-length base-ops))
  94. (base-ops* (make-vector count)))
  95. (let lp ((i 0))
  96. (when (< i count)
  97. (vector-set! base-ops* i (wrap-operation (vector-ref base-ops i) f))
  98. (lp (1+ i))))
  99. (make-choice-operation base-ops*)))))
  100. (define (choice-operation . ops)
  101. "Given the operations @var{ops}, return a new operation that if it
  102. succeeds, will succeed with one and only one of the sub-operations
  103. @var{ops}."
  104. (define (flatten ops)
  105. (match ops
  106. (() '())
  107. ((op . ops)
  108. (append (match op
  109. (($ <base-op>) (list op))
  110. (($ <choice-op> base-ops) (vector->list base-ops)))
  111. (flatten ops)))))
  112. (match (flatten ops)
  113. ((base-op) base-op)
  114. (base-ops (make-choice-operation (list->vector base-ops)))))
  115. (define (perform-operation op)
  116. "Perform the operation @var{op} and return the resulting values. If
  117. the operation cannot complete directly, block until it can complete."
  118. (define (wrap-resume resume wrap-fn)
  119. (if wrap-fn
  120. (lambda (thunk)
  121. (resume (lambda ()
  122. (call-with-values thunk wrap-fn))))
  123. resume))
  124. (define (block sched resume)
  125. (let ((flag (make-op-state)))
  126. (match op
  127. (($ <base-op> wrap-fn try-fn block-fn)
  128. (block-fn flag sched (wrap-resume resume wrap-fn)))
  129. (($ <choice-op> base-ops)
  130. (let lp ((i 0))
  131. (when (< i (vector-length base-ops))
  132. (match (vector-ref base-ops i)
  133. (($ <base-op> wrap-fn try-fn block-fn)
  134. (block-fn flag sched (wrap-resume resume wrap-fn))))
  135. (lp (1+ i))))))))
  136. (define (suspend)
  137. ;; Two cases. If there is a current fiber, then we suspend the
  138. ;; current fiber and arrange to restart it when the operation
  139. ;; succeeds. Otherwise we block the current thread until the
  140. ;; operation succeeds, to allow for communication between fibers
  141. ;; and foreign threads.
  142. (if (current-scheduler)
  143. ((suspend-current-task
  144. (lambda (sched k)
  145. (define (resume thunk)
  146. (schedule-task sched (lambda () (k thunk))))
  147. (block sched resume))))
  148. (let ((k #f)
  149. (thread (current-thread))
  150. (mutex (make-mutex))
  151. (condvar (make-condition-variable)))
  152. (define (resume thunk)
  153. (cond
  154. ((eq? (current-thread) thread)
  155. (set! k thunk))
  156. (else
  157. (call-with-blocked-asyncs
  158. (lambda ()
  159. (lock-mutex mutex)
  160. (set! k thunk)
  161. (signal-condition-variable condvar)
  162. (unlock-mutex mutex))))))
  163. (lock-mutex mutex)
  164. (block #f resume)
  165. (let lp ()
  166. (cond
  167. (k
  168. (unlock-mutex mutex)
  169. (k))
  170. (else
  171. (wait-condition-variable condvar mutex)
  172. (lp)))))))
  173. ;; First, try to sync on an op. If no op syncs, block.
  174. (match op
  175. (($ <base-op> wrap-fn try-fn)
  176. (match (try-fn)
  177. (#f (suspend))
  178. (thunk
  179. (if wrap-fn
  180. (call-with-values thunk wrap-fn)
  181. (thunk)))))
  182. (($ <choice-op> base-ops)
  183. (let* ((count (vector-length base-ops))
  184. (offset (random count)))
  185. (let lp ((i 0))
  186. (if (< i count)
  187. (match (vector-ref base-ops (modulo (+ i offset) count))
  188. (($ <base-op> wrap-fn try-fn)
  189. (match (try-fn)
  190. (#f (lp (1+ i)))
  191. (thunk
  192. (if wrap-fn
  193. (call-with-values thunk wrap-fn)
  194. (thunk))))))
  195. (suspend)))))))