PageRenderTime 60ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/gnu/packages/patches/guile-fibers-wait-for-io-readiness.patch

https://gitlab.com/janneke/guix
Patch | 346 lines | 341 code | 5 blank | 0 comment | 0 complexity | 780d8239a69ebc5944a559fa56230407 MD5 | raw file
  1. Scheme-GNUnet requires the new operations 'wait-until-port-readable-operation'
  2. and 'wait-until-port-readable-operation' for communicating with services.
  3. This patch has been previously submitted at <https://github.com/wingo/fibers/pull/50>,
  4. on Sep 16, 2021. As of Feb 3, 2022, upstream has not responded yet.
  5. diff --git a/Makefile.am b/Makefile.am
  6. index e2db57e..0134255 100644
  7. --- a/Makefile.am
  8. +++ b/Makefile.am
  9. @@ -33,6 +33,7 @@ SOURCES = \
  10. fibers/deque.scm \
  11. fibers/epoll.scm \
  12. fibers/interrupts.scm \
  13. + fibers/io-wakeup.scm \
  14. fibers/nameset.scm \
  15. fibers/operations.scm \
  16. fibers/posix-clocks.scm \
  17. @@ -67,6 +68,7 @@ TESTS = \
  18. tests/conditions.scm \
  19. tests/channels.scm \
  20. tests/foreign.scm \
  21. + tests/io-wakeup.scm \
  22. tests/parameters.scm \
  23. tests/preemption.scm \
  24. tests/speedup.scm
  25. diff --git a/fibers.texi b/fibers.texi
  26. index 52f7177..0990c8f 100644
  27. --- a/fibers.texi
  28. +++ b/fibers.texi
  29. @@ -12,6 +12,7 @@ This manual is for Fibers (version @value{VERSION}, updated
  30. @value{UPDATED})
  31. Copyright 2016-2022 Andy Wingo
  32. +Copyright 2021 Maxime Devos
  33. @quotation
  34. @c For more information, see COPYING.docs in the fibers
  35. @@ -453,6 +454,7 @@ of operations for channels and timers, and an internals interface.
  36. * Channels:: Share memory by communicating.
  37. * Timers:: Operations on time.
  38. * Conditions:: Waiting for simple state changes.
  39. +* Port Readiness:: Waiting until a port is ready for I/O.
  40. * REPL Commands:: Experimenting with Fibers at the console.
  41. * Schedulers and Tasks:: Fibers are built from lower-level primitives.
  42. @end menu
  43. @@ -722,6 +724,28 @@ signalled. Equivalent to @code{(perform-operation (wait-operation
  44. cvar))}.
  45. @end defun
  46. +@node Port Readiness
  47. +@section Port Readiness
  48. +
  49. +These two operations can be used on file ports to wait until
  50. +they are readable or writable. Spurious wake-ups are possible.
  51. +This is complementary to Guile's suspendable ports.
  52. +
  53. +@example
  54. +(use-modules (fibers io-wakeup))
  55. +@end example
  56. +
  57. +@defun wait-until-port-readable-operation port
  58. +Make an operation that will succeed with no values when the input
  59. +port @var{port} becomes readable. For passive sockets, this operation
  60. +succeeds when a connection becomes available.
  61. +@end defun
  62. +
  63. +@defun wait-until-port-writable-operation
  64. +Make an operation that will succeed with no values when the output
  65. +port @var{port} becomes writable.
  66. +@end defun
  67. +
  68. @node REPL Commands
  69. @section REPL Commands
  70. diff --git a/fibers/io-wakeup.scm b/fibers/io-wakeup.scm
  71. new file mode 100644
  72. index 0000000..5df03f1
  73. --- /dev/null
  74. +++ b/fibers/io-wakeup.scm
  75. @@ -0,0 +1,93 @@
  76. +;; Fibers: cooperative, event-driven user-space threads.
  77. +
  78. +;;;; Copyright (C) 2016,2021 Free Software Foundation, Inc.
  79. +;;;; Copyright (C) 2021 Maxime Devos
  80. +;;;;
  81. +;;;; This library is free software; you can redistribute it and/or
  82. +;;;; modify it under the terms of the GNU Lesser General Public
  83. +;;;; License as published by the Free Software Foundation; either
  84. +;;;; version 3 of the License, or (at your option) any later version.
  85. +;;;;
  86. +;;;; This library is distributed in the hope that it will be useful,
  87. +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  88. +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  89. +;;;; Lesser General Public License for more details.
  90. +;;;;
  91. +;;;; You should have received a copy of the GNU Lesser General Public
  92. +;;;; License along with this library; if not, write to the Free Software
  93. +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  94. +;;;;
  95. +
  96. +(define-module (fibers io-wakeup)
  97. + #:use-module (fibers scheduler)
  98. + #:use-module (fibers operations)
  99. + #:use-module (ice-9 atomic)
  100. + #:use-module (ice-9 match)
  101. + #:use-module (ice-9 threads)
  102. + #:use-module (ice-9 ports internal)
  103. + #:export (wait-until-port-readable-operation
  104. + wait-until-port-writable-operation))
  105. +
  106. +(define *poll-sched* (make-atomic-box #f))
  107. +
  108. +(define (poll-sched)
  109. + (or (atomic-box-ref *poll-sched*)
  110. + (let ((sched (make-scheduler)))
  111. + (cond
  112. + ((atomic-box-compare-and-swap! *poll-sched* #f sched))
  113. + (else
  114. + ;; FIXME: Would be nice to clean up this thread at some point.
  115. + (call-with-new-thread
  116. + (lambda ()
  117. + (define (finished?) #f)
  118. + (run-scheduler sched finished?)))
  119. + sched)))))
  120. +
  121. +;; These procedure are subject to spurious wakeups.
  122. +
  123. +(define (readable? port)
  124. + "Test if PORT is writable."
  125. + (match (select (vector port) #() #() 0)
  126. + ((#() #() #()) #f)
  127. + ((#(_) #() #()) #t)))
  128. +
  129. +(define (writable? port)
  130. + "Test if PORT is writable."
  131. + (match (select #() (vector port) #() 0)
  132. + ((#() #() #()) #f)
  133. + ((#() #(_) #()) #t)))
  134. +
  135. +(define (make-wait-operation ready? schedule-when-ready port port-ready-fd this-procedure)
  136. + (make-base-operation #f
  137. + (lambda _
  138. + (and (ready? port) values))
  139. + (lambda (flag sched resume)
  140. + (define (commit)
  141. + (match (atomic-box-compare-and-swap! flag 'W 'S)
  142. + ('W (resume values))
  143. + ('C (commit))
  144. + ('S #f)))
  145. + (if sched
  146. + (schedule-when-ready
  147. + sched (port-ready-fd port) commit)
  148. + (schedule-task
  149. + (poll-sched)
  150. + (lambda ()
  151. + (perform-operation (this-procedure port))
  152. + (commit)))))))
  153. +
  154. +(define (wait-until-port-readable-operation port)
  155. + "Make an operation that will succeed when PORT is readable."
  156. + (unless (input-port? port)
  157. + (error "refusing to wait forever for input on non-input port"))
  158. + (make-wait-operation readable? schedule-task-when-fd-readable port
  159. + port-read-wait-fd
  160. + wait-until-port-readable-operation))
  161. +
  162. +(define (wait-until-port-writable-operation port)
  163. + "Make an operation that will succeed when PORT is writable."
  164. + (unless (output-port? port)
  165. + (error "refusing to wait forever for output on non-output port"))
  166. + (make-wait-operation writable? schedule-task-when-fd-writable port
  167. + port-write-wait-fd
  168. + wait-until-port-writable-operation))
  169. diff --git a/tests/io-wakeup.scm b/tests/io-wakeup.scm
  170. new file mode 100644
  171. index 0000000..c14fa81
  172. --- /dev/null
  173. +++ b/tests/io-wakeup.scm
  174. @@ -0,0 +1,167 @@
  175. +;; Fibers: cooperative, event-driven user-space threads.
  176. +
  177. +;;;; Copyright (C) 2016 Free Software Foundation, Inc.
  178. +;;;; Copyright (C) 2021 Maxime Devos
  179. +;;;;
  180. +;;;; This library is free software; you can redistribute it and/or
  181. +;;;; modify it under the terms of the GNU Lesser General Public
  182. +;;;; License as published by the Free Software Foundation; either
  183. +;;;; version 3 of the License, or (at your option) any later version.
  184. +;;;;
  185. +;;;; This library is distributed in the hope that it will be useful,
  186. +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  187. +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  188. +;;;; Lesser General Public License for more details.
  189. +;;;;
  190. +;;;; You should have received a copy of the GNU Lesser General Public
  191. +;;;; License along with this library; if not, write to the Free Software
  192. +;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  193. +;;;;
  194. +
  195. +(define-module (tests io-wakeup)
  196. + #:use-module (rnrs bytevectors)
  197. + #:use-module (ice-9 control)
  198. + #:use-module (ice-9 suspendable-ports)
  199. + #:use-module (ice-9 binary-ports)
  200. + #:use-module (fibers)
  201. + #:use-module (fibers io-wakeup)
  202. + #:use-module (fibers operations)
  203. + #:use-module (fibers timers))
  204. +
  205. +(define failed? #f)
  206. +
  207. +(define-syntax-rule (assert-equal expected actual)
  208. + (let ((x expected))
  209. + (format #t "assert ~s equal to ~s: " 'actual x)
  210. + (force-output)
  211. + (let ((y actual))
  212. + (cond
  213. + ((equal? x y) (format #t "ok\n"))
  214. + (else
  215. + (format #t "no (got ~s)\n" y)
  216. + (set! failed? #t))))))
  217. +
  218. +(define-syntax-rule (assert-run-fibers-terminates exp)
  219. + (begin
  220. + (format #t "assert run-fibers on ~s terminates: " 'exp)
  221. + (force-output)
  222. + (let ((start (get-internal-real-time)))
  223. + (call-with-values (lambda () (run-fibers (lambda () exp)))
  224. + (lambda vals
  225. + (format #t "ok (~a s)\n" (/ (- (get-internal-real-time) start)
  226. + 1.0 internal-time-units-per-second))
  227. + (apply values vals))))))
  228. +
  229. +(define-syntax-rule (assert-run-fibers-returns (expected ...) exp)
  230. + (begin
  231. + (call-with-values (lambda () (assert-run-fibers-terminates exp))
  232. + (lambda run-fiber-return-vals
  233. + (assert-equal '(expected ...) run-fiber-return-vals)))))
  234. +
  235. +
  236. +;; Note that theoretically, on very slow systems, SECONDS might need
  237. +;; to be increased. However, readable/timeout? and writable/timeout?
  238. +;; call this 5 times in a loop anyways, so the effective timeout is
  239. +;; a fourth of a second, which should be plenty in practice.
  240. +(define* (with-timeout op #:key (seconds 0.05) (wrap values))
  241. + (choice-operation op
  242. + (wrap-operation (sleep-operation seconds) wrap)))
  243. +
  244. +(define* (readable/timeout? port #:key (allowed-spurious 5))
  245. + "Does waiting for readability time-out?
  246. +Allow @var{allowed-spurious} spurious wakeups."
  247. + (or (perform-operation
  248. + (with-timeout
  249. + (wrap-operation (wait-until-port-readable-operation port)
  250. + (lambda () #f))
  251. + #:wrap (lambda () #t)))
  252. + (and (> allowed-spurious 0)
  253. + (readable/timeout? port #:allowed-spurious
  254. + (- allowed-spurious 1)))))
  255. +
  256. +(define* (writable/timeout? port #:key (allowed-spurious 5))
  257. + "Does waiting for writability time-out?
  258. +Allow @var{allowed-spurious} spurious wakeups."
  259. + (or (perform-operation
  260. + (with-timeout
  261. + (wrap-operation (wait-until-port-writable-operation port)
  262. + (lambda () #f))
  263. + #:wrap (lambda () #t)))
  264. + (and (> allowed-spurious 0)
  265. + (writable/timeout? port #:allowed-spurious
  266. + (- allowed-spurious 1)))))
  267. +
  268. +;; Tests:
  269. +;; * wait-until-port-readable-operaton / wait-until-port-writable-operation
  270. +;; blocks if the port isn't ready for input / output.
  271. +;;
  272. +;; This is tested with a pipe (read & write)
  273. +;; and a listening socket (read, or accept in this case).
  274. +;;
  275. +;; Due to the possibility of spurious wakeups,
  276. +;; a limited few spurious wakeups are tolerated.
  277. +;;
  278. +;; * these operations succeed if the port is ready for input / output.
  279. +;;
  280. +;; These are again tested with a pipe and a listening socket
  281. +;;
  282. +;; Blocking is detected with a small time-out.
  283. +
  284. +(define (make-listening-socket)
  285. + (let ((server (socket PF_INET SOCK_DGRAM 0)))
  286. + (bind server AF_INET INADDR_LOOPBACK 0)
  287. + server))
  288. +
  289. +(let ((s (make-listening-socket)))
  290. + (assert-run-fibers-returns (#t)
  291. + (readable/timeout? s))
  292. + (assert-equal #t (readable/timeout? s))
  293. + (close s))
  294. +
  295. +(define (set-nonblocking! sock)
  296. + (let ((flags (fcntl sock F_GETFL)))
  297. + (fcntl sock F_SETFL (logior O_NONBLOCK flags))))
  298. +
  299. +(define-syntax-rule (with-pipes (A B) exp exp* ...)
  300. + (let* ((pipes (pipe))
  301. + (A (car pipes))
  302. + (B (cdr pipes)))
  303. + exp exp* ...
  304. + (close A)
  305. + (close B)))
  306. +
  307. +(with-pipes (A B)
  308. + (setvbuf A 'none)
  309. + (setvbuf B 'none)
  310. + (assert-run-fibers-returns (#t)
  311. + (readable/timeout? A))
  312. + (assert-equal #t (readable/timeout? A))
  313. +
  314. + ;; The buffer is empty, so writability is expected.
  315. + (assert-run-fibers-returns (#f)
  316. + (writable/timeout? B))
  317. + (assert-equal #f (writable/timeout? B))
  318. +
  319. + ;; Fill the buffer
  320. + (set-nonblocking! B)
  321. + (let ((bv (make-bytevector 1024)))
  322. + (let/ec k
  323. + (parameterize ((current-write-waiter k))
  324. + (let loop ()
  325. + (put-bytevector B bv)
  326. + (loop)))))
  327. +
  328. + ;; As the buffer is full, writable/timeout? should return
  329. + ;; #t.
  330. + (assert-run-fibers-returns (#t)
  331. + (writable/timeout? B))
  332. + ;; There's plenty to read now, so readable/timeout? should
  333. + ;; return #f.
  334. + (assert-run-fibers-returns (#f)
  335. + (readable/timeout? A)))
  336. +
  337. +(exit (if failed? 1 0))
  338. +
  339. +;; Local Variables:
  340. +;; eval: (put 'with-pipes 'scheme-indent-function 1)
  341. +;; End: