1 | ;;;; timed-resource.scm |
---|
2 | ;;;; Kon Lovett, Mar '20 |
---|
3 | ;;;; Kon Lovett, Jun '17 |
---|
4 | ;;;; Kon Lovett, Oct '09 |
---|
5 | |
---|
6 | ;; Issues |
---|
7 | ;; |
---|
8 | |
---|
9 | (module timed-resource |
---|
10 | |
---|
11 | (;export |
---|
12 | timed-resource-shutdown? |
---|
13 | default-timed-resource-timeout |
---|
14 | make-timed-resource |
---|
15 | timed-resource? check-timed-resource error-timed-resource |
---|
16 | timed-resource-open? |
---|
17 | timed-resource-name |
---|
18 | call-with-timed-resource |
---|
19 | use-timed-resource |
---|
20 | ;deprecated |
---|
21 | with-timed-resource) |
---|
22 | |
---|
23 | (import scheme) |
---|
24 | (import (chicken base)) |
---|
25 | (import (chicken type)) |
---|
26 | (import (only (chicken string) ->string)) |
---|
27 | (import (only (chicken condition) abort handle-exceptions with-exception-handler)) |
---|
28 | (import (only (srfi 1) delete!)) |
---|
29 | (import (only (srfi 18) make-thread thread-start! thread-join! thread-sleep! |
---|
30 | thread-terminate! thread-signal! make-mutex mutex-name |
---|
31 | terminated-thread-exception? uncaught-exception?)) |
---|
32 | (import (only miscmacros let/cc define-parameter)) |
---|
33 | (import (only record-variants define-record-type-variant)) |
---|
34 | (import (only thread-utils print-exception-error thread-unblock!)) |
---|
35 | (import (only thread-reaper thread-reaper-shutdown? thread-reap!)) |
---|
36 | (import (only synch-object make-synch-with-object synch-with-object?)) |
---|
37 | (import (only synch-dynexn synch-with set!-synch-with record-synch)) |
---|
38 | (import (only synch-open %synch-with %set!-synch-with %record-synch)) |
---|
39 | (import (only type-checks check-procedure check-number check-positive-number |
---|
40 | define-check+error-type)) |
---|
41 | |
---|
42 | ;; |
---|
43 | |
---|
44 | (define-type timed-resource (struct timed-resource)) |
---|
45 | |
---|
46 | (: check-timed-resource (* * #!rest --> timed-resource)) |
---|
47 | (: timed-resource? (* -> boolean : timed-resource)) |
---|
48 | (: timed-resource-name (timed-resource --> *)) |
---|
49 | (: timed-resource-open? (timed-resource -> boolean)) |
---|
50 | (: timed-resource-shutdown? (-> boolean)) |
---|
51 | (: make-timed-resource (procedure procedure number #!optional * -> timed-resource)) |
---|
52 | (: call-with-timed-resource (timed-resource (* -> *) -> *)) |
---|
53 | |
---|
54 | ;; |
---|
55 | |
---|
56 | (define-inline (->boolean x) (and x #t)) |
---|
57 | |
---|
58 | (define-constant DEFAULT-SHUTDOWN-TIMEOUT #f) |
---|
59 | |
---|
60 | (: default-timed-resource-timeout (#!optional (or boolean number) -> (or boolean number))) |
---|
61 | |
---|
62 | (define-parameter default-timed-resource-timeout DEFAULT-SHUTDOWN-TIMEOUT (lambda (x) |
---|
63 | (and x (check-positive-number 'default-timed-resource-timeout x)))) |
---|
64 | |
---|
65 | ;; |
---|
66 | |
---|
67 | (define-constant timed-resource 'timed-resource) ;type tag variable |
---|
68 | (define-record-type-variant timed-resource (unsafe unchecked inline) |
---|
69 | (%make-timed-resource op cl to mtx th it) |
---|
70 | %timed-resource? |
---|
71 | (op @timed-resource-open) |
---|
72 | (cl @timed-resource-close) |
---|
73 | (to timed-resource-timeout) |
---|
74 | (mtx timed-resource-mutex) |
---|
75 | (th timed-resource-thread timed-resource-thread-set!) |
---|
76 | (it timed-resource-item timed-resource-item-set!) ) |
---|
77 | |
---|
78 | ;; |
---|
79 | |
---|
80 | (define-check+error-type timed-resource %timed-resource?) |
---|
81 | |
---|
82 | (define (timed-resource? obj) (%timed-resource? obj)) |
---|
83 | (define (timed-resource-name tr) |
---|
84 | (mutex-name (timed-resource-mutex (check-timed-resource 'timed-resource-name tr))) ) |
---|
85 | (define (timed-resource-open? tr) |
---|
86 | (->boolean (timed-resource-item (check-timed-resource 'timed-resource-open? tr))) ) |
---|
87 | |
---|
88 | ;; Open & close a Resource |
---|
89 | |
---|
90 | (define (timed-resource-close tr) |
---|
91 | (let ((res (timed-resource-item tr))) |
---|
92 | ;Drop the ref just in case the closer blows up |
---|
93 | (timed-resource-item-set! tr #f) |
---|
94 | ((@timed-resource-close tr) res) ) ) |
---|
95 | |
---|
96 | (define (timed-resource-open! tr) |
---|
97 | (timed-resource-item-set! tr ((@timed-resource-open tr))) ) |
---|
98 | |
---|
99 | ;catches & returns exception conditions |
---|
100 | (define (checked-timed-resource-close tr succflag) |
---|
101 | (let/cc return |
---|
102 | (with-exception-handler |
---|
103 | (lambda (exn) (return exn)) |
---|
104 | (lambda () (timed-resource-close tr) succflag))) ) |
---|
105 | |
---|
106 | ;; Set of timed resources |
---|
107 | |
---|
108 | (define *timed-resources* #f) ;The set of timed resource objects |
---|
109 | (define *shutdown?* #f) ;Program shutdown? |
---|
110 | |
---|
111 | (define-inline (*timed-resource-shutdown?) (or *shutdown?* (thread-reaper-shutdown?))) |
---|
112 | |
---|
113 | ;Cleanly shutdown remaining timed-resources. |
---|
114 | ;Cannot use the reaper since it can shutdown before we do! |
---|
115 | |
---|
116 | (define UNBLOCKED-TAG '(timed-resource-unblocked)) |
---|
117 | (define CLOSED-TAG '(timed-resource-closed)) |
---|
118 | |
---|
119 | ;note that the set of timed resource objects is kept only for shutdown |
---|
120 | ;processing. |
---|
121 | (define (shutdown-timed-resources!) |
---|
122 | (set! *shutdown?* #t) |
---|
123 | (when *timed-resources* |
---|
124 | (%synch-with *timed-resources* trs |
---|
125 | ;release every blocked timed-resource and "manually" reap. Note that if |
---|
126 | ;the tr is still in the list then it is not queued by the reaper! |
---|
127 | (for-each |
---|
128 | (lambda (tr) |
---|
129 | (let ((th (timed-resource-thread tr))) |
---|
130 | (thread-unblock! th) |
---|
131 | (thread-signal! th UNBLOCKED-TAG) |
---|
132 | (thread-join! th (default-timed-resource-timeout)) ) ) |
---|
133 | trs) ) ) ) |
---|
134 | |
---|
135 | #; ;UNUSED |
---|
136 | (define (timed-resource-terminate! tr) |
---|
137 | (let ( |
---|
138 | (th (timed-resource-thread tr)) ) |
---|
139 | (thread-terminate! th) |
---|
140 | (when (timed-resource-item tr) |
---|
141 | (let ( |
---|
142 | (res (checked-timed-resource-close tr CLOSED-TAG)) ) |
---|
143 | (unless (eq? CLOSED-TAG res) |
---|
144 | (print-exception-error res) ) ) ) |
---|
145 | (handle-exceptions exn |
---|
146 | (cond |
---|
147 | ((and |
---|
148 | (uncaught-exception? exn) |
---|
149 | (terminated-thread-exception? (uncaught-exception-reason exn))) |
---|
150 | ;expecting so ignore |
---|
151 | (void) ) |
---|
152 | (else |
---|
153 | (print-exception-error exn) ) ) |
---|
154 | (thread-join! th) ) ) ) |
---|
155 | |
---|
156 | (define (setup-timed-resource) |
---|
157 | (set! *timed-resources* (make-synch-with-object '() 'timed-resources)) |
---|
158 | (on-exit shutdown-timed-resources!) ) |
---|
159 | |
---|
160 | (define (add-timed-resource! tr) |
---|
161 | (unless *timed-resources* (setup-timed-resource)) ;done once |
---|
162 | (%set!-synch-with *timed-resources* trs (cons tr trs)) ) |
---|
163 | |
---|
164 | (define (remove-timed-resource! tr) |
---|
165 | (%set!-synch-with *timed-resources* trs (delete! tr trs eq?)) ) |
---|
166 | |
---|
167 | ;; The timer thread |
---|
168 | |
---|
169 | ;returns CLOSED-TAG for success, otherwise an exception object |
---|
170 | (define (release-timed-resource! tr) |
---|
171 | ;(assert (eq? (current-thread) (timed-resource-thread tr))) ;used only by a tr! |
---|
172 | ;(can use weaker synch since close catches exceptions) |
---|
173 | (%record-synch tr timed-resource |
---|
174 | ;(returns any exception conditions or CLOSED-TAG for success) |
---|
175 | (let ( |
---|
176 | (res (checked-timed-resource-close tr CLOSED-TAG)) |
---|
177 | (th (timed-resource-thread tr)) ) |
---|
178 | (cond |
---|
179 | ;when teminating anyway no exceptions but make a note of any problem |
---|
180 | ((*timed-resource-shutdown?) |
---|
181 | (unless (eq? CLOSED-TAG res) |
---|
182 | (print-exception-error res th) ) |
---|
183 | CLOSED-TAG ) |
---|
184 | (else |
---|
185 | (remove-timed-resource! tr) |
---|
186 | (thread-reap! th) |
---|
187 | res ) ) ) ) ) |
---|
188 | |
---|
189 | (define ((make-timed-resource-timer-thunk tr)) |
---|
190 | ;We're active |
---|
191 | (add-timed-resource! tr) |
---|
192 | ;allow "timeout" seconds of access |
---|
193 | (handle-exceptions |
---|
194 | ;as |
---|
195 | exn |
---|
196 | ;with |
---|
197 | (unless (eq? UNBLOCKED-TAG exn) ;early unblock only "handled exception" |
---|
198 | (abort exn) ) |
---|
199 | ;in |
---|
200 | (thread-sleep! (timed-resource-timeout tr)) ) |
---|
201 | ;release the resource but propagate any exceptions |
---|
202 | (let ((res (release-timed-resource! tr))) |
---|
203 | (unless (eq? CLOSED-TAG res) |
---|
204 | (abort res) ) ) ) |
---|
205 | |
---|
206 | (define (start-timed-resource-timer! tr) |
---|
207 | (let ( |
---|
208 | (th |
---|
209 | (make-thread |
---|
210 | (make-timed-resource-timer-thunk tr) |
---|
211 | (timed-resource-name tr))) ) |
---|
212 | (timed-resource-thread-set! tr th) |
---|
213 | (thread-start! th) ) ) |
---|
214 | |
---|
215 | ;; Get a timed resource |
---|
216 | |
---|
217 | (define (timed-resource-start! tr) |
---|
218 | ;open resource before starting the timer thread so the overhead doesn't count |
---|
219 | ;(and any exceptions can propagate in caller's thread) |
---|
220 | (timed-resource-open! tr) |
---|
221 | (start-timed-resource-timer! tr) ) |
---|
222 | |
---|
223 | ;; Get a resource object |
---|
224 | |
---|
225 | (define (timed-resource-aquire tr) |
---|
226 | (or |
---|
227 | ;just return resource if already open |
---|
228 | (timed-resource-item tr) |
---|
229 | ;otherwise begin a timed open of the resource |
---|
230 | (begin |
---|
231 | (timed-resource-start! tr) |
---|
232 | (timed-resource-item tr) ) ) ) |
---|
233 | |
---|
234 | ;(string-append (->string name) "-") |
---|
235 | (define (make-timed-resource-name name) (gensym name)) |
---|
236 | |
---|
237 | ;;; |
---|
238 | |
---|
239 | (define (timed-resource-shutdown?) (*timed-resource-shutdown?)) |
---|
240 | |
---|
241 | (define (make-timed-resource opener closer timeout #!optional (name 'timed-resource)) |
---|
242 | (and |
---|
243 | (not (*timed-resource-shutdown?)) ;Shouldn't be necessary |
---|
244 | (%make-timed-resource |
---|
245 | (check-procedure 'make-timed-resource opener 'open-procedure) |
---|
246 | (check-procedure 'make-timed-resource closer 'close-procedure) |
---|
247 | (check-positive-number 'make-timed-resource timeout 'timeout) |
---|
248 | (make-mutex (make-timed-resource-name name)) |
---|
249 | #f #f) ) ) |
---|
250 | |
---|
251 | (define (call-with-timed-resource tr proc) |
---|
252 | (check-timed-resource 'with-timed-resource tr) |
---|
253 | (check-procedure 'with-timed-resource proc) |
---|
254 | (unless (*timed-resource-shutdown?) ;Shouldn't be necessary |
---|
255 | (record-synch tr timed-resource (proc (timed-resource-aquire tr))) ) ) |
---|
256 | |
---|
257 | (define-syntax use-timed-resource |
---|
258 | (syntax-rules () |
---|
259 | ((use-timed-resource (?tr ?v) ?body ...) |
---|
260 | (call-with-timed-resource ?tr (lambda (?v) ?body ...)) ) ) ) |
---|
261 | |
---|
262 | ;; Deprecations |
---|
263 | |
---|
264 | (: with-timed-resource (deprecated call-with-timed-resource)) |
---|
265 | (define with-timed-resource call-with-timed-resource) |
---|
266 | |
---|
267 | ) ;module timed-resource |
---|