2024-11-19 18:43:43 +00:00
|
|
|
;;; Guile Knots
|
|
|
|
|
;;; Copyright © 2020 Christopher Baines <mail@cbaines.net>
|
|
|
|
|
;;;
|
|
|
|
|
;;; This file is part of Guile Knots.
|
|
|
|
|
;;;
|
|
|
|
|
;;; The Guile Knots is free software; you can redistribute it and/or
|
|
|
|
|
;;; modify it under the terms of the GNU General Public License as
|
|
|
|
|
;;; published by the Free Software Foundation; either version 3 of the
|
|
|
|
|
;;; License, or (at your option) any later version.
|
|
|
|
|
;;;
|
|
|
|
|
;;; The Guile Knots is distributed in the hope that it will be useful,
|
|
|
|
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
;;; General Public License for more details.
|
|
|
|
|
;;;
|
|
|
|
|
;;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;;; along with the guix-data-service. If not, see
|
|
|
|
|
;;; <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
(define-module (knots resource-pool)
|
|
|
|
|
#:use-module (srfi srfi-1)
|
|
|
|
|
#:use-module (srfi srfi-9)
|
2025-01-06 19:22:50 +00:00
|
|
|
#:use-module (srfi srfi-9 gnu)
|
2025-11-24 21:54:00 +00:00
|
|
|
#:use-module (srfi srfi-43)
|
2025-01-31 12:33:50 +01:00
|
|
|
#:use-module (srfi srfi-71)
|
2025-06-30 22:57:08 +01:00
|
|
|
#:use-module (ice-9 q)
|
2024-11-19 18:43:43 +00:00
|
|
|
#:use-module (ice-9 match)
|
|
|
|
|
#:use-module (ice-9 exceptions)
|
|
|
|
|
#:use-module (fibers)
|
|
|
|
|
#:use-module (fibers timers)
|
|
|
|
|
#:use-module (fibers channels)
|
|
|
|
|
#:use-module (fibers scheduler)
|
|
|
|
|
#:use-module (fibers operations)
|
2025-04-27 10:03:06 +01:00
|
|
|
#:use-module (fibers conditions)
|
2025-02-04 12:52:12 +00:00
|
|
|
#:use-module (knots)
|
2025-01-08 15:57:30 +00:00
|
|
|
#:use-module (knots parallelism)
|
2025-06-25 18:46:46 +02:00
|
|
|
#:export (make-fixed-size-resource-pool
|
2024-11-19 18:43:43 +00:00
|
|
|
make-resource-pool
|
2025-06-25 18:46:46 +02:00
|
|
|
|
|
|
|
|
resource-pool?
|
2025-01-08 12:23:08 +00:00
|
|
|
resource-pool-name
|
2025-01-08 15:57:30 +00:00
|
|
|
resource-pool-channel
|
|
|
|
|
resource-pool-configuration
|
2024-11-19 18:43:43 +00:00
|
|
|
destroy-resource-pool
|
|
|
|
|
|
|
|
|
|
&resource-pool-timeout
|
2025-01-06 19:04:06 +00:00
|
|
|
resource-pool-timeout-error-pool
|
2024-11-19 18:43:43 +00:00
|
|
|
resource-pool-timeout-error?
|
|
|
|
|
|
2025-04-27 09:41:56 +01:00
|
|
|
&resource-pool-too-many-waiters
|
|
|
|
|
resource-pool-too-many-waiters-error-pool
|
|
|
|
|
resource-pool-too-many-waiters-error-waiters-count
|
|
|
|
|
resource-pool-too-many-waiters-error?
|
|
|
|
|
|
|
|
|
|
&resource-pool-destroyed
|
|
|
|
|
resource-pool-destroyed-error-pool
|
|
|
|
|
resource-pool-destroyed-error?
|
|
|
|
|
|
2025-04-28 09:20:33 +01:00
|
|
|
&resource-pool-destroy-resource
|
|
|
|
|
make-resource-pool-destroy-resource-exception
|
|
|
|
|
resource-pool-destroy-resource-exception?
|
|
|
|
|
|
2024-11-19 18:43:43 +00:00
|
|
|
resource-pool-default-timeout-handler
|
|
|
|
|
|
|
|
|
|
call-with-resource-from-pool
|
|
|
|
|
with-resource-from-pool
|
|
|
|
|
|
|
|
|
|
resource-pool-stats))
|
|
|
|
|
|
2025-01-08 15:57:30 +00:00
|
|
|
(define &resource-pool-abort-add-resource
|
|
|
|
|
(make-exception-type '&recource-pool-abort-add-resource
|
|
|
|
|
&error
|
|
|
|
|
'()))
|
|
|
|
|
|
|
|
|
|
(define make-resource-pool-abort-add-resource-error
|
|
|
|
|
(record-constructor &resource-pool-abort-add-resource))
|
|
|
|
|
|
|
|
|
|
(define resource-pool-abort-add-resource-error?
|
2025-06-27 00:16:18 +02:00
|
|
|
(exception-predicate &resource-pool-abort-add-resource))
|
2025-01-08 15:57:30 +00:00
|
|
|
|
2024-11-19 18:43:43 +00:00
|
|
|
(define-record-type <resource-pool>
|
2025-04-27 10:03:06 +01:00
|
|
|
(make-resource-pool-record name channel destroy-condition configuration)
|
2024-11-19 18:43:43 +00:00
|
|
|
resource-pool?
|
2025-04-27 10:03:06 +01:00
|
|
|
(name resource-pool-name)
|
2025-11-24 21:54:00 +00:00
|
|
|
(channel resource-pool-channel
|
|
|
|
|
set-resource-pool-channel!)
|
2025-04-27 10:03:06 +01:00
|
|
|
(destroy-condition resource-pool-destroy-condition)
|
|
|
|
|
(configuration resource-pool-configuration))
|
2026-03-23 11:56:53 +00:00
|
|
|
(set-procedure-property!
|
|
|
|
|
(macro-transformer (module-ref (current-module) 'resource-pool?))
|
|
|
|
|
'documentation
|
|
|
|
|
"Return @code{#t} if OBJ is a @code{<resource-pool>}.")
|
|
|
|
|
(set-procedure-property!
|
|
|
|
|
(macro-transformer (module-ref (current-module) 'resource-pool-name))
|
|
|
|
|
'documentation
|
|
|
|
|
"Return the name of the resource pool.")
|
|
|
|
|
(set-procedure-property!
|
|
|
|
|
(macro-transformer (module-ref (current-module) 'resource-pool-channel))
|
|
|
|
|
'documentation
|
|
|
|
|
"Return the channel used by the resource pool.")
|
|
|
|
|
(set-procedure-property!
|
|
|
|
|
(macro-transformer (module-ref (current-module) 'resource-pool-configuration))
|
|
|
|
|
'documentation
|
|
|
|
|
"Return the configuration alist of the resource pool.")
|
2024-11-19 18:43:43 +00:00
|
|
|
|
2025-01-06 19:22:50 +00:00
|
|
|
(set-record-type-printer!
|
|
|
|
|
<resource-pool>
|
|
|
|
|
(lambda (resource-pool port)
|
2026-01-12 10:00:35 +00:00
|
|
|
(display/knots
|
2025-01-06 19:22:50 +00:00
|
|
|
(simple-format #f "#<resource-pool name: \"~A\">"
|
|
|
|
|
(resource-pool-name resource-pool))
|
|
|
|
|
port)))
|
|
|
|
|
|
2025-11-17 10:46:46 +00:00
|
|
|
(define (safe-deq q)
|
|
|
|
|
(if (null? (car q))
|
|
|
|
|
#f
|
|
|
|
|
(let ((it (caar q))
|
|
|
|
|
(next (cdar q)))
|
|
|
|
|
(if (null? next)
|
|
|
|
|
(set-cdr! q #f))
|
|
|
|
|
(set-car! q next)
|
|
|
|
|
it)))
|
|
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(define-record-type <resource-details>
|
|
|
|
|
(make-resource-details value checkout-count last-used)
|
|
|
|
|
resource-details?
|
|
|
|
|
(value resource-details-value)
|
|
|
|
|
(checkout-count resource-details-checkout-count
|
|
|
|
|
set-resource-details-checkout-count!)
|
|
|
|
|
(last-used resource-details-last-used
|
|
|
|
|
set-resource-details-last-used!))
|
|
|
|
|
|
|
|
|
|
(define-inlinable (increment-resource-checkout-count! resource)
|
|
|
|
|
(set-resource-details-checkout-count!
|
|
|
|
|
resource
|
|
|
|
|
(1+ (resource-details-checkout-count resource))))
|
|
|
|
|
|
|
|
|
|
(define-inlinable (decrement-resource-checkout-count! resource)
|
|
|
|
|
(set-resource-details-checkout-count!
|
|
|
|
|
resource
|
|
|
|
|
(1+ (resource-details-checkout-count resource))))
|
|
|
|
|
|
|
|
|
|
(define (spawn-fiber-for-checkout channel
|
|
|
|
|
reply-channel
|
|
|
|
|
reply-timeout
|
|
|
|
|
resource-id
|
|
|
|
|
resource)
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((checkout-success?
|
|
|
|
|
(perform-operation
|
|
|
|
|
(choice-operation
|
|
|
|
|
(wrap-operation
|
|
|
|
|
(put-operation reply-channel
|
|
|
|
|
(list 'success resource-id resource))
|
|
|
|
|
(const #t))
|
|
|
|
|
(wrap-operation (sleep-operation
|
|
|
|
|
reply-timeout)
|
|
|
|
|
(const #f))))))
|
|
|
|
|
(unless checkout-success?
|
|
|
|
|
(put-message
|
|
|
|
|
channel
|
|
|
|
|
(list 'return-failed-checkout resource-id)))))))
|
|
|
|
|
|
|
|
|
|
(define* (make-fixed-size-resource-pool resources-list-or-vector
|
2025-06-25 18:46:46 +02:00
|
|
|
#:key
|
|
|
|
|
(delay-logger (const #f))
|
|
|
|
|
(duration-logger (const #f))
|
|
|
|
|
scheduler
|
|
|
|
|
(name "unnamed")
|
|
|
|
|
default-checkout-timeout
|
|
|
|
|
default-max-waiters)
|
2026-03-17 21:47:47 +00:00
|
|
|
"Create a resource pool from RESOURCES-LIST-OR-VECTOR, a list or
|
|
|
|
|
vector of pre-existing resource values.
|
|
|
|
|
|
|
|
|
|
Use @code{with-resource-from-pool} or
|
|
|
|
|
@code{call-with-resource-from-pool} to borrow a resource and return it
|
|
|
|
|
automatically when done.
|
|
|
|
|
|
|
|
|
|
Optional keyword arguments:
|
|
|
|
|
|
|
|
|
|
@table @code
|
|
|
|
|
@item #:name
|
|
|
|
|
A optional string used in log messages.
|
|
|
|
|
Defaults to @code{\"unnamed\"}.
|
|
|
|
|
|
|
|
|
|
@item #:default-checkout-timeout
|
|
|
|
|
Default checkout timeout when requesting a resource from the pool,
|
|
|
|
|
unset by default.
|
|
|
|
|
|
|
|
|
|
@item #:default-max-waiters
|
|
|
|
|
Maximum number of fibers that may queue waiting for a resource. When
|
|
|
|
|
this limit is exceeded, @code{&resource-pool-too-many-waiters} is
|
|
|
|
|
raised when a resource is requested. Defaults to @code{#f} (no limit).
|
|
|
|
|
|
|
|
|
|
@item #:scheduler
|
|
|
|
|
The Fibers scheduler to use for the pool's internal fiber. Defaults
|
|
|
|
|
to the current scheduler.
|
|
|
|
|
@end table"
|
2025-06-25 18:46:46 +02:00
|
|
|
(define channel (make-channel))
|
|
|
|
|
(define destroy-condition
|
|
|
|
|
(make-condition))
|
|
|
|
|
|
|
|
|
|
(define pool
|
|
|
|
|
(make-resource-pool-record
|
|
|
|
|
name
|
|
|
|
|
channel
|
|
|
|
|
destroy-condition
|
|
|
|
|
`((delay-logger . ,delay-logger)
|
|
|
|
|
(duration-logger . ,duration-logger)
|
|
|
|
|
(scheduler . ,scheduler)
|
|
|
|
|
(name . ,name)
|
|
|
|
|
(default-checkout-timeout . ,default-checkout-timeout)
|
|
|
|
|
(default-max-waiters . ,default-max-waiters))))
|
|
|
|
|
|
|
|
|
|
(define checkout-failure-count 0)
|
|
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(define resources
|
|
|
|
|
(vector-map
|
|
|
|
|
(lambda (_ resource)
|
|
|
|
|
(make-resource-details
|
|
|
|
|
resource
|
|
|
|
|
0
|
|
|
|
|
#f))
|
|
|
|
|
(if (vector? resources-list-or-vector)
|
|
|
|
|
resources-list-or-vector
|
|
|
|
|
(list->vector resources-list-or-vector))))
|
|
|
|
|
|
|
|
|
|
(define (destroy-loop)
|
|
|
|
|
(define (empty?)
|
|
|
|
|
(vector-every (lambda (r)
|
|
|
|
|
(eq? r #f))
|
|
|
|
|
resources))
|
|
|
|
|
|
|
|
|
|
(let loop ()
|
2025-06-25 18:46:46 +02:00
|
|
|
(match (get-message channel)
|
|
|
|
|
(('checkout reply timeout-time max-waiters)
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((op
|
|
|
|
|
(put-operation
|
|
|
|
|
reply
|
|
|
|
|
(cons 'resource-pool-destroyed
|
|
|
|
|
#f))))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(if timeout-time
|
|
|
|
|
(choice-operation
|
|
|
|
|
op
|
|
|
|
|
(wrap-operation
|
|
|
|
|
(sleep-operation
|
|
|
|
|
(/ (- timeout-time
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
internal-time-units-per-second))
|
|
|
|
|
(const #f)))
|
|
|
|
|
op)))))
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop))
|
2025-06-25 18:46:46 +02:00
|
|
|
(((and (or 'return
|
2025-11-24 21:54:00 +00:00
|
|
|
'return-failed-checkout)
|
2025-06-25 18:46:46 +02:00
|
|
|
return-type)
|
2025-11-24 21:54:00 +00:00
|
|
|
resource-id)
|
|
|
|
|
(vector-set! resources
|
|
|
|
|
resource-id
|
|
|
|
|
#f)
|
|
|
|
|
|
|
|
|
|
(if (empty?)
|
|
|
|
|
(begin
|
|
|
|
|
(set-resource-pool-channel! pool #f)
|
|
|
|
|
(signal-condition! destroy-condition)
|
2025-06-25 18:46:46 +02:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
;; No loop
|
|
|
|
|
*unspecified*)
|
|
|
|
|
(loop)))
|
2025-06-25 18:46:46 +02:00
|
|
|
|
|
|
|
|
(('stats reply timeout-time)
|
|
|
|
|
(let ((stats
|
2025-11-24 21:54:00 +00:00
|
|
|
`((resources . ,(vector-length resources))
|
2025-06-25 18:46:46 +02:00
|
|
|
(available . 0)
|
|
|
|
|
(waiters . 0)
|
|
|
|
|
(checkout-failure-count . ,checkout-failure-count))))
|
|
|
|
|
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((op
|
|
|
|
|
(put-operation reply stats)))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(if timeout-time
|
|
|
|
|
(choice-operation
|
|
|
|
|
op
|
|
|
|
|
(sleep-operation
|
|
|
|
|
(/ (- timeout-time
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
internal-time-units-per-second)))
|
|
|
|
|
op))))))
|
|
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop))
|
2025-06-25 18:46:46 +02:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(('destroy)
|
|
|
|
|
(loop))
|
2025-06-25 18:46:46 +02:00
|
|
|
(unknown
|
|
|
|
|
(simple-format
|
|
|
|
|
(current-error-port)
|
|
|
|
|
"unrecognised message to ~A resource pool channel: ~A\n"
|
|
|
|
|
name
|
|
|
|
|
unknown)
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop)))))
|
2025-06-25 18:46:46 +02:00
|
|
|
|
|
|
|
|
(define (main-loop)
|
2025-11-24 21:54:00 +00:00
|
|
|
(let loop ((available (iota (vector-length resources)))
|
2025-06-30 22:57:08 +01:00
|
|
|
(waiters (make-q)))
|
2025-06-25 18:46:46 +02:00
|
|
|
|
|
|
|
|
(match (get-message channel)
|
|
|
|
|
(('checkout reply timeout-time max-waiters)
|
|
|
|
|
(if (null? available)
|
|
|
|
|
(let ((waiters-count
|
2025-06-30 22:57:08 +01:00
|
|
|
(q-length waiters)))
|
2025-06-25 18:46:46 +02:00
|
|
|
(if (and max-waiters
|
|
|
|
|
(>= waiters-count
|
|
|
|
|
max-waiters))
|
|
|
|
|
(begin
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((op
|
|
|
|
|
(put-operation
|
|
|
|
|
reply
|
|
|
|
|
(cons 'too-many-waiters
|
|
|
|
|
waiters-count))))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(if timeout-time
|
|
|
|
|
(choice-operation
|
|
|
|
|
op
|
|
|
|
|
(wrap-operation
|
|
|
|
|
(sleep-operation
|
|
|
|
|
(/ (- timeout-time
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
internal-time-units-per-second))
|
|
|
|
|
(const #f)))
|
|
|
|
|
op)))))
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop available
|
2025-06-25 18:46:46 +02:00
|
|
|
waiters))
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop available
|
2025-06-30 22:57:08 +01:00
|
|
|
(enq! waiters (cons reply timeout-time)))))
|
2025-06-25 18:46:46 +02:00
|
|
|
|
|
|
|
|
(if timeout-time
|
|
|
|
|
(let ((current-internal-time
|
|
|
|
|
(get-internal-real-time)))
|
|
|
|
|
;; If this client is still waiting
|
|
|
|
|
(if (> timeout-time
|
|
|
|
|
current-internal-time)
|
|
|
|
|
(let ((reply-timeout
|
|
|
|
|
(/ (- timeout-time
|
|
|
|
|
current-internal-time)
|
2025-11-24 21:54:00 +00:00
|
|
|
internal-time-units-per-second))
|
|
|
|
|
(resource-id
|
|
|
|
|
new-available
|
|
|
|
|
(car+cdr available)))
|
2025-06-25 18:46:46 +02:00
|
|
|
|
|
|
|
|
;; Don't sleep in this fiber, so spawn a new
|
|
|
|
|
;; fiber to handle handing over the resource,
|
|
|
|
|
;; and returning it if there's a timeout
|
2025-11-24 21:54:00 +00:00
|
|
|
(spawn-fiber-for-checkout
|
|
|
|
|
channel
|
|
|
|
|
reply
|
|
|
|
|
reply-timeout
|
|
|
|
|
resource-id
|
|
|
|
|
(resource-details-value
|
|
|
|
|
(vector-ref resources
|
|
|
|
|
resource-id)))
|
|
|
|
|
(loop new-available
|
2025-06-25 18:46:46 +02:00
|
|
|
waiters))
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop available
|
2025-06-25 18:46:46 +02:00
|
|
|
waiters)))
|
2025-11-24 21:54:00 +00:00
|
|
|
(let* ((resource-id
|
|
|
|
|
next-available
|
|
|
|
|
(car+cdr available))
|
|
|
|
|
(resource-details
|
|
|
|
|
(vector-ref resources
|
|
|
|
|
resource-id)))
|
|
|
|
|
(put-message reply
|
|
|
|
|
(list 'success
|
|
|
|
|
resource-id
|
|
|
|
|
(resource-details-value
|
|
|
|
|
resource-details)))
|
|
|
|
|
|
|
|
|
|
(loop next-available
|
2025-06-25 18:46:46 +02:00
|
|
|
waiters)))))
|
|
|
|
|
|
|
|
|
|
(((and (or 'return
|
|
|
|
|
'return-failed-checkout)
|
|
|
|
|
return-type)
|
2025-11-24 21:54:00 +00:00
|
|
|
resource-id)
|
2025-06-25 18:46:46 +02:00
|
|
|
|
|
|
|
|
(when (eq? 'return-failed-checkout
|
|
|
|
|
return-type)
|
|
|
|
|
(set! checkout-failure-count
|
|
|
|
|
(+ 1 checkout-failure-count)))
|
|
|
|
|
|
2025-11-17 10:46:46 +00:00
|
|
|
(let ((current-internal-time
|
|
|
|
|
(get-internal-real-time)))
|
|
|
|
|
(let waiter-loop ((waiter (safe-deq waiters)))
|
|
|
|
|
(match waiter
|
|
|
|
|
(#f
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop (cons resource-id available)
|
2025-11-17 10:46:46 +00:00
|
|
|
waiters))
|
|
|
|
|
((reply . timeout)
|
|
|
|
|
(if (and timeout
|
|
|
|
|
(< timeout current-internal-time))
|
|
|
|
|
(waiter-loop (safe-deq waiters))
|
|
|
|
|
(if timeout
|
|
|
|
|
(let ((reply-timeout
|
|
|
|
|
(/ (- timeout
|
|
|
|
|
current-internal-time)
|
|
|
|
|
internal-time-units-per-second)))
|
|
|
|
|
;; Don't sleep in this fiber, so spawn a
|
|
|
|
|
;; new fiber to handle handing over the
|
|
|
|
|
;; resource, and returning it if there's
|
|
|
|
|
;; a timeout
|
2025-11-24 21:54:00 +00:00
|
|
|
(spawn-fiber-for-checkout
|
|
|
|
|
channel
|
|
|
|
|
reply
|
|
|
|
|
reply-timeout
|
|
|
|
|
resource-id
|
|
|
|
|
(resource-details-value
|
|
|
|
|
(vector-ref resources
|
|
|
|
|
resource-id))))
|
|
|
|
|
(put-message reply
|
|
|
|
|
(list 'success
|
|
|
|
|
resource-id
|
|
|
|
|
(resource-details-value
|
|
|
|
|
(vector-ref resources
|
|
|
|
|
resource-id))))))
|
|
|
|
|
(loop available
|
2025-11-17 10:46:46 +00:00
|
|
|
waiters))))))
|
2025-06-25 18:46:46 +02:00
|
|
|
|
|
|
|
|
(('list-resources reply)
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
2025-11-25 14:26:39 +00:00
|
|
|
(put-message reply (vector->list resources))))
|
2025-06-25 18:46:46 +02:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop available
|
2025-06-25 18:46:46 +02:00
|
|
|
waiters))
|
|
|
|
|
|
|
|
|
|
(('stats reply timeout-time)
|
|
|
|
|
(let ((stats
|
2025-11-24 21:54:00 +00:00
|
|
|
`((resources . ,(vector-length resources))
|
2025-06-25 18:46:46 +02:00
|
|
|
(available . ,(length available))
|
2025-06-30 22:57:08 +01:00
|
|
|
(waiters . ,(q-length waiters))
|
2025-06-25 18:46:46 +02:00
|
|
|
(checkout-failure-count . ,checkout-failure-count))))
|
|
|
|
|
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((op
|
|
|
|
|
(put-operation reply stats)))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(if timeout-time
|
|
|
|
|
(choice-operation
|
|
|
|
|
op
|
|
|
|
|
(sleep-operation
|
|
|
|
|
(/ (- timeout-time
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
internal-time-units-per-second)))
|
|
|
|
|
op))))))
|
|
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop available
|
2025-06-25 18:46:46 +02:00
|
|
|
waiters))
|
|
|
|
|
|
|
|
|
|
(('destroy)
|
2025-11-24 21:54:00 +00:00
|
|
|
(let ((current-internal-time (get-internal-real-time)))
|
|
|
|
|
;; Notify all waiters that the pool has been destroyed
|
|
|
|
|
(for-each
|
|
|
|
|
(match-lambda
|
|
|
|
|
((reply . timeout)
|
|
|
|
|
(when (or (not timeout)
|
|
|
|
|
(> timeout current-internal-time))
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((op
|
|
|
|
|
(put-operation
|
|
|
|
|
reply
|
|
|
|
|
(cons 'resource-pool-destroyed
|
|
|
|
|
#f))))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(if timeout
|
|
|
|
|
(choice-operation
|
|
|
|
|
op
|
|
|
|
|
(wrap-operation
|
|
|
|
|
(sleep-operation
|
|
|
|
|
(/ (- timeout
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
internal-time-units-per-second))
|
|
|
|
|
(const #f)))
|
|
|
|
|
op))))))))
|
|
|
|
|
(car waiters))
|
|
|
|
|
|
|
|
|
|
(if (= (vector-length resources)
|
|
|
|
|
(length available))
|
|
|
|
|
(begin
|
|
|
|
|
(set-resource-pool-channel! pool #f)
|
|
|
|
|
(signal-condition! destroy-condition)
|
2025-06-25 18:46:46 +02:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
;; No loop
|
|
|
|
|
*unspecified*)
|
|
|
|
|
(destroy-loop))))
|
2025-06-25 18:46:46 +02:00
|
|
|
|
|
|
|
|
(unknown
|
|
|
|
|
(simple-format
|
|
|
|
|
(current-error-port)
|
|
|
|
|
"unrecognised message to ~A resource pool channel: ~A\n"
|
|
|
|
|
name
|
|
|
|
|
unknown)
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop available
|
2025-06-25 18:46:46 +02:00
|
|
|
waiters)))))
|
|
|
|
|
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
#f)
|
|
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
(let* ((stack (make-stack #t))
|
|
|
|
|
(error-string
|
|
|
|
|
(call-with-output-string
|
|
|
|
|
(lambda (port)
|
|
|
|
|
(display-backtrace stack port 3)
|
|
|
|
|
(simple-format
|
|
|
|
|
port
|
|
|
|
|
"exception in the ~A pool fiber, " name)
|
|
|
|
|
(print-exception
|
|
|
|
|
port
|
|
|
|
|
(stack-ref stack 3)
|
|
|
|
|
'%exception
|
|
|
|
|
(list exn))))))
|
2026-01-12 10:00:35 +00:00
|
|
|
(display/knots error-string
|
|
|
|
|
(current-error-port)))
|
2025-06-25 18:46:46 +02:00
|
|
|
(raise-exception exn))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(start-stack
|
|
|
|
|
#t
|
|
|
|
|
(main-loop)))))
|
|
|
|
|
#:unwind? #t))
|
|
|
|
|
(or scheduler
|
|
|
|
|
(current-scheduler)))
|
|
|
|
|
|
|
|
|
|
pool)
|
|
|
|
|
|
2025-01-08 15:57:30 +00:00
|
|
|
(define* (make-resource-pool return-new-resource max-size
|
2025-02-04 11:13:15 +00:00
|
|
|
#:key (min-size 0)
|
2024-11-19 18:43:43 +00:00
|
|
|
(idle-seconds #f)
|
|
|
|
|
(delay-logger (const #f))
|
|
|
|
|
(duration-logger (const #f))
|
|
|
|
|
destructor
|
|
|
|
|
lifetime
|
|
|
|
|
scheduler
|
|
|
|
|
(name "unnamed")
|
2025-02-04 12:52:12 +00:00
|
|
|
(add-resources-parallelism 1)
|
2025-04-27 09:41:56 +01:00
|
|
|
default-checkout-timeout
|
|
|
|
|
default-max-waiters)
|
2026-03-17 21:47:47 +00:00
|
|
|
"Create a dynamic resource pool. RETURN-NEW-RESOURCE is a thunk
|
|
|
|
|
called to create each new resource value. MAX-SIZE is the maximum
|
|
|
|
|
number of resources the pool will hold simultaneously.
|
|
|
|
|
|
|
|
|
|
Resources are created on demand when a checkout is requested and the
|
|
|
|
|
pool is not yet at MAX-SIZE. Use @code{with-resource-from-pool} or
|
|
|
|
|
@code{call-with-resource-from-pool} to request a resource and return
|
|
|
|
|
it automatically when done.
|
|
|
|
|
|
|
|
|
|
Optional keyword arguments:
|
|
|
|
|
|
|
|
|
|
@table @code
|
|
|
|
|
@item #:min-size
|
|
|
|
|
Minimum number of resources to keep alive even when idle. Defaults to
|
|
|
|
|
@code{0}.
|
|
|
|
|
|
|
|
|
|
@item #:idle-seconds
|
|
|
|
|
Seconds a resource may remain unused before being destroyed, provided
|
|
|
|
|
the pool is above @code{#:min-size}. Defaults to @code{#f} (never
|
|
|
|
|
expire idle resources).
|
|
|
|
|
|
|
|
|
|
@item #:lifetime
|
|
|
|
|
Maximum number of checkouts a single resource will serve before being
|
|
|
|
|
destroyed and replaced by a fresh one. Defaults to @code{#f} (no
|
|
|
|
|
limit).
|
|
|
|
|
|
|
|
|
|
@item #:destructor
|
|
|
|
|
A procedure called as @code{(destructor resource)} when a resource is
|
|
|
|
|
removed from the pool. Defaults to @code{#f}.
|
|
|
|
|
|
|
|
|
|
@item #:add-resources-parallelism
|
|
|
|
|
Maximum number of concurrent calls to RETURN-NEW-RESOURCE when the
|
|
|
|
|
pool needs to grow. Allowing resources to be created in parallel can
|
|
|
|
|
result in more resources being created than can fit inside the pool,
|
|
|
|
|
if this happens, the surplus resources are destroyed. Defaults to
|
|
|
|
|
@code{1}.
|
|
|
|
|
|
|
|
|
|
@item #:name
|
|
|
|
|
A string used in log messages. Defaults to @code{\"unnamed\"}.
|
|
|
|
|
|
|
|
|
|
@item #:default-checkout-timeout
|
|
|
|
|
Default checkout timeout when requesting a resource from the pool,
|
|
|
|
|
unset by default.
|
|
|
|
|
|
|
|
|
|
@item #:default-max-waiters
|
|
|
|
|
Maximum number of fibers that may queue waiting for a resource. When
|
|
|
|
|
this limit is exceeded, @code{&resource-pool-too-many-waiters} is
|
|
|
|
|
raised when a resource is requested. Defaults to @code{#f} (no limit).
|
|
|
|
|
|
|
|
|
|
@item #:scheduler
|
|
|
|
|
The Fibers scheduler to use for the pool's internal fiber. Defaults
|
|
|
|
|
to the current scheduler.
|
|
|
|
|
@end table"
|
2025-01-08 15:57:30 +00:00
|
|
|
(define channel (make-channel))
|
2025-04-27 10:03:06 +01:00
|
|
|
(define destroy-condition
|
|
|
|
|
(make-condition))
|
2025-01-08 15:57:30 +00:00
|
|
|
|
|
|
|
|
(define pool
|
|
|
|
|
(make-resource-pool-record
|
|
|
|
|
name
|
|
|
|
|
channel
|
2025-04-27 10:03:06 +01:00
|
|
|
destroy-condition
|
2025-01-08 15:57:30 +00:00
|
|
|
`((max-size . ,max-size)
|
|
|
|
|
(min-size . ,min-size)
|
|
|
|
|
(idle-seconds . ,idle-seconds)
|
|
|
|
|
(delay-logger . ,delay-logger)
|
|
|
|
|
(duration-logger . ,duration-logger)
|
|
|
|
|
(destructor . ,destructor)
|
|
|
|
|
(lifetime . ,lifetime)
|
|
|
|
|
(scheduler . ,scheduler)
|
|
|
|
|
(name . ,name)
|
2025-04-27 09:41:56 +01:00
|
|
|
(default-checkout-timeout . ,default-checkout-timeout)
|
|
|
|
|
(default-max-waiters . ,default-max-waiters))))
|
2025-01-08 15:57:30 +00:00
|
|
|
|
|
|
|
|
(define checkout-failure-count 0)
|
|
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(define resources
|
|
|
|
|
(make-hash-table))
|
|
|
|
|
|
|
|
|
|
(define-inlinable (count-resources resources)
|
|
|
|
|
(hash-count (const #t) resources))
|
|
|
|
|
|
2025-06-25 18:46:46 +02:00
|
|
|
(define return-new-resource/parallelism-limiter
|
|
|
|
|
(make-parallelism-limiter
|
|
|
|
|
(or add-resources-parallelism
|
|
|
|
|
max-size)
|
|
|
|
|
#:name
|
|
|
|
|
(string-append
|
|
|
|
|
name
|
|
|
|
|
" resource pool new resource parallelism limiter")))
|
|
|
|
|
|
|
|
|
|
(define (spawn-fiber-to-return-new-resource)
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
;; This can happen if the resource pool is destroyed very
|
|
|
|
|
;; quickly
|
2025-11-25 09:58:58 +00:00
|
|
|
(if (resource-pool-destroyed-error? exn)
|
|
|
|
|
#f
|
|
|
|
|
(raise-exception exn)))
|
2025-06-25 18:46:46 +02:00
|
|
|
(lambda ()
|
2026-01-12 10:50:11 +00:00
|
|
|
(let loop ()
|
|
|
|
|
(let ((success?
|
|
|
|
|
(with-parallelism-limiter
|
|
|
|
|
return-new-resource/parallelism-limiter
|
|
|
|
|
(let ((max-size
|
|
|
|
|
(assq-ref (resource-pool-configuration pool)
|
|
|
|
|
'max-size))
|
|
|
|
|
(size (count-resources resources)))
|
|
|
|
|
(or (>= size max-size)
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda _ #f)
|
|
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
(simple-format
|
|
|
|
|
(current-error-port)
|
|
|
|
|
"exception adding resource to pool ~A: ~A\n\n"
|
|
|
|
|
name
|
|
|
|
|
return-new-resource)
|
|
|
|
|
(print-backtrace-and-exception/knots exn)
|
|
|
|
|
(raise-exception exn))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((new-resource
|
|
|
|
|
(start-stack #t (return-new-resource))))
|
|
|
|
|
(put-message channel
|
|
|
|
|
(list 'add-resource new-resource)))
|
|
|
|
|
#t)))
|
|
|
|
|
#:unwind? #t))))))
|
|
|
|
|
(unless success?
|
|
|
|
|
;; TODO Maybe this should be configurable?
|
|
|
|
|
(sleep 1)
|
|
|
|
|
|
|
|
|
|
;; Important to retry here and eventually create
|
|
|
|
|
;; a new resource, as there might be waiters
|
|
|
|
|
;; stuck waiting for a resource, especially if
|
|
|
|
|
;; the pool is empty.
|
|
|
|
|
(loop)))))
|
2025-06-25 18:46:46 +02:00
|
|
|
#:unwind? #t))))
|
2025-01-08 15:57:30 +00:00
|
|
|
|
2025-11-25 09:58:45 +00:00
|
|
|
(define (spawn-fiber-to-destroy-resource resource-id resource-value)
|
2025-01-08 15:57:30 +00:00
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let loop ()
|
2025-11-25 09:58:45 +00:00
|
|
|
(let* ((success?
|
2025-11-24 21:54:00 +00:00
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda _ #f)
|
|
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
(simple-format
|
|
|
|
|
(current-error-port)
|
|
|
|
|
"exception running resource pool destructor (~A): ~A\n"
|
|
|
|
|
name
|
|
|
|
|
destructor)
|
|
|
|
|
(print-backtrace-and-exception/knots exn)
|
|
|
|
|
(raise-exception exn))
|
|
|
|
|
(lambda ()
|
2025-11-25 09:58:45 +00:00
|
|
|
(start-stack #t (destructor resource-value))
|
2025-11-24 21:54:00 +00:00
|
|
|
#t)))
|
|
|
|
|
#:unwind? #t)))
|
2025-01-08 15:57:30 +00:00
|
|
|
|
2025-01-31 12:33:50 +01:00
|
|
|
(if success?
|
|
|
|
|
(put-message channel
|
2025-11-24 21:54:00 +00:00
|
|
|
(list 'remove resource-id))
|
2025-01-31 12:33:50 +01:00
|
|
|
(begin
|
|
|
|
|
(sleep 5)
|
2025-01-08 15:57:30 +00:00
|
|
|
|
2025-01-31 12:33:50 +01:00
|
|
|
(loop))))))))
|
2025-01-08 15:57:30 +00:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(define (destroy-loop resources next-resource-id)
|
|
|
|
|
(let loop ((next-resource-id next-resource-id))
|
2025-04-27 09:41:56 +01:00
|
|
|
(match (get-message channel)
|
|
|
|
|
(('add-resource resource)
|
2025-11-24 21:54:00 +00:00
|
|
|
(if destructor
|
|
|
|
|
(begin
|
|
|
|
|
(spawn-fiber-to-destroy-resource next-resource-id
|
|
|
|
|
resource)
|
|
|
|
|
(hash-set! resources next-resource-id resource)
|
|
|
|
|
|
|
|
|
|
(loop (1+ next-resource-id)))
|
|
|
|
|
(loop next-resource-id)))
|
2025-04-27 09:41:56 +01:00
|
|
|
|
|
|
|
|
(('checkout reply timeout-time max-waiters)
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((op
|
|
|
|
|
(put-operation
|
|
|
|
|
reply
|
|
|
|
|
(cons 'resource-pool-destroyed
|
|
|
|
|
#f))))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(if timeout-time
|
|
|
|
|
(choice-operation
|
|
|
|
|
op
|
|
|
|
|
(wrap-operation
|
|
|
|
|
(sleep-operation
|
|
|
|
|
(/ (- timeout-time
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
internal-time-units-per-second))
|
|
|
|
|
(const #f)))
|
|
|
|
|
op)))))
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop next-resource-id))
|
2025-04-27 09:41:56 +01:00
|
|
|
(((and (or 'return
|
|
|
|
|
'return-failed-checkout
|
|
|
|
|
'remove)
|
|
|
|
|
return-type)
|
2025-11-24 21:54:00 +00:00
|
|
|
resource-id)
|
2025-06-25 18:46:46 +02:00
|
|
|
(when (and (not (eq? return-type 'remove))
|
|
|
|
|
destructor)
|
2025-11-24 21:54:00 +00:00
|
|
|
(spawn-fiber-to-destroy-resource
|
|
|
|
|
resource-id
|
2025-11-25 09:58:45 +00:00
|
|
|
(resource-details-value
|
|
|
|
|
(hash-ref resources resource-id))))
|
2025-06-25 18:46:46 +02:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(hash-remove! resources resource-id)
|
2025-04-27 09:41:56 +01:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(if (= 0 (count-resources resources))
|
|
|
|
|
(begin
|
|
|
|
|
(set-resource-pool-channel! pool #f)
|
|
|
|
|
(signal-condition! destroy-condition)
|
2025-04-27 09:41:56 +01:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
;; No loop
|
|
|
|
|
*unspecified*)
|
|
|
|
|
(loop next-resource-id)))
|
2025-06-25 18:46:46 +02:00
|
|
|
(('stats reply timeout-time)
|
2025-04-27 09:41:56 +01:00
|
|
|
(let ((stats
|
2025-11-24 21:54:00 +00:00
|
|
|
`((resources . ,(count-resources resources))
|
2025-04-27 09:41:56 +01:00
|
|
|
(available . 0)
|
|
|
|
|
(waiters . 0)
|
|
|
|
|
(checkout-failure-count . ,checkout-failure-count))))
|
|
|
|
|
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
2025-06-25 18:46:46 +02:00
|
|
|
(let ((op
|
|
|
|
|
(put-operation reply stats)))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(if timeout-time
|
|
|
|
|
(choice-operation
|
|
|
|
|
op
|
|
|
|
|
(sleep-operation
|
|
|
|
|
(/ (- timeout-time
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
internal-time-units-per-second)))
|
|
|
|
|
op))))))
|
2025-04-27 09:41:56 +01:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop next-resource-id))
|
2025-04-27 09:41:56 +01:00
|
|
|
|
|
|
|
|
(('check-for-idle-resources)
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop next-resource-id))
|
2025-04-27 09:41:56 +01:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(('destroy)
|
|
|
|
|
(loop next-resource-id))
|
2025-04-27 09:41:56 +01:00
|
|
|
(unknown
|
|
|
|
|
(simple-format
|
|
|
|
|
(current-error-port)
|
|
|
|
|
"unrecognised message to ~A resource pool channel: ~A\n"
|
|
|
|
|
name
|
|
|
|
|
unknown)
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop next-resource-id)))))
|
2025-04-27 09:41:56 +01:00
|
|
|
|
2025-02-03 10:30:16 +01:00
|
|
|
(define (main-loop)
|
2025-11-24 21:54:00 +00:00
|
|
|
(let loop ((next-resource-id 0)
|
2025-02-03 10:30:16 +01:00
|
|
|
(available '())
|
2025-11-24 21:54:00 +00:00
|
|
|
(waiters (make-q)))
|
2025-02-03 10:30:16 +01:00
|
|
|
|
|
|
|
|
(match (get-message channel)
|
|
|
|
|
(('add-resource resource)
|
2025-11-24 21:54:00 +00:00
|
|
|
(if (= (count-resources resources) max-size)
|
|
|
|
|
(if destructor
|
|
|
|
|
(begin
|
|
|
|
|
(hash-set! resources
|
|
|
|
|
next-resource-id
|
|
|
|
|
(make-resource-details
|
|
|
|
|
resource
|
|
|
|
|
0
|
|
|
|
|
(get-internal-real-time)))
|
|
|
|
|
(spawn-fiber-to-destroy-resource next-resource-id
|
|
|
|
|
resource)
|
|
|
|
|
|
|
|
|
|
(loop (1+ next-resource-id)
|
2025-02-03 10:30:16 +01:00
|
|
|
available
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters))
|
|
|
|
|
(loop next-resource-id
|
|
|
|
|
available
|
|
|
|
|
waiters))
|
|
|
|
|
|
|
|
|
|
(let* ((current-internal-time
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
(resource-details
|
|
|
|
|
(make-resource-details
|
|
|
|
|
resource
|
|
|
|
|
0
|
|
|
|
|
current-internal-time)))
|
|
|
|
|
(hash-set! resources
|
|
|
|
|
next-resource-id
|
|
|
|
|
resource-details)
|
2025-11-17 10:46:46 +00:00
|
|
|
(let waiter-loop ((waiter (safe-deq waiters)))
|
|
|
|
|
(match waiter
|
|
|
|
|
(#f
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop (1+ next-resource-id)
|
2025-11-25 09:37:29 +00:00
|
|
|
(cons next-resource-id available)
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters))
|
2025-11-17 10:46:46 +00:00
|
|
|
((reply . timeout)
|
|
|
|
|
(if (and timeout
|
|
|
|
|
(< timeout current-internal-time))
|
|
|
|
|
(waiter-loop (safe-deq waiters))
|
|
|
|
|
(if timeout
|
|
|
|
|
(let ((reply-timeout
|
|
|
|
|
(/ (- timeout
|
|
|
|
|
current-internal-time)
|
|
|
|
|
internal-time-units-per-second)))
|
|
|
|
|
;; Don't sleep in this fiber, so spawn a
|
|
|
|
|
;; new fiber to handle handing over the
|
|
|
|
|
;; resource, and returning it if there's
|
|
|
|
|
;; a timeout
|
2025-11-24 21:54:00 +00:00
|
|
|
(spawn-fiber-for-checkout channel
|
|
|
|
|
reply
|
2025-11-17 10:46:46 +00:00
|
|
|
reply-timeout
|
2025-11-24 21:54:00 +00:00
|
|
|
next-resource-id
|
2025-11-17 10:46:46 +00:00
|
|
|
resource))
|
2025-11-24 21:54:00 +00:00
|
|
|
(put-message reply (list 'success
|
|
|
|
|
next-resource-id
|
2025-11-17 10:46:46 +00:00
|
|
|
resource))))
|
2025-11-24 21:54:00 +00:00
|
|
|
(set-resource-details-checkout-count! resource-details
|
|
|
|
|
1)
|
|
|
|
|
(loop (1+ next-resource-id)
|
2025-11-17 10:46:46 +00:00
|
|
|
available
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters)))))))
|
2025-02-04 12:52:12 +00:00
|
|
|
|
2025-04-27 09:41:56 +01:00
|
|
|
(('checkout reply timeout-time max-waiters)
|
2025-02-03 10:30:16 +01:00
|
|
|
(if (null? available)
|
|
|
|
|
(begin
|
2025-11-24 21:54:00 +00:00
|
|
|
(unless (= (count-resources resources) max-size)
|
2025-02-03 10:30:16 +01:00
|
|
|
(spawn-fiber-to-return-new-resource))
|
|
|
|
|
|
2025-04-27 09:41:56 +01:00
|
|
|
(let ((waiters-count
|
2025-06-30 22:57:08 +01:00
|
|
|
(q-length waiters)))
|
2025-04-27 09:41:56 +01:00
|
|
|
(if (and max-waiters
|
|
|
|
|
(>= waiters-count
|
|
|
|
|
max-waiters))
|
|
|
|
|
(begin
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((op
|
|
|
|
|
(put-operation
|
|
|
|
|
reply
|
|
|
|
|
(cons 'too-many-waiters
|
|
|
|
|
waiters-count))))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(if timeout-time
|
|
|
|
|
(choice-operation
|
|
|
|
|
op
|
|
|
|
|
(wrap-operation
|
|
|
|
|
(sleep-operation
|
|
|
|
|
(/ (- timeout-time
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
internal-time-units-per-second))
|
|
|
|
|
(const #f)))
|
|
|
|
|
op)))))
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop next-resource-id
|
2025-04-27 09:41:56 +01:00
|
|
|
available
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters))
|
|
|
|
|
(loop next-resource-id
|
2025-04-27 09:41:56 +01:00
|
|
|
available
|
2025-11-24 21:54:00 +00:00
|
|
|
(enq! waiters (cons reply timeout-time))))))
|
2025-02-03 10:30:16 +01:00
|
|
|
|
2025-02-04 13:12:57 +00:00
|
|
|
(if timeout-time
|
|
|
|
|
(let ((current-internal-time
|
|
|
|
|
(get-internal-real-time)))
|
|
|
|
|
;; If this client is still waiting
|
|
|
|
|
(if (> timeout-time
|
|
|
|
|
current-internal-time)
|
2025-11-24 21:54:00 +00:00
|
|
|
(let* ((reply-timeout
|
|
|
|
|
(/ (- timeout-time
|
|
|
|
|
current-internal-time)
|
|
|
|
|
internal-time-units-per-second))
|
|
|
|
|
(resource-id
|
|
|
|
|
(car available))
|
|
|
|
|
(resource-details
|
|
|
|
|
(hash-ref resources resource-id)))
|
|
|
|
|
|
|
|
|
|
(increment-resource-checkout-count!
|
|
|
|
|
resource-details)
|
2025-02-04 13:12:57 +00:00
|
|
|
|
|
|
|
|
;; Don't sleep in this fiber, so spawn a new
|
|
|
|
|
;; fiber to handle handing over the resource,
|
|
|
|
|
;; and returning it if there's a timeout
|
2025-11-24 21:54:00 +00:00
|
|
|
(spawn-fiber-for-checkout channel
|
|
|
|
|
reply
|
2025-02-04 13:12:57 +00:00
|
|
|
reply-timeout
|
2025-11-24 21:54:00 +00:00
|
|
|
resource-id
|
|
|
|
|
(resource-details-value
|
|
|
|
|
resource-details))
|
|
|
|
|
(loop next-resource-id
|
2025-02-04 13:12:57 +00:00
|
|
|
(cdr available)
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters))
|
|
|
|
|
(loop next-resource-id
|
2025-02-04 13:12:57 +00:00
|
|
|
available
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters)))
|
|
|
|
|
(let* ((resource-id
|
|
|
|
|
next-available
|
|
|
|
|
(car+cdr available))
|
|
|
|
|
(resource-details
|
|
|
|
|
(hash-ref resources
|
|
|
|
|
resource-id)))
|
|
|
|
|
(increment-resource-checkout-count! resource-details)
|
|
|
|
|
|
|
|
|
|
(put-message reply
|
|
|
|
|
(list 'success
|
|
|
|
|
resource-id
|
|
|
|
|
(resource-details-value
|
|
|
|
|
resource-details)))
|
|
|
|
|
|
|
|
|
|
(loop next-resource-id
|
|
|
|
|
next-available
|
|
|
|
|
waiters)))))
|
2025-02-03 10:30:16 +01:00
|
|
|
|
|
|
|
|
(((and (or 'return
|
|
|
|
|
'return-failed-checkout)
|
|
|
|
|
return-type)
|
2025-11-24 21:54:00 +00:00
|
|
|
resource-id)
|
2025-02-03 10:30:16 +01:00
|
|
|
|
|
|
|
|
(when (eq? 'return-failed-checkout
|
|
|
|
|
return-type)
|
|
|
|
|
(set! checkout-failure-count
|
|
|
|
|
(+ 1 checkout-failure-count)))
|
|
|
|
|
|
2025-11-17 10:46:46 +00:00
|
|
|
(let ((current-internal-time
|
|
|
|
|
(get-internal-real-time))
|
2025-11-24 21:54:00 +00:00
|
|
|
(resource-details
|
|
|
|
|
(hash-ref resources resource-id)))
|
2025-11-17 11:20:10 +00:00
|
|
|
(if (and lifetime
|
2025-11-24 21:54:00 +00:00
|
|
|
(>= (resource-details-checkout-count resource-details)
|
2025-11-17 11:20:10 +00:00
|
|
|
lifetime))
|
|
|
|
|
(begin
|
2025-11-24 21:54:00 +00:00
|
|
|
(spawn-fiber-to-destroy-resource resource-id
|
2025-11-25 09:58:45 +00:00
|
|
|
(resource-details-value
|
|
|
|
|
resource-details))
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop next-resource-id
|
2025-11-17 11:20:10 +00:00
|
|
|
available
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters))
|
2025-11-17 11:20:10 +00:00
|
|
|
(let waiter-loop ((waiter (safe-deq waiters)))
|
|
|
|
|
(match waiter
|
|
|
|
|
(#f
|
2025-11-24 21:54:00 +00:00
|
|
|
(if (eq? 'return-failed-checkout
|
|
|
|
|
return-type)
|
|
|
|
|
(decrement-resource-checkout-count! resource-details)
|
|
|
|
|
(set-resource-details-last-used!
|
|
|
|
|
resource-details
|
|
|
|
|
current-internal-time))
|
|
|
|
|
|
|
|
|
|
(loop next-resource-id
|
|
|
|
|
(cons resource-id available)
|
|
|
|
|
waiters))
|
2025-11-17 11:20:10 +00:00
|
|
|
((reply . timeout)
|
|
|
|
|
(if (and timeout
|
|
|
|
|
(< timeout current-internal-time))
|
|
|
|
|
(waiter-loop (safe-deq waiters))
|
|
|
|
|
(if timeout
|
|
|
|
|
(let ((reply-timeout
|
|
|
|
|
(/ (- timeout
|
|
|
|
|
current-internal-time)
|
|
|
|
|
internal-time-units-per-second)))
|
|
|
|
|
;; Don't sleep in this fiber, so spawn a
|
|
|
|
|
;; new fiber to handle handing over the
|
|
|
|
|
;; resource, and returning it if there's
|
|
|
|
|
;; a timeout
|
2025-11-24 21:54:00 +00:00
|
|
|
(spawn-fiber-for-checkout
|
|
|
|
|
channel
|
|
|
|
|
reply
|
|
|
|
|
reply-timeout
|
|
|
|
|
resource-id
|
|
|
|
|
(resource-details-value resource-details)))
|
|
|
|
|
(put-message reply
|
|
|
|
|
(list 'success
|
|
|
|
|
resource-id
|
|
|
|
|
(resource-details-value
|
|
|
|
|
resource-details)))))
|
|
|
|
|
|
|
|
|
|
(set-resource-details-last-used! resource-details
|
|
|
|
|
current-internal-time)
|
|
|
|
|
(when (eq? 'return-failed-checkout
|
|
|
|
|
return-type)
|
|
|
|
|
(decrement-resource-checkout-count! resource-details))
|
|
|
|
|
|
|
|
|
|
(loop next-resource-id
|
2025-11-17 11:20:10 +00:00
|
|
|
available
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters)))))))
|
|
|
|
|
|
|
|
|
|
(('remove resource-id)
|
|
|
|
|
(hash-remove! resources
|
|
|
|
|
resource-id)
|
|
|
|
|
|
|
|
|
|
(when (and (not (q-empty? waiters))
|
|
|
|
|
(< (- (count-resources resources) 1)
|
|
|
|
|
max-size))
|
|
|
|
|
(spawn-fiber-to-return-new-resource))
|
|
|
|
|
|
|
|
|
|
(loop next-resource-id
|
|
|
|
|
available ; resource shouldn't be in this list
|
|
|
|
|
waiters))
|
|
|
|
|
|
|
|
|
|
(('destroy resource-id)
|
|
|
|
|
(let ((resource-details
|
|
|
|
|
(hash-ref resources
|
|
|
|
|
resource-id)))
|
|
|
|
|
(spawn-fiber-to-destroy-resource resource-id
|
2025-11-25 09:58:45 +00:00
|
|
|
(resource-details-value
|
|
|
|
|
resource-details))
|
2025-11-24 21:54:00 +00:00
|
|
|
|
|
|
|
|
(loop next-resource-id
|
|
|
|
|
available
|
|
|
|
|
waiters)))
|
2025-04-28 09:20:33 +01:00
|
|
|
|
2025-04-28 10:08:00 +01:00
|
|
|
(('list-resources reply)
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
2025-11-25 14:26:39 +00:00
|
|
|
(put-message reply (hash-map->list
|
|
|
|
|
(lambda (_ value) value)
|
|
|
|
|
resources))))
|
2025-04-28 10:08:00 +01:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop next-resource-id
|
2025-04-28 10:08:00 +01:00
|
|
|
available
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters))
|
2025-04-28 10:08:00 +01:00
|
|
|
|
2025-06-25 18:46:46 +02:00
|
|
|
(('stats reply timeout-time)
|
2025-02-03 10:30:16 +01:00
|
|
|
(let ((stats
|
2025-11-24 21:54:00 +00:00
|
|
|
`((resources . ,(count-resources resources))
|
2025-02-03 10:30:16 +01:00
|
|
|
(available . ,(length available))
|
2025-06-30 22:57:08 +01:00
|
|
|
(waiters . ,(q-length waiters))
|
2025-11-24 21:54:00 +00:00
|
|
|
(resources-checkout-count
|
|
|
|
|
. ,(hash-fold
|
|
|
|
|
(lambda (_ resource-details result)
|
|
|
|
|
(cons (resource-details-checkout-count
|
|
|
|
|
resource-details)
|
|
|
|
|
result))
|
|
|
|
|
'()
|
|
|
|
|
resources))
|
2025-02-03 10:30:16 +01:00
|
|
|
(checkout-failure-count . ,checkout-failure-count))))
|
|
|
|
|
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
2025-06-25 18:46:46 +02:00
|
|
|
(let ((op
|
|
|
|
|
(put-operation reply stats)))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(if timeout-time
|
|
|
|
|
(choice-operation
|
|
|
|
|
op
|
|
|
|
|
(sleep-operation
|
|
|
|
|
(/ (- timeout-time
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
internal-time-units-per-second)))
|
|
|
|
|
op))))))
|
2025-02-03 10:30:16 +01:00
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop next-resource-id
|
2025-02-03 10:30:16 +01:00
|
|
|
available
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters))
|
2025-02-03 10:30:16 +01:00
|
|
|
|
|
|
|
|
(('check-for-idle-resources)
|
2025-11-24 21:54:00 +00:00
|
|
|
(let* ((internal-real-time
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
(candidate-resource-ids-to-destroy
|
2025-02-03 10:30:16 +01:00
|
|
|
(filter-map
|
2025-11-24 21:54:00 +00:00
|
|
|
(lambda (resource-id)
|
|
|
|
|
(let ((resource-details
|
|
|
|
|
(hash-ref resources resource-id)))
|
|
|
|
|
(if (> (/ (- internal-real-time
|
|
|
|
|
(resource-details-last-used
|
|
|
|
|
resource-details))
|
|
|
|
|
internal-time-units-per-second)
|
|
|
|
|
idle-seconds)
|
|
|
|
|
resource-id
|
|
|
|
|
#f)))
|
|
|
|
|
available))
|
|
|
|
|
(max-resources-to-destroy
|
|
|
|
|
(max 0
|
|
|
|
|
(- (count-resources resources)
|
|
|
|
|
min-size)))
|
|
|
|
|
(resources-to-destroy
|
|
|
|
|
(take candidate-resource-ids-to-destroy
|
|
|
|
|
(min max-resources-to-destroy
|
|
|
|
|
(length candidate-resource-ids-to-destroy)))))
|
|
|
|
|
(when destructor
|
|
|
|
|
(for-each
|
|
|
|
|
(lambda (resource-id)
|
|
|
|
|
(spawn-fiber-to-destroy-resource
|
|
|
|
|
resource-id
|
2025-11-25 09:58:45 +00:00
|
|
|
(resource-details-value
|
|
|
|
|
(hash-ref resources resource-id))))
|
2025-11-24 21:54:00 +00:00
|
|
|
resources-to-destroy))
|
|
|
|
|
|
|
|
|
|
(loop next-resource-id
|
|
|
|
|
(lset-difference = available resources-to-destroy)
|
|
|
|
|
waiters)))
|
2025-02-03 10:30:16 +01:00
|
|
|
|
2025-04-27 10:03:06 +01:00
|
|
|
(('destroy)
|
2025-11-24 21:54:00 +00:00
|
|
|
(let ((current-internal-time (get-internal-real-time)))
|
|
|
|
|
(for-each
|
|
|
|
|
(match-lambda
|
|
|
|
|
((reply . timeout)
|
|
|
|
|
(when (or (not timeout)
|
|
|
|
|
(> timeout current-internal-time))
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(let ((op
|
|
|
|
|
(put-operation
|
|
|
|
|
reply
|
|
|
|
|
(cons 'resource-pool-destroyed
|
|
|
|
|
#f))))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(if timeout
|
|
|
|
|
(choice-operation
|
|
|
|
|
op
|
|
|
|
|
(wrap-operation
|
|
|
|
|
(sleep-operation
|
|
|
|
|
(/ (- timeout
|
|
|
|
|
(get-internal-real-time))
|
|
|
|
|
internal-time-units-per-second))
|
|
|
|
|
(const #f)))
|
|
|
|
|
op))))))))
|
|
|
|
|
(car waiters))
|
|
|
|
|
|
|
|
|
|
(when destructor
|
|
|
|
|
(for-each
|
|
|
|
|
(lambda (resource-id)
|
|
|
|
|
(spawn-fiber-to-destroy-resource
|
|
|
|
|
resource-id
|
2025-11-25 09:58:45 +00:00
|
|
|
(resource-details-value
|
|
|
|
|
(hash-ref resources
|
|
|
|
|
resource-id))))
|
2025-11-24 21:54:00 +00:00
|
|
|
available))
|
|
|
|
|
|
|
|
|
|
;; Do this in parallel to avoid deadlocks between the
|
|
|
|
|
;; limiter and returning new resources to this pool
|
|
|
|
|
(and=> return-new-resource/parallelism-limiter
|
|
|
|
|
(lambda (limiter)
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(destroy-parallelism-limiter limiter)))))
|
|
|
|
|
|
|
|
|
|
(if (or (= 0 (count-resources resources))
|
|
|
|
|
(not destructor))
|
|
|
|
|
(begin
|
|
|
|
|
(set-resource-pool-channel! pool #f)
|
|
|
|
|
(signal-condition! destroy-condition)
|
|
|
|
|
|
|
|
|
|
;; No loop
|
|
|
|
|
*unspecified*)
|
|
|
|
|
(destroy-loop resources next-resource-id))))
|
2025-02-03 10:30:16 +01:00
|
|
|
|
|
|
|
|
(unknown
|
|
|
|
|
(simple-format
|
|
|
|
|
(current-error-port)
|
|
|
|
|
"unrecognised message to ~A resource pool channel: ~A\n"
|
|
|
|
|
name
|
|
|
|
|
unknown)
|
2025-11-24 21:54:00 +00:00
|
|
|
(loop next-resource-id
|
2025-02-03 10:30:16 +01:00
|
|
|
available
|
2025-11-24 21:54:00 +00:00
|
|
|
waiters)))))
|
2025-02-03 10:30:16 +01:00
|
|
|
|
2025-01-08 15:57:30 +00:00
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
|
|
|
|
(when idle-seconds
|
|
|
|
|
(spawn-fiber
|
|
|
|
|
(lambda ()
|
2025-11-24 17:06:27 +00:00
|
|
|
(let loop ()
|
|
|
|
|
(put-message channel '(check-for-idle-resources))
|
2025-11-26 10:06:20 +00:00
|
|
|
(when (perform-operation
|
|
|
|
|
(choice-operation
|
|
|
|
|
(wrap-operation
|
|
|
|
|
(sleep-operation idle-seconds)
|
|
|
|
|
(const #t))
|
|
|
|
|
(wrap-operation
|
|
|
|
|
(wait-operation destroy-condition)
|
|
|
|
|
(const #f))))
|
2025-11-24 17:06:27 +00:00
|
|
|
(loop))))))
|
2025-01-08 15:57:30 +00:00
|
|
|
|
2025-02-03 10:30:16 +01:00
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
#f)
|
2025-01-08 15:57:30 +00:00
|
|
|
(lambda ()
|
2025-02-03 10:30:16 +01:00
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
(let* ((stack (make-stack #t))
|
|
|
|
|
(error-string
|
|
|
|
|
(call-with-output-string
|
|
|
|
|
(lambda (port)
|
2025-02-03 12:16:07 +01:00
|
|
|
(display-backtrace stack port 3)
|
2025-02-03 10:30:16 +01:00
|
|
|
(simple-format
|
|
|
|
|
port
|
|
|
|
|
"exception in the ~A pool fiber, " name)
|
|
|
|
|
(print-exception
|
|
|
|
|
port
|
|
|
|
|
(stack-ref stack 3)
|
|
|
|
|
'%exception
|
2025-02-03 12:16:07 +01:00
|
|
|
(list exn))))))
|
2026-01-12 10:00:35 +00:00
|
|
|
(display/knots error-string
|
|
|
|
|
(current-error-port)))
|
2025-02-03 10:30:16 +01:00
|
|
|
(raise-exception exn))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(start-stack
|
|
|
|
|
#t
|
|
|
|
|
(main-loop)))))
|
|
|
|
|
#:unwind? #t))
|
2025-01-08 15:57:30 +00:00
|
|
|
(or scheduler
|
|
|
|
|
(current-scheduler)))
|
2024-11-19 18:43:43 +00:00
|
|
|
|
2025-01-08 15:57:30 +00:00
|
|
|
pool)
|
2024-11-19 18:43:43 +00:00
|
|
|
|
|
|
|
|
(define (destroy-resource-pool pool)
|
2026-03-18 08:58:41 +00:00
|
|
|
"Destroy POOL, preventing any new checkouts. Blocks until all
|
|
|
|
|
checked-out resources have been returned, running the pool's
|
|
|
|
|
@code{#:destructor} on each. Any fibers waiting for a resource
|
|
|
|
|
receive @code{&resource-pool-destroyed}."
|
2025-04-27 10:03:06 +01:00
|
|
|
(perform-operation
|
|
|
|
|
(choice-operation
|
|
|
|
|
(wrap-operation
|
|
|
|
|
(put-operation (resource-pool-channel pool)
|
|
|
|
|
(list 'destroy))
|
|
|
|
|
(lambda _
|
2025-06-25 18:46:46 +02:00
|
|
|
(wait
|
|
|
|
|
(resource-pool-destroy-condition pool))))
|
2025-04-27 10:03:06 +01:00
|
|
|
(wait-operation
|
|
|
|
|
(resource-pool-destroy-condition pool))))
|
|
|
|
|
#t)
|
2024-11-19 18:43:43 +00:00
|
|
|
|
|
|
|
|
(define &resource-pool-timeout
|
|
|
|
|
(make-exception-type '&recource-pool-timeout
|
|
|
|
|
&error
|
2025-01-06 19:04:06 +00:00
|
|
|
'(pool)))
|
|
|
|
|
|
|
|
|
|
(define resource-pool-timeout-error-pool
|
|
|
|
|
(exception-accessor
|
|
|
|
|
&resource-pool-timeout
|
|
|
|
|
(record-accessor &resource-pool-timeout 'pool)))
|
2026-03-23 11:56:53 +00:00
|
|
|
(set-procedure-property! resource-pool-timeout-error-pool 'documentation
|
|
|
|
|
"Return the pool from a @code{&resource-pool-timeout} exception.")
|
2024-11-19 18:43:43 +00:00
|
|
|
|
|
|
|
|
(define make-resource-pool-timeout-error
|
|
|
|
|
(record-constructor &resource-pool-timeout))
|
|
|
|
|
|
|
|
|
|
(define resource-pool-timeout-error?
|
2025-06-27 00:16:18 +02:00
|
|
|
(exception-predicate &resource-pool-timeout))
|
2026-03-23 11:56:53 +00:00
|
|
|
(set-procedure-property! resource-pool-timeout-error? 'documentation
|
|
|
|
|
"Return @code{#t} if OBJ is a @code{&resource-pool-timeout} exception.")
|
2024-11-19 18:43:43 +00:00
|
|
|
|
2025-04-27 09:41:56 +01:00
|
|
|
(define &resource-pool-too-many-waiters
|
|
|
|
|
(make-exception-type '&recource-pool-too-many-waiters
|
|
|
|
|
&error
|
|
|
|
|
'(pool waiters-count)))
|
|
|
|
|
|
|
|
|
|
(define resource-pool-too-many-waiters-error-pool
|
|
|
|
|
(exception-accessor
|
|
|
|
|
&resource-pool-too-many-waiters
|
|
|
|
|
(record-accessor &resource-pool-too-many-waiters 'pool)))
|
2026-03-23 11:56:53 +00:00
|
|
|
(set-procedure-property! resource-pool-too-many-waiters-error-pool 'documentation
|
|
|
|
|
"Return the pool from a @code{&resource-pool-too-many-waiters} exception.")
|
2025-04-27 09:41:56 +01:00
|
|
|
|
|
|
|
|
(define resource-pool-too-many-waiters-error-waiters-count
|
|
|
|
|
(exception-accessor
|
|
|
|
|
&resource-pool-too-many-waiters
|
|
|
|
|
(record-accessor &resource-pool-too-many-waiters 'waiters-count)))
|
2026-03-23 11:56:53 +00:00
|
|
|
(set-procedure-property! resource-pool-too-many-waiters-error-waiters-count 'documentation
|
|
|
|
|
"Return the waiters count from a @code{&resource-pool-too-many-waiters} exception.")
|
2025-04-27 09:41:56 +01:00
|
|
|
|
|
|
|
|
(define make-resource-pool-too-many-waiters-error
|
|
|
|
|
(record-constructor &resource-pool-too-many-waiters))
|
|
|
|
|
|
|
|
|
|
(define resource-pool-too-many-waiters-error?
|
2025-06-27 00:16:18 +02:00
|
|
|
(exception-predicate &resource-pool-too-many-waiters))
|
2026-03-23 11:56:53 +00:00
|
|
|
(set-procedure-property! resource-pool-too-many-waiters-error? 'documentation
|
|
|
|
|
"Return @code{#t} if OBJ is a @code{&resource-pool-too-many-waiters} exception.")
|
2025-04-27 09:41:56 +01:00
|
|
|
|
|
|
|
|
(define &resource-pool-destroyed
|
|
|
|
|
(make-exception-type '&recource-pool-destroyed
|
|
|
|
|
&error
|
|
|
|
|
'(pool)))
|
|
|
|
|
|
|
|
|
|
(define resource-pool-destroyed-error-pool
|
|
|
|
|
(exception-accessor
|
|
|
|
|
&resource-pool-destroyed
|
|
|
|
|
(record-accessor &resource-pool-destroyed 'pool)))
|
2026-03-23 11:56:53 +00:00
|
|
|
(set-procedure-property! resource-pool-destroyed-error-pool 'documentation
|
|
|
|
|
"Return the pool from a @code{&resource-pool-destroyed} exception.")
|
2025-04-27 09:41:56 +01:00
|
|
|
|
|
|
|
|
(define make-resource-pool-destroyed-error
|
|
|
|
|
(record-constructor &resource-pool-destroyed))
|
|
|
|
|
|
|
|
|
|
(define resource-pool-destroyed-error?
|
2025-06-27 00:16:18 +02:00
|
|
|
(exception-predicate &resource-pool-destroyed))
|
2026-03-23 11:56:53 +00:00
|
|
|
(set-procedure-property! resource-pool-destroyed-error? 'documentation
|
|
|
|
|
"Return @code{#t} if OBJ is a @code{&resource-pool-destroyed} exception.")
|
2025-04-27 09:41:56 +01:00
|
|
|
|
2025-04-28 09:20:33 +01:00
|
|
|
(define &resource-pool-destroy-resource
|
|
|
|
|
(make-exception-type '&recource-pool-destroy-resource
|
|
|
|
|
&exception
|
|
|
|
|
'()))
|
|
|
|
|
|
|
|
|
|
(define make-resource-pool-destroy-resource-exception
|
|
|
|
|
(record-constructor &resource-pool-destroy-resource))
|
2026-03-23 11:56:53 +00:00
|
|
|
(set-procedure-property! make-resource-pool-destroy-resource-exception 'documentation
|
|
|
|
|
"Construct a @code{&resource-pool-destroy-resource} exception.")
|
2025-04-28 09:20:33 +01:00
|
|
|
|
|
|
|
|
(define resource-pool-destroy-resource-exception?
|
2025-06-27 00:16:18 +02:00
|
|
|
(exception-predicate &resource-pool-destroy-resource))
|
2026-03-23 11:56:53 +00:00
|
|
|
(set-procedure-property! resource-pool-destroy-resource-exception? 'documentation
|
|
|
|
|
"Return @code{#t} if OBJ is a @code{&resource-pool-destroy-resource} exception.")
|
2025-04-28 09:20:33 +01:00
|
|
|
|
2024-11-19 18:43:43 +00:00
|
|
|
(define resource-pool-default-timeout-handler
|
|
|
|
|
(make-parameter #f))
|
|
|
|
|
|
|
|
|
|
(define* (call-with-resource-from-pool
|
|
|
|
|
pool proc #:key (timeout 'default)
|
2025-04-27 09:41:56 +01:00
|
|
|
(timeout-handler (resource-pool-default-timeout-handler))
|
2025-04-27 10:52:36 +01:00
|
|
|
(max-waiters 'default)
|
2025-04-28 09:20:33 +01:00
|
|
|
(channel (resource-pool-channel pool))
|
|
|
|
|
(destroy-resource-on-exception? #f))
|
2024-11-19 18:43:43 +00:00
|
|
|
"Call PROC with a resource from POOL, blocking until a resource becomes
|
|
|
|
|
available. Return the resource once PROC has returned."
|
|
|
|
|
|
|
|
|
|
(define timeout-or-default
|
|
|
|
|
(if (eq? timeout 'default)
|
2025-02-04 12:52:12 +00:00
|
|
|
(assq-ref (resource-pool-configuration pool)
|
|
|
|
|
'default-checkout-timeout)
|
2024-11-19 18:43:43 +00:00
|
|
|
timeout))
|
|
|
|
|
|
2025-04-27 09:41:56 +01:00
|
|
|
(define max-waiters-or-default
|
|
|
|
|
(if (eq? max-waiters 'default)
|
|
|
|
|
(assq-ref (resource-pool-configuration pool)
|
|
|
|
|
'default-max-waiters)
|
|
|
|
|
max-waiters))
|
|
|
|
|
|
2025-11-24 21:54:00 +00:00
|
|
|
(unless channel
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-resource-pool-destroyed-error pool)))
|
|
|
|
|
|
2025-04-27 09:41:56 +01:00
|
|
|
(let ((reply
|
2025-02-04 12:52:12 +00:00
|
|
|
(if timeout-or-default
|
|
|
|
|
(let loop ((reply (make-channel))
|
|
|
|
|
(start-time (get-internal-real-time)))
|
|
|
|
|
(let ((request-success?
|
|
|
|
|
(perform-operation
|
|
|
|
|
(choice-operation
|
|
|
|
|
(wrap-operation
|
2025-04-27 10:52:36 +01:00
|
|
|
(put-operation channel
|
2025-02-04 12:52:12 +00:00
|
|
|
(list 'checkout
|
|
|
|
|
reply
|
|
|
|
|
(+ start-time
|
|
|
|
|
(* timeout-or-default
|
2025-04-27 09:41:56 +01:00
|
|
|
internal-time-units-per-second))
|
|
|
|
|
max-waiters-or-default))
|
2025-02-04 12:52:12 +00:00
|
|
|
(const #t))
|
|
|
|
|
(wrap-operation (sleep-operation timeout-or-default)
|
|
|
|
|
(const #f))))))
|
|
|
|
|
(if request-success?
|
|
|
|
|
(let ((time-remaining
|
|
|
|
|
(- timeout-or-default
|
|
|
|
|
(/ (- (get-internal-real-time)
|
|
|
|
|
start-time)
|
|
|
|
|
internal-time-units-per-second))))
|
|
|
|
|
(if (> time-remaining 0)
|
|
|
|
|
(let ((response
|
|
|
|
|
(perform-operation
|
|
|
|
|
(choice-operation
|
|
|
|
|
(get-operation reply)
|
|
|
|
|
(wrap-operation (sleep-operation time-remaining)
|
|
|
|
|
(const #f))))))
|
|
|
|
|
(if (or (not response)
|
|
|
|
|
(eq? response 'resource-pool-retry-checkout))
|
|
|
|
|
(if (> (- timeout-or-default
|
|
|
|
|
(/ (- (get-internal-real-time)
|
|
|
|
|
start-time)
|
|
|
|
|
internal-time-units-per-second))
|
|
|
|
|
0)
|
|
|
|
|
(loop (make-channel)
|
|
|
|
|
start-time)
|
2025-04-27 09:41:56 +01:00
|
|
|
'timeout)
|
2025-02-04 12:52:12 +00:00
|
|
|
response))
|
2025-07-01 23:13:31 +01:00
|
|
|
'timeout))
|
|
|
|
|
'timeout)))
|
|
|
|
|
(let ((reply (make-channel)))
|
2025-04-27 10:52:36 +01:00
|
|
|
(put-message channel
|
2025-02-04 12:52:12 +00:00
|
|
|
(list 'checkout
|
|
|
|
|
reply
|
2025-04-27 09:41:56 +01:00
|
|
|
#f
|
|
|
|
|
max-waiters-or-default))
|
2025-02-04 13:12:57 +00:00
|
|
|
(get-message reply)))))
|
2025-02-04 12:52:12 +00:00
|
|
|
|
2025-04-27 09:41:56 +01:00
|
|
|
(match reply
|
|
|
|
|
('timeout
|
|
|
|
|
(when timeout-handler
|
|
|
|
|
(timeout-handler pool proc timeout))
|
|
|
|
|
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-resource-pool-timeout-error pool)))
|
|
|
|
|
(('too-many-waiters . count)
|
|
|
|
|
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-resource-pool-too-many-waiters-error pool
|
|
|
|
|
count)))
|
|
|
|
|
(('resource-pool-destroyed . #f)
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-resource-pool-destroyed-error pool)))
|
2025-11-24 21:54:00 +00:00
|
|
|
(('success resource-id resource-value)
|
2025-04-27 09:41:56 +01:00
|
|
|
(call-with-values
|
|
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
|
|
|
|
;; Unwind the stack before calling put-message, as
|
|
|
|
|
;; this avoids inconsistent behaviour with
|
|
|
|
|
;; continuation barriers
|
2025-04-28 09:20:33 +01:00
|
|
|
(put-message
|
2025-11-24 21:54:00 +00:00
|
|
|
channel
|
2025-04-28 09:20:33 +01:00
|
|
|
(list (if (or destroy-resource-on-exception?
|
|
|
|
|
(resource-pool-destroy-resource-exception? exn))
|
|
|
|
|
'destroy
|
|
|
|
|
'return)
|
2025-11-24 21:54:00 +00:00
|
|
|
resource-id))
|
2025-06-26 21:27:32 +02:00
|
|
|
(raise-exception exn))
|
2025-04-27 09:41:56 +01:00
|
|
|
(lambda ()
|
|
|
|
|
(with-exception-handler
|
|
|
|
|
(lambda (exn)
|
2025-05-15 09:25:30 +01:00
|
|
|
(let ((stack
|
|
|
|
|
(match (fluid-ref %stacks)
|
|
|
|
|
((stack-tag . prompt-tag)
|
|
|
|
|
(make-stack #t
|
|
|
|
|
0 prompt-tag
|
|
|
|
|
0 (and prompt-tag 1)))
|
|
|
|
|
(_
|
|
|
|
|
(make-stack #t)))))
|
2025-04-27 09:41:56 +01:00
|
|
|
(raise-exception
|
|
|
|
|
(make-exception
|
|
|
|
|
exn
|
2025-05-15 09:25:30 +01:00
|
|
|
(make-knots-exception stack)))))
|
2025-04-27 09:41:56 +01:00
|
|
|
(lambda ()
|
2025-11-24 21:54:00 +00:00
|
|
|
(proc resource-value))))
|
2025-04-27 09:41:56 +01:00
|
|
|
#:unwind? #t))
|
|
|
|
|
(lambda vals
|
2025-11-24 21:54:00 +00:00
|
|
|
(put-message channel
|
|
|
|
|
`(return ,resource-id))
|
2025-04-27 09:41:56 +01:00
|
|
|
(apply values vals)))))))
|
2024-11-19 18:43:43 +00:00
|
|
|
|
|
|
|
|
(define-syntax-rule (with-resource-from-pool pool resource exp ...)
|
2026-03-23 11:56:53 +00:00
|
|
|
"Evaluate EXP ... with RESOURCE bound to a resource checked out from
|
|
|
|
|
POOL. Syntactic sugar around @code{call-with-resource-from-pool}."
|
2024-11-19 18:43:43 +00:00
|
|
|
(call-with-resource-from-pool
|
|
|
|
|
pool
|
|
|
|
|
(lambda (resource) exp ...)))
|
|
|
|
|
|
|
|
|
|
(define* (resource-pool-stats pool #:key (timeout 5))
|
2026-03-18 08:58:41 +00:00
|
|
|
"Return an alist of statistics for POOL with the following keys:
|
|
|
|
|
|
|
|
|
|
@table @code
|
|
|
|
|
@item resources
|
|
|
|
|
Total number of resources currently held by the pool.
|
|
|
|
|
@item available
|
|
|
|
|
Number of resources not currently checked out.
|
|
|
|
|
@item waiters
|
|
|
|
|
Number of fibers currently queued waiting for a resource.
|
|
|
|
|
@item checkout-failure-count
|
|
|
|
|
Cumulative number of checkouts where an exception was raised inside
|
|
|
|
|
the proc.
|
|
|
|
|
@end table
|
|
|
|
|
|
|
|
|
|
Blocks waiting for the pool fiber to respond. @code{#:timeout} is
|
|
|
|
|
the number of seconds to wait; defaults to @code{5}. Raises
|
|
|
|
|
@code{&resource-pool-timeout} if the pool does not respond in time."
|
2025-11-24 21:54:00 +00:00
|
|
|
(define channel
|
|
|
|
|
(resource-pool-channel pool))
|
|
|
|
|
|
|
|
|
|
(unless channel
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-resource-pool-destroyed-error pool)))
|
|
|
|
|
|
2025-06-25 18:46:46 +02:00
|
|
|
(if timeout
|
|
|
|
|
(let* ((reply (make-channel))
|
|
|
|
|
(start-time (get-internal-real-time))
|
|
|
|
|
(timeout-time
|
|
|
|
|
(+ start-time
|
|
|
|
|
(* internal-time-units-per-second timeout))))
|
|
|
|
|
(perform-operation
|
|
|
|
|
(choice-operation
|
|
|
|
|
(wrap-operation
|
2025-11-24 21:54:00 +00:00
|
|
|
(put-operation channel
|
2025-06-25 18:46:46 +02:00
|
|
|
`(stats ,reply ,timeout-time))
|
|
|
|
|
(const #t))
|
|
|
|
|
(wrap-operation (sleep-operation timeout)
|
|
|
|
|
(lambda _
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-resource-pool-timeout-error pool))))))
|
|
|
|
|
|
|
|
|
|
(let ((time-remaining
|
|
|
|
|
(- timeout
|
|
|
|
|
(/ (- (get-internal-real-time)
|
|
|
|
|
start-time)
|
|
|
|
|
internal-time-units-per-second))))
|
|
|
|
|
(if (> time-remaining 0)
|
|
|
|
|
(perform-operation
|
|
|
|
|
(choice-operation
|
|
|
|
|
(get-operation reply)
|
|
|
|
|
(wrap-operation (sleep-operation time-remaining)
|
|
|
|
|
(lambda _
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-resource-pool-timeout-error pool))))))
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-resource-pool-timeout-error pool)))))
|
|
|
|
|
(let ((reply (make-channel)))
|
2025-11-24 21:54:00 +00:00
|
|
|
(put-message channel
|
2025-06-25 18:46:46 +02:00
|
|
|
`(stats ,reply #f))
|
|
|
|
|
(get-message reply))))
|
2024-11-19 18:43:43 +00:00
|
|
|
|
2025-04-28 10:08:00 +01:00
|
|
|
(define (resource-pool-list-resources pool)
|
2025-11-24 21:54:00 +00:00
|
|
|
(define channel
|
|
|
|
|
(resource-pool-channel pool))
|
|
|
|
|
|
|
|
|
|
(unless channel
|
|
|
|
|
(raise-exception
|
|
|
|
|
(make-resource-pool-destroyed-error pool)))
|
|
|
|
|
|
2025-04-28 10:08:00 +01:00
|
|
|
(let ((reply (make-channel)))
|
|
|
|
|
(put-message (resource-pool-channel pool)
|
|
|
|
|
(list 'list-resources reply))
|
|
|
|
|
(get-message reply)))
|