Repository navigation
Expand file tree
/
Copy pathcachequeue.scm
More file actions
297 lines (262 loc) · 9.53 KB
/
Copy pathcachequeue.scm
File metadata and controls
297 lines (262 loc) · 9.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
;;; -*- Mode: Scheme; Character-encoding: utf-8; -*-
;;; Copyright (C) 2005-2020 beingmeta, inc. All rights reserved.
;;; Copyright (C) 2020-2022 Kenneth Haase (ken.haase@alum.mit.edu).
(in-module 'cachequeue)
;;; Cache queues support the queued computation and caching of complex
;;; functions.
;;; cq/request requests an up to date value for a given call, its
;;; consumer argument (if not false) is called on the result when it is ready.
;;; if the consumer argument is false, this queues the request (if neccesary)
;;; and returns any cached value
;;; cq/require gets a value from the cachequeue's cache or, if neccessary,
;;; generates it and stores it in the cache.
(use-module '{ezrecords kno/meltcache fifo logger reflection})
(define-init %loglevel %warning%)
(module-export!
'{cachequeue
cq/get cq/require cq/status cq/probe cq/info cq/meltentry
cachequeue? cqstep cqdaemon
cq/prefetch})
(define meltentry-creation (within-module 'kno/meltcache meltentry-creation))
(define meltentry-expiration (within-module 'kno/meltcache meltentry-expiration))
(define (doconsume method result)
(if (null? method) result
(apply (car method) result (cdr method))))
(define-init cachequeue-default-nthreads 4)
(define-init cachequeue-default-meltpoint #f)
;;; Making cache qeueues
(defrecord (cachequeue #[PREFIX "cq" OPAQUE #t])
cache ; This caches computed values
fifo ; This is the FIFO of pending compute requests
;; State for active tasks
lock ; A lock/condvar for controlling modifications to the state
pending ; This is a table mapping pending tasks to state information
; the data is either a timestamp (time queued), a pair of
; timestamps (time queued/time started), or an error (when ending
; with an error)
;; Various configuration information
meltpoint ; If not #f, the cache is a meltcache with this meltpoint
props ; These are miscellaneous properties of the object
)
#|
(module-export!
'{cq-cache cq-method cq-fifo cq-cache
cq-compute cq-consumers
cq-meltpoint cq-props})
|#
;; This maps cqs to the threads listening to them
;; Warning! This could introduce hard-to-gc circularities
;; (We're not using this right now)
(define cqthreads (make-hashtable))
;;; Constructing cachequeues
(define (cachequeue (cache (make-hashtable))
(nthreads cachequeue-default-nthreads)
(meltpoint cachequeue-default-meltpoint))
"Create a cachequeue which allows for scheduled cached computations. \
CACHE is the results cache, NTHREADS is the number of threads to start \
for processing requests, and MELTPOINT indicates whether the cache \
is a meltcache."
(when (string? cache)
(unless (or (position #\@ cache) (file-exists? cache))
(make-index cache #[type hashindex slots 1000]))
(set! cache (open-index cache)))
(let ((cq (cons-cachequeue
cache (make-fifo) (make-condvar)
(make-hashtable) meltpoint (frame-create #f))))
(when nthreads
(dotimes (i nthreads)
(add! cqthreads cq (thread/call cqdaemon cq i #f))))
cq))
;;; Standard API
(define (cq/get cq fn . args)
(cachequeuerequest
cq (if (and (pair? fn) (null? args)) fn (cons fn args))))
(define (cq/require cq fn . args)
(cachequeuerequire
cq (if (and (pair? fn) (null? args)) fn (cons fn args))))
(define (cq/status cq fn . args)
(cqstatus cq (if (and (pair? fn) (null? args)) fn (cons fn args))))
(define (cq/probe cq fn . args)
(let* ((call (if (and (pair? fn) (null? args)) fn (cons fn args)))
(cache (cq-cache cq))
(cachekey (if (index? cache)
(cons (procedure-id (car call)) (cdr call))
call))
(cachevalue (get cache cachekey)))
(meltvalue cachevalue)))
(define (cq/meltentry cq fn . args)
(cachequeuerequest
cq (if (and (pair? fn) (null? args)) fn (cons fn args))
#f))
(define (cq/info cq fn . args)
(let* ((call (if (and (pair? fn) (null? args)) fn (cons fn args)))
(cache (cq-cache cq))
(cachekey (if (index? cache)
(cons (procedure-id (car call)) (cdr call))
call)))
(get cache cachekey)))
(define (cq/prefetch cq fn . args)
(when (index? (cq-cache cq))
(prefetch-keys!
(cq-cache cq)
(for-choices (f fn)
(apply list (procedure-id f) args)))))
;;; Making requests
(define (cachequeuepush cq call (statehandler #f))
(%debug "Pushing request for " call)
(with-lock (cq-lock cq)
(let* ((cache (cq-cache cq))
(pending (cq-pending cq))
(cachekey (if (index? cache)
(cons (procedure-id (car call)) (cdr call))
call))
(cachevalue (get cache cachekey)))
(if (if (exists? cachevalue)
(not (melted-value? cachevalue))
(test cache cachekey))
cachevalue
(let ((state (get pending call)))
(when (fail? state)
(set! state (timestamp))
(add! pending call state)
(fifo-push (cq-fifo cq) call)
(when statehandler (statehandler state)))
cachevalue)))))
(define (cachequeuepreempt cq call)
(%debug "Preemptive request for " call)
(with-lock (cq-lock cq)
(let* ((cache (cq-cache cq))
(pending (cq-pending cq))
(cachekey (if (index? cache)
(cons (procedure-id (car call)) (cdr call))
call))
(cachevalue (get cache cachekey)))
(if (if (exists? cachevalue)
(not (melted-value? cachevalue))
;; Since we can cache an empty choice, we use test to determine
;; whether the empty value is cached
(test cache cachekey))
(begin (%debug "Using cache value for preemptive request " call)
cachevalue)
(let ((state (get pending call)))
(if (and (exists? state) (pair? state))
(begin
(%debug "Waiting for active computation to resolve " call)
(while (pair? (get pending call))
(condvar/wait (cq-lock cq)))
(%debug "Call is no longer pending " call)
(try (get pending call) (get cache cachekey)))
(begin
(%debug "Directly executing " call)
(when (exists? state)
(store! pending call (cons (get pending call) (timestamp)))
(fifo-jump (cq-fifo cq) call))
(sync/release! (cq-lock cq))
(%debug "Starting execution of " call)
(let ((value (erreify (apply (car call) (cdr call)))))
(%debug "Finished executing " call)
(sync/lock! (cq-lock cq))
(%debug "Caching results of " call)
(if (error? value)
(store! pending call value)
(begin
(when (cq-meltpoint cq)
(set! value
(meltentry (try cachevalue #f)
value (cq-meltpoint cq))))
(store! cache cachekey value)
(drop! pending call)))
value))))))))
(define (cachequeuerequest cq call (melt #t))
(let* ((cache (cq-cache cq))
(pending (cq-pending cq))
(cachekey (if (index? cache)
(cons (procedure-id (car call)) (cdr call))
call))
(cachevalue (get cache cachekey)))
(if (if (exists? cachevalue)
(not (melted-value? cachevalue))
(test cache cachekey))
(begin (%debug "Using cached result for " call)
(if melt (meltvalue cachevalue) cachevalue))
(if melt (meltvalue (cachequeuepush cq call))
(cachequeuepush cq call)))))
(define (cachequeuerequire cq call)
(let* ((cache (cq-cache cq))
(pending (cq-pending cq))
(cachekey (if (index? cache)
(cons (procedure-id (car call)) (cdr call))
call))
(cachevalue (get cache cachekey)))
(if (if (exists? cachevalue)
(not (melted-value? cachevalue))
(test cache cachekey))
(meltvalue cachevalue)
(meltvalue (cachequeuepreempt cq call)))))
(define (cqstatus cq call (args))
"Returns the status of a queued computation"
(set! args (cdr call))
(let ((cachekey (if (index? (cq-cache cq))
(cons (procedure-id (car call)) (cdr call))
call)))
(if (test (cq-cache cq) cachekey)
(let ((cachev (get (cq-cache cq) cachekey)))
(if (and (exists? cachev) (meltentry? cachev))
(if (melted? cachev)
(list 'melted
(meltentry-creation cachev)
(meltentry-expiration cachev))
(list 'cached
(meltentry-creation cachev)
(meltentry-expiration cachev)))
'(cached)))
(let ((statev (get (cq-pending cq) call)))
(cond ((fail? statev) #f)
((error? statev) (cons 'error statev))
((pair? statev)
(list 'running (car statev) (cdr statev)))
((timestamp? statev)
(list 'queued statev))))))
(if (test (cq-cache cq) args) #t
(unwind-protect
(begin (sync/lock! (cq-lock cq))
(try (get (cq-cache cq) args)
#f))
(sync/release! (cq-lock cq)))))
(define (cqinfo cq call)
"Returns the status of a queued computation"
(let ((cachekey (if (index? (cq-cache cq))
(cons (procedure-id (car call)) (cdr call))
call)))
(get (cq-cache cq) cachekey)))
;;; Doing tasks on the queue
(define (cqstep cq call)
(%debug "CACHEQUEUE step applying " (car call) " to " (cdr call))
(store! (cq-pending cq) call
(cons (get (cq-pending cq) call)
(timestamp)))
(let ((cache (cq-cache cq))
(value (erreify (apply (car call) (cdr call)))))
(if (error? value)
(store! (cq-pending cq) call value)
(begin (if (cq-meltpoint cq)
(meltcache/store cache value (car call) (cdr call)
(cq-meltpoint cq))
(store! cache
(if (index? cache)
(cons (procedure-id (car call)) (cdr call))
call)
value))
(drop! (cq-pending cq) call)))
(%debug "CACHEQUEUE step done applying " (car call) " to " (cdr call))
value))
(define (cqdaemon cq i (max 1))
(let ((entry (fifo-pop (cq-fifo cq)))
(count 0))
(while (and (exists? entry)
(pair? entry)
(or (not max) (< count max)))
(cqstep cq entry)
(set! count (1+ count))
(when (or (not max) (< count max))
(set! entry (fifo-pop (cq-fifo cq)))))))