Module: asynchronous-results-implementation Synopsis: Environment Manager Author: Hugh Greene Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// -=- ID POOL -=- (internal) /// /// This section implements a pool of unique IDs. There are operations /// to obtain an ID from the pool and to release one back to the pool. /// (We could use the Strategy pattern here, to allow different ways /// of getting IDs, e.g., using GUIDs under Win32.) /// /// The pool is of finite size, so an attempt to obtain an ID when /// all are in use will block until one becomes available. /// *NEXT-ID*, $IDS-IN-USE (internal) /// /// These variables keep track of which ID should be allocated next and /// which have been allocated. The *NEXT-ID* is not guaranteed to be /// available, though it is likely to be (if IDs are returned to the /// pool "often enough"). If it is claimed, the ID allocation code /// will scan for the next unclaimed ID and assign that instead (and /// update *NEXT-ID* accordingly). define variable *next-id* :: = 0; define constant $ids-in-use :: = make(
); /// $ID-POOL-LOCK (internal) /// /// This lock is used to synchronise access to the pool of IDs. define constant $id-pool-lock :: = make(); /// $OBTAIN-ID-RETRY-INTERVAL /// /// The number of seconds to wait before trying again to obtain an ID /// from the pool, if the all IDs are in use. define constant $OBTAIN-ID-RETRY-INTERVAL :: = 3; /// OBTAIN-ID (internal) /// /// Returns the next free ID, updating *NEXT-ID* to the next one after that. define function obtain-id () => (id-string :: false-or()) local method id-free? (id :: ) => (free? :: ) ~element($ids-in-use, id, default: #f) end method; local method following-id (id :: ) => (id+1 :: ) if (id = $maximum-integer) 0 else id + 1 end end method; with-lock ($id-pool-lock) let free-id :: = if (id-free?(*next-id*)) // Just use the *next-id*. *next-id* else // Scan all IDs after *next-id*, until we hit a free one or we get // all the way back round to *next-id*. In the latter case, wait // for a while, then try again; let free-id = #f; until (free-id) for (id = following-id(*next-id*) then following-id(id), until: (id-free?(id) | (id = *next-id*))) finally if (id = *next-id*) release($id-pool-lock); sleep($obtain-id-retry-interval); wait-for($id-pool-lock); else free-id := id end; end; end; free-id end; *next-id* := following-id(free-id); $ids-in-use[free-id] := #t; integer-to-string(free-id); end; end function obtain-id; /// RELEASE-ID (internal) define function release-id (id-string :: ) => () with-lock ($id-pool-lock) let id = string-to-integer(id-string, default: #f); when (id) //--- Maybe we should signal a condition for unconvertable strings? //--- OTOH, the only callers of this will already have ensured that //--- only valid IDs are passed in here. remove-key!($ids-in-use, id); end; end; end function release-id; /// -=- TOKEN CLASS -=- /// /// Results protocol: /// function get-results-id () => (id :: ) /// function wait-for-results (id :: ) => (results :: ); /// // May signal subclass /// // with restarts and (results); /// // or signal subclass /// // with no restarts. /// function provide-results (id :: , results :: ) => (); /// // May signal subclass /// // with no restarts. /// end protocol <>; /// (internal) define sealed abstract class () required keyword id:; end class ; define sealed method make (class :: subclass(), #rest initargs, #key id, #all-keys) => (condition :: ) apply(next-method, class, format-arguments: vector(id), initargs) end method make; /// (asynchronous-results) define sealed class (, ) keyword format-string: = "Attempted to wait for results on invalid ID %s"; end class ; /// (asynchronous-results) define sealed class (, ) keyword format-string: = "Timed out while waiting for results on ID %s"; end class ; /// (asynchronous-results) define sealed class () keyword format-string: = "Keep waiting for results"; end class ; /// (asynchronous-results) define sealed class () sealed constant slot results-to-assume :: false-or(), required-init-keyword: assume:; keyword format-string: = "Assume results %="; end class ; define sealed method make (class == , #rest initargs, #key assume, #all-keys) => (assume-results :: ) apply(next-method, class, format-arguments: vector(assume), initargs) end method make; /// (internal) define sealed class () sealed constant slot token-id :: = obtain-id(); sealed slot token-results :: false-or(), init-value: #f; sealed constant slot token-notification :: = make(, lock: make()); end class ; /// $TOKEN-TABLE (internal) /// /// This table maps TOKEN-IDs to s. define constant $token-table ::
= make(
); /// INITIALIZE (dylan) define sealed method initialize (token :: , #key, #all-keys) next-method(); $token-table[token.token-id] := token; end method initialize; /// GET-RESULTS-ID (asynchronous-results) define function get-results-id () => (id :: ) make().token-id end function get-results-id; /// ID-RESULTS-TOKEN (internal) define function id-results-token (id :: , #key error? :: = #t) => (token :: false-or(type-union(, singleton(#"aborted")))) let token = element($token-table, id, default: #f); if (token) token else when (error?) error(make(, id: id)); end; end; end function id-results-token; /// WAIT-FOR-RESULTS (asynchronous-results) define function wait-for-results (id :: , #key timeout :: false-or() = #f) => (results :: false-or()) let token = id-results-token(id); let notification = token.token-notification; let results = #f; let got-notification? = #f; with-lock (notification.associated-lock) block () unless (token.token-results) until (got-notification?) got-notification? := wait-for(notification, timeout: timeout); unless (got-notification?) block () error(make(, id: id)); exception () // Okay, go back round the "until". end; end; end; end; results := token.token-results; cleanup when (got-notification?) abort-results(id); // And now the notification and lock will just be GC'd. end; exception (assumption :: ) results := assumption.results-to-assume; end; end; end function wait-for-results; /// PROVIDE-RESULTS (asynchronous-results) define function provide-results (id :: , results :: ) => () let token = id-results-token(id, error?: #f); when (token) let notification = token.token-notification; with-lock (notification.associated-lock) token.token-results := results; release-all(notification); end; end; end function provide-results; /// ABORT-RESULTS (asynchronous-results) define function abort-results (id :: ) => () remove-key!($token-table, id); release-id(id); end function abort-results; /// Utility macros /// WITH-ASYNCHRONOUS-RESULTS (asynchronous-results) define macro with-asynchronous-results { with-asynchronous-results (?:name, #key ?abort-on-condition?:expression = #t, ?timeout:expression = #f) ?:body end } => { let _id = get-results-id(); let ?name = _id; block () ?body; wait-for-results(_id, timeout: ?timeout) cleanup when (?abort-on-condition?) abort-results(_id); end; end } end macro with-asynchronous-results;