summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/cache/async.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/cache/async.tcl')
-rw-r--r--tcllib/modules/cache/async.tcl185
1 files changed, 185 insertions, 0 deletions
diff --git a/tcllib/modules/cache/async.tcl b/tcllib/modules/cache/async.tcl
new file mode 100644
index 0000000..e6c866a
--- /dev/null
+++ b/tcllib/modules/cache/async.tcl
@@ -0,0 +1,185 @@
+## -*- tcl -*-
+# ### ### ### ######### ######### #########
+
+# Copyright (c) 2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+# Aynchronous in-memory cache. Queries of the cache generate
+# asynchronous requests for data for unknown parts, with asynchronous
+# result return. Data found in the cache may return fully asynchronous
+# as well, or semi-synchronous. The latter meaning that the regular
+# callbacks are used, but invoked directly, and not decoupled through
+# events. The cache can be pre-filled synchronously.
+
+# ### ### ### ######### ######### #########
+## Requisites
+
+package require Tcl 8.4 ; #
+package require snit ; #
+
+# ### ### ### ######### ######### #########
+##
+
+snit::type cache::async {
+
+ # ### ### ### ######### ######### #########
+ ## Unknown methods and options are forwared to the object actually
+ ## providing the cached data, making the cache a proper facade for
+ ## it.
+
+ delegate method * to myprovider
+ delegate option * to myprovider
+
+ # ### ### ### ######### ######### #########
+ ## API
+
+ option -full-async-results -default 1 -type snit::boolean
+
+ constructor {provider args} {
+ set myprovider $provider
+ $self configurelist $args
+ return
+ }
+
+ method get {key donecmd} {
+ # Register request
+ lappend mywaiting($key) $donecmd
+
+ # Check if the request can be satisfied from the cache. If yes
+ # then that is done.
+
+ if {[info exists mymiss($key)]} {
+ $self NotifyUnset 1 $key
+ return
+ } elseif {[info exists myhit($key)]} {
+ $self NotifySet 1 $key
+ return
+ }
+
+ # We have to ask our provider if there is data or
+ # not. however, if a request for this key is already in flight
+ # then we have to do nothing more. Our registration at the
+ # beginning ensures that we will get notified when the
+ # requested information comes back.
+
+ if {[llength $mywaiting($key)] > 1} return
+
+ # This is the first query for this key, ask the provider.
+
+ after idle [linsert $myprovider end get $key $self]
+ return
+ }
+
+ method clear {args} {
+ # Note: This method cannot interfere with async queries caused
+ # by 'get' invokations. If the data is present, and now
+ # removed, all 'get' invokations before this call were
+ # satisfied from the cache and only invokations coming after
+ # it can trigger async queries of the provider. If the data is
+ # not present the state will not change, and queries in flight
+ # simply refill the cache as they would do anyway without the
+ # 'clear'.
+
+ if {![llength $args]} {
+ array unset myhit *
+ array unset mymiss *
+ } elseif {[llength $arg] == 1} {
+ set key [lindex $args 0]
+ unset -nocomplain myhit($key)
+ unset -nocomplain mymiss($key)
+ } else {
+ WrongArgs ?key?
+ }
+ return
+ }
+
+ method exists {key} {
+ return [expr {[info exists myhit($key)] || [info exists mymiss($key)]}]
+ }
+
+ method set {key value} {
+ # Add data to the cache, and notify all outstanding queries.
+ # Nothing is done if the key is already known and has the same
+ # value.
+
+ # This is the method invoked by the provider in response to
+ # queries, and also the method to use to prefill the cache
+ # with data.
+
+ if {
+ [info exists myhit($key)] &&
+ ($value eq $myhit($key))
+ } return
+
+ set myhit($key) $value
+ unset -nocomplain mymiss($key)
+ $self NotifySet 0 $key
+ return
+ }
+
+ method unset {key} {
+ # Add hole to the cache, and notify all outstanding queries.
+ # This is the method invoked by the provider in response to
+ # queries, and also the method to use to prefill the cache
+ # with holes.
+ unset -nocomplain myhit($key)
+ set mymiss($key) .
+ $self NotifyUnset 0 $key
+ return
+ }
+
+ method NotifySet {found key} {
+ if {![info exists mywaiting($key)] || ![llength $mywaiting($key)]} return
+
+ set pending $mywaiting($key)
+ unset mywaiting($key)
+
+ set value $myhit($key)
+ if {$found && !$options(-full-async-results)} {
+ foreach donecmd $pending {
+ uplevel \#0 [linsert $donecmd end set $key $value]
+ }
+ } else {
+ foreach donecmd $pending {
+ after idle [linsert $donecmd end set $key $value]
+ }
+ }
+ return
+ }
+
+ method NotifyUnset {found key} {
+ if {![info exists mywaiting($key)] || ![llength $mywaiting($key)]} return
+
+ set pending $mywaiting($key)
+ unset mywaiting($key)
+
+ if {$found && !$options(-full-async-results)} {
+ foreach donecmd $pending {
+ uplevel \#0 [linsert $donecmd end unset $key]
+ }
+ } else {
+ foreach donecmd $pending {
+ after idle [linsert $donecmd end unset $key]
+ }
+ }
+ return
+ }
+
+ proc WrongArgs {expected} {
+ return -code error "wrong#args: Expected $expected"
+ }
+
+ # ### ### ### ######### ######### #########
+ ## State
+
+ variable myprovider ; # Command prefix providing the data to cache.
+ variable myhit -array {} ; # Cache array mapping keys to values.
+ variable mymiss -array {} ; # Cache array mapping keys to holes.
+ variable mywaiting -array {} ; # Map of keys pending to notifier commands.
+
+ # ### ### ### ######### ######### #########
+}
+
+# ### ### ### ######### ######### #########
+## Ready
+
+package provide cache::async 0.3