queue.scm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. ; Part of Scheme 48 1.9. See file COPYING for notices and license.
  2. ; Authors: Richard Kelsey, Jonathan Rees, David Frese, Mike Sperber,
  3. ; Robert Ransom, Taylor Campbell
  4. ;;;; Queues
  5. ;;; Following Taylor Campbell's suggestion, the elements in a queue
  6. ;;; are stored in an ordinary list, with a dummy pair at the beginning
  7. ;;; of the list. The queue record maintains pointers to both the
  8. ;;; dummy pair (in the HEAD field) and the last pair in the list (in
  9. ;;; the TAIL field).
  10. ;;;
  11. ;;; With this representation, the only fields that queue operations
  12. ;;; need to mutate are cdrs of pairs and the TAIL field of the queue
  13. ;;; record, and the TAIL field needs to be set if and only if a pair's
  14. ;;; cdr is set to '(). This allows all queue mutations to be
  15. ;;; performed by two procedures, SPLICE-IN-QUEUE-LIST! and
  16. ;;; SPLICE-OUT-OF-QUEUE!, which are simple, easy to use correctly,
  17. ;;; and, when used correctly, maintain the queue's invariant (that the
  18. ;;; TAIL field points to the last pair in the queue's list).
  19. ;;;
  20. ;;; The procedures exported from this module never give away pointers
  21. ;;; to the pairs in a queue's list, and never attach pairs provided by
  22. ;;; other code to a queue's list. Once pairs are in a queue's list,
  23. ;;; their CARs are never modified, so there is no need to use
  24. ;;; PROVISIONAL-CAR. However, pairs' CDRs are (necessarily) modified
  25. ;;; by ENQUEUE! and other queue operations, so all accesses to and
  26. ;;; modifications of CDRs must be provisional.
  27. (define-synchronized-record-type queue :queue
  28. (really-make-queue head tail)
  29. (tail) ;synchronize on these
  30. queue?
  31. ;; Despite their names, the tail's accessor and modifier are both
  32. ;; provisional.
  33. (head real-queue-head)
  34. (tail queue-tail set-queue-tail!))
  35. ;; A few of the comments below use the following utility functions:
  36. ;;
  37. ;; (define (func-exp/1 f n)
  38. ;; (lambda (x)
  39. ;; (do ((x x (f x))
  40. ;; (n n (- n 1)))
  41. ;; ((= n 0) x))))
  42. ;;
  43. ;; (define (nth-cdr n x)
  44. ;; ((func-exp/1 cdr n) x))
  45. ;;
  46. ;; (define (nth-prov-cdr n x)
  47. ;; ((func-exp/1 provisional-cdr n) x))
  48. ;;
  49. ;; (define (prov-cell-push! c x)
  50. ;; (ensure-atomicity!
  51. ;; (provisional-cell-set! c (cons x (provisional-cell-ref c)))))
  52. ;;; Unique IDs and discloser for debugging.
  53. (define *next-queue-list-uid* (make-cell 1))
  54. (define (next-queue-list-uid)
  55. (atomically
  56. (let ((uid (provisional-cell-ref *next-queue-list-uid*)))
  57. (provisional-cell-set! *next-queue-list-uid* (+ uid 1))
  58. uid)))
  59. (define (queue-uid q)
  60. (car (real-queue-head q)))
  61. (define-record-discloser :queue
  62. (lambda (q)
  63. (list 'queue (queue-uid q))))
  64. ;;; Constructors.
  65. ;; MAKE-QUEUE - Create a new, empty queue.
  66. (define (make-queue)
  67. (let ((head (cons (next-queue-list-uid) '())))
  68. (really-make-queue head head)))
  69. ;; LIST->QUEUE - Create a new queue containing a list of elements.
  70. ;;
  71. ;; This does not use the other queue operations because they would add
  72. ;; unnecessary synchronization overhead. Even if this procedure
  73. ;; temporarily set the current proposal to #F, each call to ENQUEUE!
  74. ;; would create and commit a proposal unnecessarily.
  75. (define (list->queue xs)
  76. (call-with-values
  77. (lambda ()
  78. (list->queue-list xs))
  79. really-make-queue))
  80. ;;; Internal utilities.
  81. ;; LIST->QUEUE-LIST - Copies a list, prepending a head pair, and
  82. ;; returns the head pair and the last pair in the copy (or null, if
  83. ;; the original list is empty).
  84. ;;
  85. ;; Throws an exception if XS is an improper list.
  86. (define list->queue-list ;cons-only version
  87. (let ()
  88. (define (loop xs)
  89. (if (null? (cdr xs))
  90. (let ((tail (cons (car xs) '())))
  91. (values tail tail))
  92. (receive (head tail) (loop (cdr xs))
  93. (values (cons (car xs) head) tail))))
  94. (lambda (xs)
  95. (loop (cons (next-queue-list-uid) xs)))))
  96. ;; (define (list->queue-list xs) ;side-effecting version
  97. ;; (let ((result-head (cons (next-queue-list-uid) '())))
  98. ;; (let loop ((xs xs)
  99. ;; (prev-result-pair result-head))
  100. ;; (if (null? xs)
  101. ;; (values result-head prev-result-pair)
  102. ;; (let ((cur-result-pair (cons (car xs) '())))
  103. ;; (set-cdr! prev-result-pair cur-result-pair)
  104. ;; (loop (cdr xs) cur-result-pair))))))
  105. ;; (define (list->queue-list xs) ;alternate cons-only version
  106. ;; (if (null? xs)
  107. ;; (let ((result-head (cons (next-queue-list-uid) '())))
  108. ;; (values result-head result-head))
  109. ;; (receive (head tail)
  110. ;; (let loop ((xs xs))
  111. ;; (if (null? (cdr xs))
  112. ;; (let ((result-tail (cons (car xs) '())))
  113. ;; (values result-tail result-tail))
  114. ;; (receive (head tail) (loop (cdr xs))
  115. ;; (values (cons (car xs) head) tail))))
  116. ;; (values (cons (next-queue-list-uid) head) tail))))
  117. ;; SPLICE-IN-QUEUE-LIST! - Inserts a list into a queue.
  118. ;;
  119. ;; This function must be called with a proposal active. No argument
  120. ;; checking is performed.
  121. ;;
  122. ;; Preconditions:
  123. ;;
  124. ;; - Q must be a queue.
  125. ;;
  126. ;; - (QUEUE-TAIL Q) must be (NTH-PROV-CDR k (REAL-QUEUE-HEAD Q)) for
  127. ;; some exact non-negative integer k.
  128. ;;
  129. ;; - (PROVISIONAL-CDR (QUEUE-TAIL Q)) must be the empty list.
  130. ;;
  131. ;; - PAIR-BEFORE-INSERTION must be a pair.
  132. ;;
  133. ;; - PAIR-BEFORE-INSERTION must be (NTH-PROV-CDR m (REAL-QUEUE-HEAD
  134. ;; Q)) for some exact non-negative integer m.
  135. ;;
  136. ;; - SPLICE-HEAD-PAIR must be a pair.
  137. ;;
  138. ;; - SPLICE-TAIL-PAIR must be a pair.
  139. ;;
  140. ;; - SPLICE-TAIL-PAIR must be (NTH-CDR n SPLICE-HEAD-PAIR) for some
  141. ;; exact non-negative integer n.
  142. ;;
  143. ;; - Each pair reachable as (NTH-CDR i SPLICE-HEAD-PAIR) for some
  144. ;; exact non-negative integer i such that (<= i n) must not have
  145. ;; been accessed or modified provisionally within any active
  146. ;; proposal.
  147. ;;
  148. ;; Postconditions:
  149. ;;
  150. ;; - (QUEUE-TAIL Q) is (NTH-PROV-CDR k2 (REAL-QUEUE-HEAD Q)) for some
  151. ;; exact non-negative integer k2.
  152. ;;
  153. ;; - (PROVISIONAL-CDR (QUEUE-TAIL Q)) is the empty list.
  154. ;;
  155. ;; - (PROVISIONAL-CDR PAIR-BEFORE-INSERTION) is EQ? to (CDR
  156. ;; SPLICE-HEAD-PAIR).
  157. ;;
  158. ;; - (PROVISIONAL-CDR SPLICE-TAIL-PAIR) is EQ? to the value of
  159. ;; (PROVISIONAL-CDR PAIR-BEFORE-INSERTION) when this function was
  160. ;; called.
  161. (define (splice-in-queue-list! q
  162. pair-before-insertion
  163. splice-head-pair
  164. splice-tail-pair)
  165. (if (not (eq? splice-head-pair splice-tail-pair))
  166. (begin
  167. (let ((new-splice-tail-cdr (provisional-cdr pair-before-insertion)))
  168. (set-cdr! splice-tail-pair new-splice-tail-cdr)
  169. (if (null? new-splice-tail-cdr)
  170. (set-queue-tail! q splice-tail-pair)))
  171. (provisional-set-cdr! pair-before-insertion
  172. (cdr splice-head-pair)))))
  173. ;; SPLICE-OUT-OF-QUEUE! - Removes a piece of a queue's list.
  174. ;;
  175. ;; This function must be called with a proposal active. No argument
  176. ;; checking is performed.
  177. ;;
  178. ;; Preconditions:
  179. ;;
  180. ;; - Q must be a queue.
  181. ;;
  182. ;; - (QUEUE-TAIL Q) must be (NTH-PROV-CDR k (REAL-QUEUE-HEAD Q)) for
  183. ;; some exact non-negative integer k.
  184. ;;
  185. ;; - (PROVISIONAL-CDR (QUEUE-TAIL Q)) must be the empty list.
  186. ;;
  187. ;; - SPLICE-HEAD-PAIR must be a pair.
  188. ;;
  189. ;; - SPLICE-HEAD-PAIR must be (NTH-PROV-CDR m (REAL-QUEUE-HEAD Q)) for
  190. ;; some exact non-negative integer m.
  191. ;;
  192. ;; - SPLICE-TAIL-PAIR must be a pair.
  193. ;;
  194. ;; - SPLICE-TAIL-PAIR must be (NTH-PROV-CDR n SPLICE-HEAD-PAIR) for
  195. ;; some exact non-negative integer n.
  196. ;;
  197. ;; Postconditions:
  198. ;;
  199. ;; - (QUEUE-TAIL Q) is (NTH-PROV-CDR k2 (REAL-QUEUE-HEAD Q)) for some
  200. ;; exact non-negative integer k2.
  201. ;;
  202. ;; - (PROVISIONAL-CDR (QUEUE-TAIL Q)) is the empty list.
  203. ;;
  204. ;; - (PROVISIONAL-CDR SPLICE-HEAD-PAIR) is EQ? to the value of
  205. ;; (PROVISIONAL-CDR SPLICE-TAIL-PAIR) when this function was called.
  206. (define (splice-out-of-queue! q
  207. splice-head-pair
  208. splice-tail-pair)
  209. (if (not (eq? splice-head-pair splice-tail-pair))
  210. (let ((splice-tail-cdr (provisional-cdr splice-tail-pair)))
  211. (provisional-set-cdr! splice-head-pair splice-tail-cdr)
  212. (if (null? splice-tail-cdr)
  213. (set-queue-tail! q splice-head-pair)))))
  214. ;; ENQUEUE-MANY-NO-COPY! - Attach a list (provided as (CDR HEAD)) to
  215. ;; the tail of the queue. TAIL must be the last pair in HEAD (since
  216. ;; (CDR HEAD) is a list, HEAD is a non-empty list).
  217. ;;
  218. ;; No argument checking is performed.
  219. (define (enqueue-many-no-copy! q head tail)
  220. (ensure-atomicity!
  221. (splice-in-queue-list! q (queue-tail q) head tail)))
  222. ;; QUEUE-PROC-CALLER-*REALLY*-MESSED-UP! - Removes the current
  223. ;; proposal and raises an error with a rather more useful message than
  224. ;; the fool^H^H^H^Hprogrammer who provoked this error deserves.
  225. (define (queue-proc-caller-*really*-messed-up! who q)
  226. (define the-nasty-message
  227. " called on empty or inconsistent queue with a proposal active")
  228. (remove-current-proposal!)
  229. (assertion-violation who
  230. (string-append (symbol->string who)
  231. the-nasty-message)
  232. q))
  233. ;; MAKE-EMPTY-QUEUE-DIE-THUNK - Adequately described by its name.
  234. (define (make-empty-queue-die-thunk who q)
  235. (lambda ()
  236. (if (proposal-active?)
  237. (queue-proc-caller-*really*-messed-up! who q)
  238. (assertion-violation who "empty queue" q))))
  239. ;; FOO-OR-VALUE->FOO-OR-THUNK/1/0 - Converts a procedure which takes a
  240. ;; default value and returns it on failure to a procedure which takes
  241. ;; a thunk and tail-calls it on failure.
  242. ;;
  243. ;; This procedure should be moved to a utility package and generated
  244. ;; by a macro.
  245. (define foo-or-value->foo-or-thunk/1/0 ;1 arg before VALUE, 0 after
  246. (lambda (foo-or-value)
  247. (let ((unreleased (make-cell 'unreleased)))
  248. (lambda (b/0 thunk)
  249. (let ((result (foo-or-value b/0 unreleased)))
  250. (if (eq? result unreleased)
  251. (thunk)
  252. result))))))
  253. ;;; The exported queue operations.
  254. ;; QUEUE-EMPTY? - Returns #F if the queue is not empty, or #T if the
  255. ;; queue is empty.
  256. (define (queue-empty? q)
  257. ;; ENSURE-ATOMICITY is not necessary here, as this function makes
  258. ;; only one call to a provisional function (PROVISIONAL-CDR).
  259. (null? (provisional-cdr (real-queue-head q))))
  260. ;; ENQUEUE! - Enqueue one element.
  261. (define (enqueue! q v)
  262. ;; ENSURE-ATOMICITY! is not necessary here, as ENQUEUE-MANY-NO-COPY!
  263. ;; uses it for us.
  264. (let ((p (cons v '())))
  265. (enqueue-many-no-copy! q (cons 'dummy p) p)))
  266. ;; ENQUEUE-MANY! - Enqueue a list of elements.
  267. (define (enqueue-many! q xs)
  268. ;; ENSURE-ATOMICITY! is not necessary here, and not using it reduces
  269. ;; the risk of raising an exception (while traversing a
  270. ;; caller-provided value as a list) with a proposal active.
  271. (call-with-values
  272. (lambda ()
  273. (list->queue-list xs))
  274. (lambda (head tail)
  275. (enqueue-many-no-copy! q head tail))))
  276. ;; QUEUE-HEAD-OR-VALUE - Return the first element in the queue, or
  277. ;; return VALUE if the queue is empty.
  278. (define (queue-head-or-value q value)
  279. ;; ENSURE-ATOMICITY is not necessary here.
  280. (let ((first-pair (provisional-cdr (real-queue-head q))))
  281. (if (null? first-pair)
  282. value
  283. (car first-pair))))
  284. ;; QUEUE-HEAD-OR-THUNK - Return the first element in the queue, or
  285. ;; tail-call THUNK if the queue is empty.
  286. ;;
  287. ;; THUNK is tail-called so that, if this function is called without a
  288. ;; proposal active, THUNK will not use the proposal created by this
  289. ;; function. This is especially important if THUNK raises an
  290. ;; exception.
  291. (define queue-head-or-thunk
  292. (foo-or-value->foo-or-thunk/1/0 queue-head-or-value))
  293. ;; QUEUE-HEAD - Return the first element in the queue, or raise an
  294. ;; error if the queue is empty.
  295. ;;
  296. ;; DO NOT CALL THIS FUNCTION WITH A PROPOSAL ACTIVE UNLESS
  297. ;; QUEUE-EMPTY? HAS RETURNED #F!
  298. (define (queue-head q)
  299. (let ((die-thunk (make-empty-queue-die-thunk 'queue-head q)))
  300. (queue-head-or-thunk q die-thunk)))
  301. ;; MAYBE-QUEUE-HEAD - Return the first element in the queue, or return
  302. ;; #F if the queue is empty.
  303. (define (maybe-queue-head q)
  304. (queue-head-or-value q #f))
  305. ;; DEQUEUE-OR-VALUE! - Remove and return the first element in the
  306. ;; queue, or return VALUE if the queue is empty.
  307. (define (dequeue-or-value! q value)
  308. (ensure-atomicity
  309. (let* ((head (real-queue-head q))
  310. (first-pair (provisional-cdr head)))
  311. (if (null? first-pair)
  312. value ;empty queue
  313. (begin
  314. (splice-out-of-queue! q head first-pair)
  315. (car first-pair))))))
  316. ;; DEQUEUE-OR-THUNK! - Remove and return the first element in the
  317. ;; queue, or tail-call THUNK if the queue is empty.
  318. ;;
  319. ;; THUNK is tail-called here for the same reason as it is in
  320. ;; QUEUE-HEAD-OR-THUNK.
  321. (define dequeue-or-thunk!
  322. (foo-or-value->foo-or-thunk/1/0 dequeue-or-value!))
  323. ;; DEQUEUE! - Remove and return the first element in the queue, or
  324. ;; raise an error if the queue is empty.
  325. ;;
  326. ;; DO NOT CALL THIS FUNCTION WITH A PROPOSAL ACTIVE UNLESS
  327. ;; QUEUE-EMPTY? HAS RETURNED #F!
  328. (define (dequeue! q)
  329. (let ((die-thunk (make-empty-queue-die-thunk 'dequeue! q)))
  330. (dequeue-or-thunk! q die-thunk)))
  331. ;; MAYBE-DEQUEUE! - Remove and return the first element in the queue,
  332. ;; or return #F if the queue is empty.
  333. (define (maybe-dequeue! q)
  334. (dequeue-or-value! q #f))
  335. ;; EMPTY-QUEUE! - Make the queue empty.
  336. (define (empty-queue! q)
  337. (ensure-atomicity!
  338. (splice-out-of-queue! q (real-queue-head q) (queue-tail q))))
  339. ;;; Queue operations not used in the Scheme 48 system, and known to be
  340. ;;; *very* slow. These operations may be removed from this package in
  341. ;;; a future revision.
  342. ;; These operations could be made to run faster when called without an
  343. ;; active proposal by locking out all other threads from accessing the
  344. ;; queue and then using non-provisional operations on the queue's
  345. ;; list. This would require another field in the queue record type
  346. ;; and one additional provisional read in each of the queue operations
  347. ;; above. The operations below would still run slowly when called
  348. ;; with a proposal active.
  349. ;; QUEUE->LIST - Return a list of the elements in the queue.
  350. (define (queue->list q)
  351. (ensure-atomicity
  352. (let loop ((qp (provisional-cdr (real-queue-head q))))
  353. (if (null? qp)
  354. '()
  355. ;; The next line must use PROVISIONAL-CDR; see below.
  356. (cons (car qp) (loop (provisional-cdr qp)))))))
  357. ;; If LOOP were applied to (CDR QP) above, the following code would
  358. ;; return a value EQUAL? to '(a):
  359. ;;
  360. ;; (let ((q (make-queue))
  361. ;; (c (make-cell 'OOPS)))
  362. ;; (enqueue! q 'a)
  363. ;; (ensure-atomicity!
  364. ;; (enqueue! q 'b)
  365. ;; (provisional-cell-set! c (queue->list q)))
  366. ;; (cell-ref c))
  367. ;;
  368. ;; The result should be EQUAL? to '(a b).
  369. ;; QUEUE-LENGTH - Return the number of elements in the queue.
  370. ;;
  371. ;; QUEUE-LENGTH could be sped up by having all queue-modifying
  372. ;; operations maintain a count of the number of elements in the queue.
  373. ;; This would make the queue operations which *are* currently used in
  374. ;; the system much slower (e.g. ENQUEUE! currently performs 4 or 5
  375. ;; provisional operations on 2 or 3 locations; maintaining a queue
  376. ;; length counter would require it to perform another provisional read
  377. ;; and write on another location).
  378. (define (queue-length q)
  379. (ensure-atomicity
  380. (let loop ((acc 0)
  381. (qp (provisional-cdr (real-queue-head q))))
  382. (if (null? qp)
  383. acc
  384. ;; The next line must use PROVISIONAL-CDR; see below.
  385. (loop (+ acc 1) (provisional-cdr qp))))))
  386. ;; If LOOP were applied to (CDR QP) above, the following code would
  387. ;; return a value EQUAL? to 1:
  388. ;;
  389. ;; (let ((q (make-queue))
  390. ;; (c (make-cell 'OOPS)))
  391. ;; (enqueue! q 'a)
  392. ;; (ensure-atomicity!
  393. ;; (enqueue! q 'b)
  394. ;; (provisional-cell-set! c (queue-length q)))
  395. ;; (cell-ref c))
  396. ;;
  397. ;; The result should be EQUAL? to 2.
  398. ;; ON-QUEUE? - Returns #T if VALUE is currently in the queue (as
  399. ;; determined by EQV?), and returns #F if VALUE is not in the queue.
  400. (define (on-queue? q value)
  401. (ensure-atomicity
  402. (let loop ((qp (provisional-cdr (real-queue-head q))))
  403. (cond
  404. ((null? qp)
  405. #f)
  406. ((eqv? value (car qp))
  407. #t)
  408. (else
  409. ;; The next line must use PROVISIONAL-CDR; see below.
  410. (loop (provisional-cdr qp)))))))
  411. ;; If LOOP were applied to (CDR QP) above, the following code would
  412. ;; return a value EQUAL? to #f:
  413. ;;
  414. ;; (let ((q (make-queue))
  415. ;; (c (make-cell 'OOPS)))
  416. ;; (enqueue! q 'a)
  417. ;; (ensure-atomicity!
  418. ;; (enqueue! q 'b)
  419. ;; (provisional-cell-set! c (on-queue? q 'b)))
  420. ;; (cell-ref c))
  421. ;;
  422. ;; The result should be EQUAL? to #t.
  423. ;; DELETE-FROM-QUEUE-IF! - INTERNAL - Removes the first element in the
  424. ;; queue satisfying PRED; returns #T if an element is removed, #F
  425. ;; otherwise.
  426. ;;
  427. ;; PRED is called with a proposal active. PRED must not raise an
  428. ;; exception, and should not have side effects.
  429. ;;
  430. ;; Because of these restrictions on PRED and the fact that this
  431. ;; procedure may be removed due to its sloth, DELETE-FROM-QUEUE-IF! is
  432. ;; not exported.
  433. (define (delete-from-queue-if! q pred)
  434. (ensure-atomicity
  435. (let ((head (real-queue-head q)))
  436. (let loop ((prev-pair head)
  437. (cur-pair (provisional-cdr head)))
  438. (cond
  439. ((null? cur-pair)
  440. #f)
  441. ((pred (car cur-pair))
  442. (splice-out-of-queue! q prev-pair cur-pair)
  443. #t)
  444. (else
  445. (loop cur-pair (provisional-cdr cur-pair))))))))
  446. ;; DELETE-FROM-QUEUE! - Removes the first element in the queue EQV? to
  447. ;; VALUE; returns #T if an element is removed, #F otherwise.
  448. (define (delete-from-queue! q value)
  449. (delete-from-queue-if! q (lambda (x) (eqv? value x))))