operations.scm 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. ;;; Hoot implementation of Fibers
  2. ;;; Copyright (C) 2024 Igalia, S.L.
  3. ;;;
  4. ;;; Licensed under the Apache License, Version 2.0 (the "License");
  5. ;;; you may not use this file except in compliance with the License.
  6. ;;; You may obtain a copy of the License at
  7. ;;;
  8. ;;; http://www.apache.org/licenses/LICENSE-2.0
  9. ;;;
  10. ;;; Unless required by applicable law or agreed to in writing, software
  11. ;;; distributed under the License is distributed on an "AS IS" BASIS,
  12. ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. ;;; See the License for the specific language governing permissions and
  14. ;;; limitations under the License.
  15. ;;; A port of the Concurrent ML implementation from
  16. ;;; https://github.com/wingo/fibers and
  17. ;;; https://github.com/snabbco/snabb/blob/master/src/lib/fibers/op.lua.
  18. ;;; Unlike the CML in Guile's Fibers, this implementation is not
  19. ;;; parallel, so it can be much more simple, and it relies on a default
  20. ;;; prompt handler being in place instead of an explicit run-fibers.
  21. ;;;
  22. ;;; Unlike the CML in Snabb's fibers, this implementation handles
  23. ;;; multiple values.
  24. ;;;
  25. (define-module (fibers operations)
  26. #:use-module (hoot boxes)
  27. #:use-module (srfi srfi-9)
  28. #:use-module (ice-9 match)
  29. #:use-module (fibers scheduler)
  30. #:export (op-state-completed?
  31. op-state-complete!
  32. wrap-operation
  33. choice-operation
  34. perform-operation
  35. make-base-operation))
  36. ;; Two possible values: #f (waiting), or #t (completed).
  37. (define (make-op-state) (make-box #f))
  38. (define (op-state-completed? state) (box-ref state))
  39. (define (op-state-complete! state)
  40. (let ((prev (op-state-completed? state)))
  41. (box-set! state #t)
  42. (not prev)))
  43. (define-record-type <base-op>
  44. (make-base-operation wrap-fn try-fn block-fn)
  45. base-op?
  46. ;; ((arg ...) -> (result ...)) | #f
  47. (wrap-fn base-op-wrap-fn)
  48. ;; () -> (thunk | #f)
  49. (try-fn base-op-try-fn)
  50. ;; (op-state resume-k) -> ()
  51. (block-fn base-op-block-fn))
  52. (define-record-type <choice-op>
  53. (make-choice-operation base-ops)
  54. choice-op?
  55. (base-ops choice-op-base-ops))
  56. (define (wrap-operation op f)
  57. "Given the operation @var{op}, return a new operation that, if and
  58. when it succeeds, will apply @var{f} to the values yielded by
  59. performing @var{op}, and yield the result as the values of the wrapped
  60. operation."
  61. (match op
  62. (($ <base-op> wrap-fn try-fn block-fn)
  63. (make-base-operation (match wrap-fn
  64. (#f f)
  65. (_ (lambda args
  66. (call-with-values (lambda ()
  67. (apply wrap-fn args))
  68. f))))
  69. try-fn
  70. block-fn))
  71. (($ <choice-op> base-ops)
  72. (let* ((count (vector-length base-ops))
  73. (base-ops* (make-vector count)))
  74. (let lp ((i 0))
  75. (when (< i count)
  76. (vector-set! base-ops* i (wrap-operation (vector-ref base-ops i) f))
  77. (lp (1+ i))))
  78. (make-choice-operation base-ops*)))))
  79. (define (choice-operation . ops)
  80. "Given the operations @var{ops}, return a new operation that if it
  81. succeeds, will succeed with one and only one of the sub-operations
  82. @var{ops}."
  83. (define (flatten ops)
  84. (match ops
  85. (() '())
  86. ((op . ops)
  87. (append (match op
  88. (($ <base-op>) (list op))
  89. (($ <choice-op> base-ops) (vector->list base-ops)))
  90. (flatten ops)))))
  91. (match (flatten ops)
  92. ((base-op) base-op)
  93. (base-ops (make-choice-operation (list->vector base-ops)))))
  94. (define (random n)
  95. ;; FIXME!!!
  96. 0)
  97. (define (perform-operation op)
  98. "Perform the operation @var{op} and return the resulting values. If
  99. the operation cannot complete directly, block until it can complete."
  100. (define (wrap-resume resume wrap-fn)
  101. (if wrap-fn
  102. (lambda (thunk)
  103. (resume (lambda ()
  104. (call-with-values thunk wrap-fn))))
  105. resume))
  106. (define (block resume)
  107. (let ((state (make-op-state)))
  108. (match op
  109. (($ <base-op> wrap-fn try-fn block-fn)
  110. (block-fn state (wrap-resume resume wrap-fn)))
  111. (($ <choice-op> base-ops)
  112. (let lp ((i 0))
  113. (when (< i (vector-length base-ops))
  114. (match (vector-ref base-ops i)
  115. (($ <base-op> wrap-fn try-fn block-fn)
  116. (block-fn state (wrap-resume resume wrap-fn))))
  117. (lp (1+ i))))))))
  118. (define (suspend)
  119. ((suspend-current-task
  120. (lambda (k)
  121. (define (resume thunk)
  122. (schedule-task (lambda () (k thunk))))
  123. (block resume)))))
  124. ;; First, try to sync on an op. If no op syncs, block.
  125. (match op
  126. (($ <base-op> wrap-fn try-fn)
  127. (match (try-fn)
  128. (#f (suspend))
  129. (thunk
  130. (if wrap-fn
  131. (call-with-values thunk wrap-fn)
  132. (thunk)))))
  133. (($ <choice-op> base-ops)
  134. (let* ((count (vector-length base-ops))
  135. (offset (random count)))
  136. (let lp ((i 0))
  137. (if (< i count)
  138. (match (vector-ref base-ops (modulo (+ i offset) count))
  139. (($ <base-op> wrap-fn try-fn)
  140. (match (try-fn)
  141. (#f (lp (1+ i)))
  142. (thunk
  143. (if wrap-fn
  144. (call-with-values thunk wrap-fn)
  145. (thunk))))))
  146. (suspend)))))))