#lang racket/base ;; A structure (and process!) for matching supply to demand via observation of interests. (require racket/set) (require racket/match) (require "core.rkt") (require "drivers/timer.rkt") (require "pretty.rkt") (require "support/hash.rkt") (require "protocol/advertise.rkt") (provide (except-out (struct-out demand-matcher) demand-matcher) (rename-out [make-demand-matcher demand-matcher]) demand-matcher-update spawn-demand-matcher) ;; A DemandMatcher keeps track of demand for services based on some ;; Projection over a Trie, as well as a collection of functions ;; that can be used to increase supply in response to increased ;; demand, or handle a sudden drop in supply for which demand still ;; exists. (struct demand-matcher (demand-spec ;; CompiledProjection supply-spec ;; CompiledProjection demand-spec-arity ;; Natural supply-spec-arity ;; Natural start-task ;; TaskCallback on-task-exit ;; TaskCallback current-demand ;; (Setof (Listof Any)) current-supply ;; (Setof (Listof Any)) task-supervisor ;; TaskSupervisor supervision-states) ;; (Hashtable (Listof Any) Any) #:transparent #:methods gen:syndicate-pretty-printable [(define (syndicate-pretty-print s [p (current-output-port)]) (pretty-print-demand-matcher s p))]) ;; A TaskCallback is a ((Constreeof Action) Any* -> (Constreeof Action)). ;; It is called with an accumulator of actions so-far-computed as its ;; first argument, and with a value for each capture in the ;; DemandMatcher's projection as the remaining arguments. ;; A TaskRunState describes demand for or supply of some task at the ;; time of taking a transition where some aspect of the supervisor's ;; relationship to a particular task has changed, and is one of ;; - 'low, meaning no demand or supply exists ;; - 'rising, demand or supply has just appeared ;; - 'high, demand or supply exists ;; - 'falling, demand or supply has just disappeared ;; A TaskSupervisor is a ;; ((Listof Any) TaskRunState TaskRunState Any TaskCallback TaskCallback (Constreeof Action) ;; -> (Transition Any)) (struct task-state (supply-increase-expected? supply-decrease-expected?) #:prefab) (define default-initial-supervision-state (task-state #f #f)) ;; TaskSupervisor. See ../../doc/demand-matcher.md. (define (default-task-supervisor captures demand-state supply-state supervision-state start-task on-task-exit actions0) (match-define (task-state old-i old-d) (or supervision-state default-initial-supervision-state)) (let*-values (((new-i) old-i) ((new-d) old-d) ((actions) actions0) ((new-i actions) (if (and (not old-i) (or (eq? demand-state 'rising) (and (eq? demand-state 'high) (or (eq? supply-state 'low) (eq? supply-state 'falling))))) (if (and (eq? demand-state 'high) (not old-d)) (values new-i (apply on-task-exit actions captures)) (values #t (apply start-task actions captures))) (values new-i actions))) ((new-i) (if (eq? supply-state 'rising) #f new-i)) ((new-d) (if (and (eq? demand-state 'falling) (or (eq? supply-state 'rising) (eq? supply-state 'high) old-i)) #t new-d)) ((new-d) (if (eq? supply-state 'falling) #f new-d))) (transition (and (or new-i new-d) (task-state new-i new-d)) actions))) (define (default-on-task-exit s . captures) (log-error "demand-matcher: Unexpected drop in supply ~v" captures) s) (define (make-demand-matcher demand-spec supply-spec start-task [on-task-exit default-on-task-exit] #:task-supervisor [task-supervisor default-task-supervisor]) (demand-matcher demand-spec supply-spec (projection-arity demand-spec) (projection-arity supply-spec) start-task on-task-exit (set) (set) task-supervisor (hash))) (define (ensure-non-wild s kind spec direction t) (when (not s) (error 'demand-matcher "Wildcard ~a of ~v ~a:\n~a" kind spec direction (trie->pretty-string t)))) (define (compute-state tasks added-tasks removed-tasks captures) (define present? (set-member? tasks captures)) (define changing? (or (set-member? added-tasks captures) (set-member? removed-tasks captures))) (match* (present? changing?) [(#f #f) 'low] [(#f #t) 'rising] [(#t #f) 'high] [(#t #t) 'falling])) ;; DemandMatcher (Constreeof Action) Patch -> (Transition DemandMatcher) ;; Given a Patch from the environment, projects it into supply and ;; demand increase and decrease sets. Calls ChangeHandlers in response ;; to increased unsatisfied demand and decreased demanded supply. (define (demand-matcher-update d actions0 p) (match-define (demand-matcher demand-spec supply-spec demand-arity supply-arity start-task on-task-exit demand supply task-supervisor supervision-states) d) (define-values (added-demand removed-demand) (patch-project/set #:take demand-arity p demand-spec)) (define-values (added-supply removed-supply) (patch-project/set #:take supply-arity p supply-spec)) (ensure-non-wild added-demand 'demand demand-spec 'added (patch-added p)) (ensure-non-wild added-supply 'supply supply-spec 'added (patch-added p)) (ensure-non-wild removed-demand 'demand demand-spec 'removed (patch-removed p)) (ensure-non-wild removed-supply 'supply supply-spec 'removed (patch-removed p)) ;; Though the added and removed sets of patches are always disjoint, ;; *after projection* this may not hold. Cancel out any overlaps. (let ((overlap (set-intersect added-demand removed-demand))) (set! added-demand (set-subtract added-demand overlap)) (set! removed-demand (set-subtract removed-demand overlap))) (let ((overlap (set-intersect added-supply removed-supply))) (set! added-supply (set-subtract added-supply overlap)) (set! removed-supply (set-subtract removed-supply overlap))) (define all-changing-tasks (set-union added-demand removed-demand added-supply removed-supply)) (define-values (new-supervision-states actions) (for/fold [(supervision-states supervision-states) (actions actions0)] [(captures (in-set all-changing-tasks))] (define demand-state (compute-state demand added-demand removed-demand captures)) (define supply-state (compute-state supply added-supply removed-supply captures)) (match-define (transition new-supervision-state new-actions) (task-supervisor captures demand-state supply-state (hash-ref supervision-states captures #f) start-task on-task-exit actions)) (values (hash-set/remove supervision-states captures new-supervision-state) new-actions))) (transition (struct-copy demand-matcher d [current-demand (set-subtract (set-union demand added-demand) removed-demand)] [current-supply (set-subtract (set-union supply added-supply) removed-supply)] [supervision-states new-supervision-states]) actions)) ;; Behavior :> (Option Event) DemandMatcher -> (Transition DemandMatcher) ;; Handles events from the environment. Only cares about routing-updates. (define (demand-matcher-handle-event e d) (match e [(? patch? p) (demand-matcher-update d '() p)] [_ #f])) ;; Projection Projection (Any* -> (Constreeof Action)) [(Any* -> (Constreeof Action))] -> Action ;; Spawns a demand matcher actor. (define (spawn-demand-matcher demand-spec supply-spec start-task [on-task-exit #f] #:task-supervisor [task-supervisor default-task-supervisor] #:name [name #f]) (define d (make-demand-matcher demand-spec supply-spec (lambda (acs . rs) (cons (apply start-task rs) acs)) (if on-task-exit (lambda (acs . rs) (cons (apply on-task-exit rs) acs)) default-on-task-exit) #:task-supervisor task-supervisor)) (actor #:name name demand-matcher-handle-event d (patch-seq (sub (projection->pattern demand-spec)) (sub (projection->pattern supply-spec)) (pub (projection->pattern supply-spec))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (pretty-print-demand-matcher s [p (current-output-port)]) (match-define (demand-matcher demand-spec supply-spec _demand-arity _supply-arity _start-task _on-task-exit current-demand current-supply _task-supervisor supervision-states) s) (fprintf p "DEMAND MATCHER:\n") (fprintf p " - demand-spec: ~v\n" demand-spec) (fprintf p " - supply-spec: ~v\n" supply-spec) (fprintf p " - demand: ~v\n" current-demand) (fprintf p " - supply: ~v\n" current-supply) (fprintf p " - supervision-states: ~v\n" supervision-states))