tupelo 0.19 → 0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +98 -36
- data/bin/tup +1 -7
- data/bugs/take-write.rb +8 -0
- data/example/bingo/bingo-v2.rb +20 -0
- data/example/broker-queue.rb +35 -0
- data/example/child-of-child.rb +34 -0
- data/example/consistent-hash.rb +0 -2
- data/example/counters/lock.rb +24 -0
- data/example/counters/merge.rb +35 -0
- data/example/counters/optimistic.rb +29 -0
- data/example/dataflow.rb +21 -0
- data/example/dedup.rb +45 -0
- data/example/map-reduce/ex.rb +32 -0
- data/example/multi-tier/memo2.rb +0 -2
- data/example/pregel/dist-opt.rb +15 -0
- data/example/riemann/event-subspace.rb +2 -0
- data/example/riemann/expiration-dbg.rb +15 -0
- data/example/riemann/producer.rb +34 -13
- data/example/riemann/v1/expirer.rb +28 -0
- data/example/riemann/{riemann-v1.rb → v1/riemann.rb} +5 -8
- data/example/riemann/v2/expirer.rb +31 -0
- data/example/riemann/v2/hash-store.rb +33 -0
- data/example/riemann/v2/http-mode.rb +53 -0
- data/example/riemann/v2/ordered-event-store.rb +128 -0
- data/example/riemann/{riemann-v2.rb → v2/riemann.rb} +32 -17
- data/example/sqlite/poi-store.rb +160 -0
- data/example/sqlite/poi-v2.rb +58 -0
- data/example/sqlite/poi.rb +40 -0
- data/example/sqlite/tmp/poi-sqlite.rb +33 -0
- data/example/subspaces/addr-book-v1.rb +0 -2
- data/example/subspaces/addr-book-v2.rb +0 -2
- data/example/subspaces/addr-book.rb +0 -2
- data/example/subspaces/pubsub.rb +0 -2
- data/example/subspaces/ramp.rb +0 -2
- data/example/subspaces/shop/shop-v2.rb +0 -2
- data/example/subspaces/simple.rb +0 -1
- data/example/subspaces/sorted-set-space.rb +5 -0
- data/lib/tupelo/app.rb +8 -0
- data/lib/tupelo/archiver/persistent-tuplespace.rb +2 -2
- data/lib/tupelo/archiver/tuplespace.rb +2 -2
- data/lib/tupelo/client/reader.rb +18 -8
- data/lib/tupelo/client/subspace.rb +12 -4
- data/lib/tupelo/client/transaction.rb +13 -1
- data/lib/tupelo/client/worker.rb +27 -4
- data/lib/tupelo/client.rb +3 -5
- data/lib/tupelo/tuplets/persistent-archiver/tuplespace.rb +5 -0
- data/lib/tupelo/version.rb +1 -1
- data/test/lib/mock-client.rb +1 -0
- metadata +26 -7
- data/example/riemann/expirer-v1.rb +0 -25
@@ -0,0 +1,40 @@
|
|
1
|
+
# POI -- Points Of Interest
|
2
|
+
#
|
3
|
+
# This example creates a sqlite db in memory with a table of locations and
|
4
|
+
# descriptions of points of interest, and attaches the db to a subspace of the
|
5
|
+
# tuplespace. The process which manages that subspace can now do two things:
|
6
|
+
#
|
7
|
+
# 1. accept inserts (via write)
|
8
|
+
#
|
9
|
+
# 2. custom queries, accessed by write to a different subspace
|
10
|
+
#
|
11
|
+
# You can have redundant instances of this, and that will distribute load
|
12
|
+
# in #2 above.
|
13
|
+
#
|
14
|
+
# gem install sequel sqlite3
|
15
|
+
|
16
|
+
require 'tupelo/app'
|
17
|
+
require_relative 'poi-store'
|
18
|
+
|
19
|
+
Tupelo.application do
|
20
|
+
local do
|
21
|
+
POISPACE = PoiStore.define_poispace(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
child tuplespace: [PoiStore, POISPACE], subscribe: "poi", passive: true do
|
25
|
+
log.progname = "poi-store"
|
26
|
+
# handle custom queries here, using poi template
|
27
|
+
read do
|
28
|
+
log read_all # just show everything for each new tuple
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
child subscribe: nil do
|
33
|
+
write_wait lat: 12, lng: 34, desc: "foo"
|
34
|
+
sleep 0.5 # give poi store time to store and log
|
35
|
+
write_wait lat: 56, lng: 78, desc: "bar"
|
36
|
+
sleep 0.5 # give poi store time to store and log
|
37
|
+
write_wait lat: 12, lng: 34, desc: "foo" # dup is ok
|
38
|
+
sleep 0.5 # give poi store time to store and log
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
db = Sequel.sqlite
|
4
|
+
db.create_table "poi" do
|
5
|
+
primary_key :id
|
6
|
+
float :lat, null: false
|
7
|
+
float :lng, null: false
|
8
|
+
text :desc
|
9
|
+
end
|
10
|
+
|
11
|
+
#p db
|
12
|
+
#p db.schema :poi
|
13
|
+
|
14
|
+
poi = db[:poi]
|
15
|
+
|
16
|
+
#10.times do |i|
|
17
|
+
# poi << {
|
18
|
+
# lat: rand,
|
19
|
+
# lng: rand,
|
20
|
+
# desc: "point #{i}"
|
21
|
+
# }
|
22
|
+
#end
|
23
|
+
|
24
|
+
#p poi.all
|
25
|
+
#exit
|
26
|
+
|
27
|
+
poi << {lat: 1, lng: 2, desc: "a"}
|
28
|
+
poi << {lat: 1, lng: 2, desc: "b"}
|
29
|
+
|
30
|
+
#p poi.count
|
31
|
+
|
32
|
+
p poi.select(:lat, :lng, :desc).
|
33
|
+
where(lat: 0..1, lng: 0..1).limit(5).all
|
data/example/subspaces/pubsub.rb
CHANGED
data/example/subspaces/ramp.rb
CHANGED
data/example/subspaces/simple.rb
CHANGED
@@ -4,7 +4,12 @@ require 'rbtree'
|
|
4
4
|
##
|
5
5
|
## generalize SortedSetSpace to accept params that indicate which fields
|
6
6
|
## are key and value
|
7
|
+
##
|
8
|
+
## unify with space used in ../riemann v2, generalize
|
7
9
|
|
10
|
+
# This is a template class, but it doesn't just match tuples. It can
|
11
|
+
# be used to find the *next* tuple after a given one, using the rbtree
|
12
|
+
# ordering.
|
8
13
|
class SortedSetTemplate
|
9
14
|
class << self
|
10
15
|
alias [] new
|
data/lib/tupelo/app.rb
CHANGED
@@ -127,6 +127,14 @@ module Tupelo
|
|
127
127
|
app.start_trace
|
128
128
|
end
|
129
129
|
|
130
|
+
if owns_services
|
131
|
+
## optimize this away -- need lightweight client
|
132
|
+
## or (better) make subspaces a default even without using app
|
133
|
+
app.local subscribe: nil do
|
134
|
+
use_subspaces!
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
130
138
|
if block
|
131
139
|
if block.arity == 0
|
132
140
|
app.instance_eval &block
|
data/lib/tupelo/client/reader.rb
CHANGED
@@ -3,14 +3,18 @@ require 'tupelo/client/common'
|
|
3
3
|
class Tupelo::Client
|
4
4
|
# Include into class that defines #worker and #log.
|
5
5
|
module Api
|
6
|
+
NOT_META = proc {|t| not defined? t.key? or not t.key? TUPELO_META_KEY}
|
7
|
+
|
6
8
|
# If no block given, return one matching tuple, blocking if necessary.
|
7
9
|
# If block given, yield each matching tuple that is found
|
8
10
|
# locally and then yield each new match as it is written to the space.
|
9
11
|
# Guaranteed not to miss tuples, even if they arrive and are immediately
|
10
12
|
# taken. (Note that simply doing read(template) in a loop would not
|
11
13
|
# have this guarantee.)
|
12
|
-
# The template defaults to
|
13
|
-
|
14
|
+
# The template defaults to NOT_META, which matches any tuple except metas.
|
15
|
+
# The first phase of this method, reading existing tuples, is essentially
|
16
|
+
# the same as read_all, and subject to the same warnings.
|
17
|
+
def read_wait template = NOT_META
|
14
18
|
waiter = Waiter.new(worker.make_template(template), self, !block_given?)
|
15
19
|
worker << waiter
|
16
20
|
if block_given?
|
@@ -27,17 +31,23 @@ class Tupelo::Client
|
|
27
31
|
end
|
28
32
|
alias read read_wait
|
29
33
|
|
30
|
-
# The template defaults to
|
31
|
-
def read_nowait template =
|
34
|
+
# The template defaults to NOT_META, which matches any tuple except metas.
|
35
|
+
def read_nowait template = NOT_META
|
32
36
|
matcher = Matcher.new(worker.make_template(template), self)
|
33
37
|
worker << matcher
|
34
38
|
matcher.wait
|
35
39
|
end
|
36
40
|
|
37
|
-
# Returns all matching tuples currently in the space.
|
38
|
-
# to
|
39
|
-
#
|
40
|
-
|
41
|
+
# Returns all matching tuples currently in the space. Does not wait for more
|
42
|
+
# tuples to arrive. The template defaults to NOT_META, which matches any
|
43
|
+
# tuple except metas. To read all matches of more than one template, use
|
44
|
+
# the #or method from util/boolean.rb.
|
45
|
+
# Matches are guaranteed to exist at the same tick (even if they no longer
|
46
|
+
# exist after that tick). To take a snapshot like this, the worker is
|
47
|
+
# blocked from all other activity for some time, so be careful about
|
48
|
+
# using read_all when large numbers of tuples match. If a block is given,
|
49
|
+
# it runs after the worker has unblocked.
|
50
|
+
def read_all template = NOT_META
|
41
51
|
matcher = Matcher.new(worker.make_template(template), self, :all => true)
|
42
52
|
worker << matcher
|
43
53
|
a = []
|
@@ -1,5 +1,8 @@
|
|
1
1
|
class Tupelo::Client
|
2
2
|
module Api
|
3
|
+
TUPELO_SUBSPACE_TAG = "tupelo subspace".freeze
|
4
|
+
TUPELO_META_KEY = "__tupelo__".freeze
|
5
|
+
|
3
6
|
def define_subspace tag, template, addr: nil
|
4
7
|
metatuple = {
|
5
8
|
TUPELO_META_KEY => "subspace",
|
@@ -11,9 +14,10 @@ class Tupelo::Client
|
|
11
14
|
end
|
12
15
|
|
13
16
|
# call this just once at start of first client (it's optional to
|
14
|
-
# preserve behavior of non-subspace-aware code)
|
17
|
+
# preserve behavior of non-subspace-aware code); this is done automatically
|
18
|
+
# in the app framework
|
15
19
|
def use_subspaces!
|
16
|
-
return if
|
20
|
+
return if find_subspace_by_tag(TUPELO_SUBSPACE_TAG)
|
17
21
|
define_subspace(TUPELO_SUBSPACE_TAG, {
|
18
22
|
TUPELO_META_KEY => "subspace",
|
19
23
|
tag: nil,
|
@@ -24,16 +28,20 @@ class Tupelo::Client
|
|
24
28
|
|
25
29
|
def subspace tag
|
26
30
|
tag = tag.to_s
|
27
|
-
|
31
|
+
find_subspace_by_tag(tag) or begin
|
28
32
|
if subscribed_tags.include? tag
|
29
33
|
read TUPELO_META_KEY => "subspace",
|
30
34
|
tag: tag,
|
31
35
|
template: nil,
|
32
36
|
addr: nil
|
33
|
-
|
37
|
+
find_subspace_by_tag tag
|
34
38
|
end
|
35
39
|
end
|
36
40
|
## this impl will not be safe with dynamic subspaces
|
37
41
|
end
|
42
|
+
|
43
|
+
def find_subspace_by_tag tag
|
44
|
+
worker.find_subspace_by_tag tag
|
45
|
+
end
|
38
46
|
end
|
39
47
|
end
|
@@ -175,6 +175,10 @@ class Tupelo::Client
|
|
175
175
|
end
|
176
176
|
end
|
177
177
|
|
178
|
+
def read_only?
|
179
|
+
@writes.empty? && @pulses.empty? && @take_templates.empty?
|
180
|
+
end
|
181
|
+
|
178
182
|
def inspect
|
179
183
|
stat_extra =
|
180
184
|
case
|
@@ -266,6 +270,10 @@ class Tupelo::Client
|
|
266
270
|
|
267
271
|
# transaction applies only if template has a match
|
268
272
|
def read template_spec
|
273
|
+
if block_given?
|
274
|
+
raise ArgumentError,
|
275
|
+
"Transaction#read with block (streaming read) not allowed"
|
276
|
+
end
|
269
277
|
check_open
|
270
278
|
template = worker.make_template(template_spec)
|
271
279
|
@read_templates << template
|
@@ -476,6 +484,9 @@ class Tupelo::Client
|
|
476
484
|
|
477
485
|
## convert cancelling write/take to pulse
|
478
486
|
## convert cancelling take/write to read
|
487
|
+
## remove redundant pulse after read
|
488
|
+
## remove redundant read before take
|
489
|
+
## remove redundant read after write
|
479
490
|
|
480
491
|
if take_tuples_for_local.all? and read_tuples_for_local.all?
|
481
492
|
@queue << true
|
@@ -520,7 +531,8 @@ class Tupelo::Client
|
|
520
531
|
end
|
521
532
|
|
522
533
|
def done global_tick, granted_tuples
|
523
|
-
raise TransactionStateError, "must be pending" unless
|
534
|
+
raise TransactionStateError, "must be pending or read_only" unless
|
535
|
+
pending? or (closed? and read_only?)
|
524
536
|
raise unless in_worker_thread?
|
525
537
|
raise if @global_tick or @exception
|
526
538
|
|
data/lib/tupelo/client/worker.rb
CHANGED
@@ -320,6 +320,10 @@ class Tupelo::Client
|
|
320
320
|
@delta = 0
|
321
321
|
|
322
322
|
record_history msg
|
323
|
+
execute_transaction msg
|
324
|
+
end
|
325
|
+
|
326
|
+
def execute_transaction msg
|
323
327
|
op = msg.blob ? Operation.new(*blobber.load(msg.blob)) : Operation::NOOP
|
324
328
|
## op.freeze_deeply
|
325
329
|
log.debug {"applying #{op} from client #{msg.client_id}"}
|
@@ -329,7 +333,8 @@ class Tupelo::Client
|
|
329
333
|
end
|
330
334
|
|
331
335
|
take_tuples = tuplespace.find_distinct_matches_for(op.takes)
|
332
|
-
read_tuples = op.reads.map {|t| tuplespace.find_match_for(t
|
336
|
+
read_tuples = op.reads.map {|t| tuplespace.find_match_for(t,
|
337
|
+
distinct_from: take_tuples)}
|
333
338
|
succeeded = take_tuples.all? && read_tuples.all?
|
334
339
|
|
335
340
|
if client.subscribed_all
|
@@ -433,10 +438,23 @@ class Tupelo::Client
|
|
433
438
|
end
|
434
439
|
end
|
435
440
|
|
441
|
+
def find_subspace_by_tag tag
|
442
|
+
subspaces.find {|sp| sp.tag == tag}
|
443
|
+
end
|
444
|
+
|
445
|
+
def meta_subspace
|
446
|
+
@meta_subspace ||= find_subspace_by_tag(Api::TUPELO_SUBSPACE_TAG)
|
447
|
+
end
|
448
|
+
|
436
449
|
# Returns true if tuple is subspace metadata.
|
437
450
|
def is_meta_tuple? tuple
|
438
|
-
|
439
|
-
|
451
|
+
if meta_subspace
|
452
|
+
meta_subspace === tuple
|
453
|
+
else
|
454
|
+
# meta_subspace hasn't arrived yet, so use approximation
|
455
|
+
tuple.kind_of? Hash and tuple.key? Api::TUPELO_META_KEY and
|
456
|
+
tuple[Api::TUPELO_META_KEY] == "subspace"
|
457
|
+
end
|
440
458
|
end
|
441
459
|
|
442
460
|
def sniff_meta_tuple tuple
|
@@ -489,7 +507,11 @@ class Tupelo::Client
|
|
489
507
|
t.prepare
|
490
508
|
prep_waiters << t unless prep_waiters.include? t
|
491
509
|
when t.closed?
|
492
|
-
t.
|
510
|
+
if t.read_only?
|
511
|
+
t.done global_tick, nil
|
512
|
+
else
|
513
|
+
t.submit
|
514
|
+
end
|
493
515
|
prep_waiters.delete t
|
494
516
|
when t.failed?
|
495
517
|
else
|
@@ -600,6 +622,7 @@ class Tupelo::Client
|
|
600
622
|
msg.blob = blobber.dump([writes, pulses, takes, reads])
|
601
623
|
## optimization: use bitfields to identify which ops are present
|
602
624
|
## (instead of nils), in one int
|
625
|
+
## other optimization: remove trailing nil/[]
|
603
626
|
rescue => ex
|
604
627
|
raise ex, "cannot serialize #{transaction.inspect}: #{ex}"
|
605
628
|
end
|
data/lib/tupelo/client.rb
CHANGED
@@ -2,18 +2,16 @@ require 'funl/client'
|
|
2
2
|
|
3
3
|
module Tupelo
|
4
4
|
class Client < Funl::Client
|
5
|
-
require 'tupelo/client/worker'
|
6
|
-
require 'tupelo/client/tuplespace'
|
7
5
|
require 'tupelo/client/subspace'
|
8
6
|
|
9
7
|
include Api
|
10
8
|
|
9
|
+
require 'tupelo/client/worker'
|
10
|
+
require 'tupelo/client/tuplespace'
|
11
|
+
|
11
12
|
attr_reader :worker
|
12
13
|
attr_reader :tuplespace
|
13
14
|
|
14
|
-
TUPELO_SUBSPACE_TAG = "tupelo subspace".freeze
|
15
|
-
TUPELO_META_KEY = "__tupelo__".freeze
|
16
|
-
|
17
15
|
def initialize(tuplespace: SimpleTuplespace, subscribe: :all, **opts)
|
18
16
|
super **opts
|
19
17
|
@tuplespace = tuplespace
|
@@ -82,5 +82,10 @@ class Tupelo::PersistentArchiver
|
|
82
82
|
def find_match_for tuple
|
83
83
|
@counts[tuple] > 0 && tuple
|
84
84
|
end
|
85
|
+
|
86
|
+
### def find_match_for tuple, distinct_from: []
|
87
|
+
### tuple && @tuple_rec[tuple].count > distinct_from.count(tuple)
|
88
|
+
### ## is 'tuple &&' necessary?
|
89
|
+
### end
|
85
90
|
end
|
86
91
|
end
|
data/lib/tupelo/version.rb
CHANGED
data/test/lib/mock-client.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tupelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.20'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel VanderWerf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: atdo
|
@@ -95,19 +95,27 @@ files:
|
|
95
95
|
- example/balance-xfer.rb
|
96
96
|
- example/barrier.rb
|
97
97
|
- example/bingo/bingo-v1.rb
|
98
|
+
- example/bingo/bingo-v2.rb
|
98
99
|
- example/boolean-match.rb
|
99
100
|
- example/bounded-retry.rb
|
100
101
|
- example/broker-locking.rb
|
101
102
|
- example/broker-optimistic-v2.rb
|
102
103
|
- example/broker-optimistic.rb
|
104
|
+
- example/broker-queue.rb
|
103
105
|
- example/cancel.rb
|
104
106
|
- example/chat/chat-nohistory.rb
|
105
107
|
- example/chat/chat.rb
|
108
|
+
- example/child-of-child.rb
|
106
109
|
- example/concurrent-transactions.rb
|
107
110
|
- example/consistent-hash.rb
|
111
|
+
- example/counters/lock.rb
|
112
|
+
- example/counters/merge.rb
|
113
|
+
- example/counters/optimistic.rb
|
108
114
|
- example/custom-class.rb
|
109
115
|
- example/custom-search.rb
|
116
|
+
- example/dataflow.rb
|
110
117
|
- example/deadlock.rb
|
118
|
+
- example/dedup.rb
|
111
119
|
- example/dphil-optimistic-v2.rb
|
112
120
|
- example/dphil-optimistic.rb
|
113
121
|
- example/dphil.rb
|
@@ -118,6 +126,7 @@ files:
|
|
118
126
|
- example/load-balancer.rb
|
119
127
|
- example/lock-mgr-with-queue.rb
|
120
128
|
- example/lock-mgr.rb
|
129
|
+
- example/map-reduce/ex.rb
|
121
130
|
- example/map-reduce/map-reduce-v2.rb
|
122
131
|
- example/map-reduce/map-reduce.rb
|
123
132
|
- example/map-reduce/prime-factor-balanced.rb
|
@@ -136,6 +145,7 @@ files:
|
|
136
145
|
- example/observer.rb
|
137
146
|
- example/optimist.rb
|
138
147
|
- example/parallel.rb
|
148
|
+
- example/pregel/dist-opt.rb
|
139
149
|
- example/pregel/distributed.rb
|
140
150
|
- example/pregel/pagerank.rb
|
141
151
|
- example/pregel/pregel.rb
|
@@ -146,13 +156,22 @@ files:
|
|
146
156
|
- example/read-in-trans.rb
|
147
157
|
- example/remote.rb
|
148
158
|
- example/riemann/event-subspace.rb
|
149
|
-
- example/riemann/
|
159
|
+
- example/riemann/expiration-dbg.rb
|
150
160
|
- example/riemann/producer.rb
|
151
|
-
- example/riemann/
|
152
|
-
- example/riemann/riemann
|
161
|
+
- example/riemann/v1/expirer.rb
|
162
|
+
- example/riemann/v1/riemann.rb
|
163
|
+
- example/riemann/v2/expirer.rb
|
164
|
+
- example/riemann/v2/hash-store.rb
|
165
|
+
- example/riemann/v2/http-mode.rb
|
166
|
+
- example/riemann/v2/ordered-event-store.rb
|
167
|
+
- example/riemann/v2/riemann.rb
|
153
168
|
- example/small-simplified.rb
|
154
169
|
- example/small.rb
|
155
170
|
- example/socket-broker.rb
|
171
|
+
- example/sqlite/poi-store.rb
|
172
|
+
- example/sqlite/poi-v2.rb
|
173
|
+
- example/sqlite/poi.rb
|
174
|
+
- example/sqlite/tmp/poi-sqlite.rb
|
156
175
|
- example/subspaces/addr-book-v1.rb
|
157
176
|
- example/subspaces/addr-book-v2.rb
|
158
177
|
- example/subspaces/addr-book.rb
|
@@ -248,8 +267,8 @@ signing_key:
|
|
248
267
|
specification_version: 4
|
249
268
|
summary: Distributed tuplespace
|
250
269
|
test_files:
|
251
|
-
- test/unit/test-mock-seq.rb
|
252
|
-
- test/unit/test-mock-queue.rb
|
253
270
|
- test/unit/test-ops.rb
|
254
271
|
- test/unit/test-mock-client.rb
|
272
|
+
- test/unit/test-mock-seq.rb
|
273
|
+
- test/unit/test-mock-queue.rb
|
255
274
|
has_rdoc:
|
@@ -1,25 +0,0 @@
|
|
1
|
-
class Tupelo::Client
|
2
|
-
# Expire old events.
|
3
|
-
def run_expirer_v1
|
4
|
-
scheduler = make_scheduler
|
5
|
-
|
6
|
-
read subspace("event") do |event|
|
7
|
-
next if event["state"] == "expired"
|
8
|
-
|
9
|
-
event_exp = event["time"] + event["ttl"]
|
10
|
-
scheduler.at event_exp do
|
11
|
-
transaction do
|
12
|
-
take event
|
13
|
-
pulse event.merge("state" => "expired")
|
14
|
-
# Not sure if this is riemann semantics. Using #pulse rather
|
15
|
-
# that #write means that the expired event exists in the
|
16
|
-
# space only while the transaction is executing, but that is
|
17
|
-
# enough to trigger any client that is waiting on a template
|
18
|
-
# that matches the event. Use the --debug-expiration switch
|
19
|
-
# to see this happening (and use -v to make log messages
|
20
|
-
# verbose, showing timestamps).
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|