racket/collects/scheme/promise.ss
2009-12-05 09:28:41 +00:00

179 lines
6.9 KiB
Scheme

#lang scheme/base
(require "private/promise.ss" (for-syntax scheme/base))
(provide delay lazy force promise? promise-forced? promise-running?)
;; ----------------------------------------------------------------------------
;; More delay-like values, with different ways of deferring computations
(define-struct (promise/name promise) ()
#:property prop:force (lambda (p) ((pref p))))
(provide (rename-out [delay/name* delay/name]))
(define delay/name make-promise/name)
(define-syntax (delay/name* stx) (make-delayer stx #'delay/name '()))
;; utility struct
(define-struct (running-thread running) (thread))
;; used in promise/sync until it's forced
(define-struct syncinfo ([thunk #:mutable] done-evt done-sema access-sema))
(define-struct (promise/sync promise) ()
#:property prop:custom-write
(lambda (p port write?)
(promise-printer
(let ([v (pref p)])
(if (syncinfo? v) (make-promise (syncinfo-thunk v)) p))
port write?))
#:property prop:force
(lambda (p)
(reify-result
(let ([v (pref p)])
(cond
;; already forced
[(not (syncinfo? v)) v]
;; being forced...
[(running-thread? (syncinfo-thunk v))
(let ([r (syncinfo-thunk v)])
(if (eq? (running-thread-thread r) (current-thread))
;; ... by the current thread => throw the usual reentrant error
(r)
;; ... by a different thread => just wait for it
(begin (sync (syncinfo-done-evt v)) (pref p))))]
[else
;; wasn't forced yet: try to do it now
(call-with-semaphore (syncinfo-access-sema v)
(lambda ()
(let ([thunk (syncinfo-thunk v)] [done (syncinfo-done-sema v)])
;; set the thread last
(set-syncinfo-thunk!
v (make-running-thread (object-name thunk) (current-thread)))
(call-with-exception-handler
(lambda (e)
(pset! p (make-reraise e))
(semaphore-post done)
e)
(lambda ()
(pset! p (call-with-values thunk list))
(semaphore-post done))))))
;; whether it was this thread that forced it or not, the results are
;; now in
(pref p)]))))
#:property prop:evt
(lambda (p)
(let ([v (pref p)])
(handle-evt (if (syncinfo? v) (syncinfo-done-evt v) always-evt) void))))
(provide (rename-out [delay/sync* delay/sync]))
(define (delay/sync thunk)
(let ([done-sema (make-semaphore 0)])
(make-promise/sync (make-syncinfo thunk
(semaphore-peek-evt done-sema) done-sema
(make-semaphore 1)))))
(define-syntax (delay/sync* stx) (make-delayer stx #'delay/sync '()))
;; threaded promises
(define-struct (promise/thread promise) ()
#:property prop:force
(lambda (p)
(reify-result (let ([v (pref p)])
(if (running-thread? v)
(begin (thread-wait (running-thread-thread v))
(pref p))
v))))
#:property prop:evt
(lambda (p)
(let ([v (pref p)])
(handle-evt (if (running? v) (running-thread-thread v) always-evt)
void))))
(provide (rename-out [delay/thread* delay/thread]))
(define (delay/thread thunk group)
(define (run)
(call-with-exception-handler
(lambda (e) (pset! p (make-reraise e)) (kill-thread (current-thread)))
(lambda () (pset! p (call-with-values thunk list)))))
(define p
(make-promise/thread
(make-running-thread
(object-name thunk)
(if group
(parameterize ([current-thread-group (make-thread-group)]) (thread run))
(thread run)))))
p)
(define-syntax delay/thread*
(let ([kwds (list (cons '#:group #'#t))])
(lambda (stx) (make-delayer stx #'delay/thread kwds))))
(define-struct (promise/idle promise/thread) ()
#:property prop:force
(lambda (p)
(reify-result (let ([v (pref p)])
(if (procedure? v)
;; either running-thread, or returns the controller
(let ([controller (if (running-thread? v)
(running-thread-thread v)
(v))])
(thread-send controller 'force!)
(thread-wait controller)
(pref p))
v)))))
(provide (rename-out [delay/idle* delay/idle]))
(define (delay/idle thunk wait-for work-while tick use*)
(define use (cond [(use* . <= . 0) 0] [(use* . >= . 1) 1] [else use*]))
(define work-time (* tick use))
(define rest-time (- tick work-time))
(define (work)
(call-with-exception-handler
(lambda (e) (pset! p (make-reraise e)) (kill-thread (current-thread)))
(lambda () (pset! p (call-with-values thunk list)))))
(define (run)
;; this thread is dedicated to controlling the worker thread, so it's
;; possible to dedicate messages to signaling a `force'.
(define force-evt (thread-receive-evt))
(sync wait-for force-evt)
(pset! p (make-running-thread (object-name thunk) controller-thread))
(let ([worker (parameterize ([current-thread-group (make-thread-group)])
(thread work))])
(cond
[(and (use . >= . 1) (equal? work-while always-evt))
;; as if it was pre-forced
(thread-wait worker)]
[(use . <= . 0)
;; work only when explicitly forced
(thread-suspend worker)
(sync force-evt)
(thread-wait worker)]
[else
(thread-suspend worker)
(let loop ()
;; rest, then wait for idle time, then resume working
(if (eq? (begin0 (or (sync/timeout rest-time force-evt)
(sync work-while force-evt))
(thread-resume worker))
force-evt)
;; forced during one of these => let it run to completion
(thread-wait worker)
;; not forced
(unless (sync/timeout work-time worker)
(thread-suspend worker)
(loop))))])))
;; I don't think that a thread-group here is needed, but it doesn't hurt
(define controller-thread
(parameterize ([current-thread-group (make-thread-group)])
(thread run)))
;; the thunk is not really used in the above, make it a function that returns
;; the controller thread so it can be forced (used in the `prop:force')
(define p (make-promise/idle
(procedure-rename (lambda () controller-thread)
(or (object-name thunk) 'idle-thread))))
p)
(define-syntax delay/idle*
(let ([kwds (list (cons '#:wait-for #'(system-idle-evt))
(cons '#:work-while #'(system-idle-evt))
(cons '#:tick #'0.2)
(cons '#:use #'0.12))])
(lambda (stx) (make-delayer stx #'delay/idle kwds))))