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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +98 -36
  3. data/bin/tup +1 -7
  4. data/bugs/take-write.rb +8 -0
  5. data/example/bingo/bingo-v2.rb +20 -0
  6. data/example/broker-queue.rb +35 -0
  7. data/example/child-of-child.rb +34 -0
  8. data/example/consistent-hash.rb +0 -2
  9. data/example/counters/lock.rb +24 -0
  10. data/example/counters/merge.rb +35 -0
  11. data/example/counters/optimistic.rb +29 -0
  12. data/example/dataflow.rb +21 -0
  13. data/example/dedup.rb +45 -0
  14. data/example/map-reduce/ex.rb +32 -0
  15. data/example/multi-tier/memo2.rb +0 -2
  16. data/example/pregel/dist-opt.rb +15 -0
  17. data/example/riemann/event-subspace.rb +2 -0
  18. data/example/riemann/expiration-dbg.rb +15 -0
  19. data/example/riemann/producer.rb +34 -13
  20. data/example/riemann/v1/expirer.rb +28 -0
  21. data/example/riemann/{riemann-v1.rb → v1/riemann.rb} +5 -8
  22. data/example/riemann/v2/expirer.rb +31 -0
  23. data/example/riemann/v2/hash-store.rb +33 -0
  24. data/example/riemann/v2/http-mode.rb +53 -0
  25. data/example/riemann/v2/ordered-event-store.rb +128 -0
  26. data/example/riemann/{riemann-v2.rb → v2/riemann.rb} +32 -17
  27. data/example/sqlite/poi-store.rb +160 -0
  28. data/example/sqlite/poi-v2.rb +58 -0
  29. data/example/sqlite/poi.rb +40 -0
  30. data/example/sqlite/tmp/poi-sqlite.rb +33 -0
  31. data/example/subspaces/addr-book-v1.rb +0 -2
  32. data/example/subspaces/addr-book-v2.rb +0 -2
  33. data/example/subspaces/addr-book.rb +0 -2
  34. data/example/subspaces/pubsub.rb +0 -2
  35. data/example/subspaces/ramp.rb +0 -2
  36. data/example/subspaces/shop/shop-v2.rb +0 -2
  37. data/example/subspaces/simple.rb +0 -1
  38. data/example/subspaces/sorted-set-space.rb +5 -0
  39. data/lib/tupelo/app.rb +8 -0
  40. data/lib/tupelo/archiver/persistent-tuplespace.rb +2 -2
  41. data/lib/tupelo/archiver/tuplespace.rb +2 -2
  42. data/lib/tupelo/client/reader.rb +18 -8
  43. data/lib/tupelo/client/subspace.rb +12 -4
  44. data/lib/tupelo/client/transaction.rb +13 -1
  45. data/lib/tupelo/client/worker.rb +27 -4
  46. data/lib/tupelo/client.rb +3 -5
  47. data/lib/tupelo/tuplets/persistent-archiver/tuplespace.rb +5 -0
  48. data/lib/tupelo/version.rb +1 -1
  49. data/test/lib/mock-client.rb +1 -0
  50. metadata +26 -7
  51. 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
@@ -12,8 +12,6 @@ resp_tag = "#{ab_tag} responses"
12
12
 
13
13
  Tupelo.application do
14
14
  local do
15
- use_subspaces!
16
-
17
15
  # Subspace for tuples belonging to the addr book.
18
16
  define_subspace(
19
17
  tag: ab_tag,
@@ -2,8 +2,6 @@ require 'tupelo/app'
2
2
 
3
3
  Tupelo.application do
4
4
  local do
5
- use_subspaces!
6
-
7
5
  define_subspace(
8
6
  tag: "address books",
9
7
  template: {
@@ -32,8 +32,6 @@ resp_tag = "#{ab_tag} responses"
32
32
 
33
33
  Tupelo.application do
34
34
  local do
35
- use_subspaces!
36
-
37
35
  # Subspace for tuples belonging to the addr book.
38
36
  define_subspace(ab_tag, [
39
37
  ab_tag,
@@ -16,8 +16,6 @@ N_CHAN = 3
16
16
 
17
17
  Tupelo.application do
18
18
  local do
19
- use_subspaces!
20
-
21
19
  N_CHAN.times do |i|
22
20
  define_subspace i, [i, String]
23
21
  end
@@ -43,8 +43,6 @@ end
43
43
  Tupelo.application do
44
44
 
45
45
  local do
46
- use_subspaces!
47
-
48
46
  bool = PortableObjectTemplate::BOOLEAN
49
47
 
50
48
  define_subspace("x", {
@@ -10,8 +10,6 @@ CUSTOMER_IDS = 1..10
10
10
 
11
11
  Tupelo.application do
12
12
  local do
13
- use_subspaces!
14
-
15
13
  define_subspace("inventory", [
16
14
  "product",
17
15
  nil, # product_id
@@ -8,7 +8,6 @@ Tupelo.application do
8
8
  log.progname = "before"
9
9
  log [subscribed_all, subscribed_tags]
10
10
 
11
- use_subspaces!
12
11
  define_subspace "foo", [Numeric]
13
12
 
14
13
  log read_all(Object)
@@ -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
@@ -135,8 +135,8 @@ class Tupelo::Archiver
135
135
  end
136
136
  end
137
137
 
138
- def find_match_for tuple
139
- @tuple_rec[tuple].count > 0 && tuple
138
+ def find_match_for tuple, distinct_from: []
139
+ @tuple_rec[tuple].count > distinct_from.count(tuple)
140
140
  end
141
141
  end
142
142
  end
@@ -69,8 +69,8 @@ class Tupelo::Archiver
69
69
  end
70
70
  end
71
71
 
72
- def find_match_for tuple
73
- @counts[tuple] > 0 && tuple
72
+ def find_match_for tuple, distinct_from: []
73
+ @counts[tuple] > distinct_from.count(tuple)
74
74
  end
75
75
  end
76
76
  end
@@ -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 Object, which matches any tuple.
13
- def read_wait template = Object
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 Object, which matches any tuple.
31
- def read_nowait template = Object
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. The template defaults
38
- # to Object, which matches any tuple. Does not wait for more tuples to
39
- # arrive.
40
- def read_all template = Object
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 subspace(TUPELO_SUBSPACE_TAG)
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
- worker.subspaces.find {|sp| sp.tag == tag} or begin
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
- worker.subspaces.find {|sp| sp.tag == tag}
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 pending?
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
 
@@ -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
- tuple.kind_of? Hash and tuple.key? TUPELO_META_KEY and
439
- tuple[TUPELO_META_KEY] == "subspace"
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.submit
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
@@ -1,3 +1,3 @@
1
1
  module Tupelo
2
- VERSION = "0.19"
2
+ VERSION = "0.20"
3
3
  end
@@ -2,6 +2,7 @@ require 'fiber'
2
2
 
3
3
  require 'tupelo/client/reader'
4
4
  require 'tupelo/client/transaction'
5
+ require 'tupelo/client/subspace'
5
6
 
6
7
  require 'mock-queue.rb'
7
8
 
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.19'
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-02-26 00:00:00.000000000 Z
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/expirer-v1.rb
159
+ - example/riemann/expiration-dbg.rb
150
160
  - example/riemann/producer.rb
151
- - example/riemann/riemann-v1.rb
152
- - example/riemann/riemann-v2.rb
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