tupelo 0.20 → 0.21
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 +15 -3
- data/bench/{tuplespace.rb → tuplestore.rb} +3 -3
- data/bin/tspy +2 -2
- data/bin/tup +20 -3
- data/example/multi-tier/{kvspace.rb → kvstore.rb} +2 -2
- data/example/multi-tier/memo2.rb +4 -4
- data/example/riemann/v2/event-sql.rb +56 -0
- data/example/riemann/v2/expirer.rb +4 -1
- data/example/riemann/v2/hash-store.rb +6 -6
- data/example/riemann/v2/ordered-event-store.rb +1 -1
- data/example/riemann/v2/riemann.rb +7 -4
- data/example/riemann/v2/sqlite-event-store.rb +174 -0
- data/example/sqlite/poi-client.rb +11 -0
- data/example/sqlite/poi-store.rb +38 -56
- data/example/sqlite/poi-template.rb +61 -0
- data/example/sqlite/poi-v2.rb +63 -33
- data/example/sqlite/poi.rb +13 -25
- data/example/sqlite/tmp/poi-sqlite.rb +10 -8
- data/example/subspaces/addr-book.rb +2 -2
- data/example/subspaces/{sorted-set-space.rb → sorted-set-store.rb} +4 -4
- data/example/wip/complex-tags.rb +45 -0
- data/lib/tupelo/app/trace.rb +1 -1
- data/lib/tupelo/app.rb +3 -3
- data/lib/tupelo/archiver/{persistent-tuplespace.rb → persistent-tuplestore.rb} +1 -1
- data/lib/tupelo/archiver/{tuplespace.rb → tuplestore.rb} +2 -2
- data/lib/tupelo/archiver/worker.rb +12 -12
- data/lib/tupelo/archiver.rb +7 -5
- data/lib/tupelo/client/reader.rb +13 -10
- data/lib/tupelo/client/subspace.rb +12 -4
- data/lib/tupelo/client/transaction.rb +98 -95
- data/lib/tupelo/client/{tuplespace.rb → tuplestore.rb} +4 -4
- data/lib/tupelo/client/worker.rb +44 -35
- data/lib/tupelo/client.rb +4 -4
- data/lib/tupelo/version.rb +1 -1
- data/test/lib/mock-client.rb +1 -1
- data/test/lib/testable-worker.rb +1 -1
- data/test/unit/test-ops.rb +1 -1
- metadata +13 -9
- data/example/map-reduce/ex.rb +0 -32
@@ -0,0 +1,61 @@
|
|
1
|
+
# Hard-coded to work with tuples belonging to the "poi" subspace
|
2
|
+
# and with the PoiStore table.
|
3
|
+
class PoiTemplate
|
4
|
+
attr_reader :lat, :lng
|
5
|
+
|
6
|
+
# Template for matching all POI tuples.
|
7
|
+
attr_reader :poi_template
|
8
|
+
|
9
|
+
# lat and lng can be intervals or single values or nil to match any value
|
10
|
+
def initialize lat: nil, lng: nil, poi_template: nil
|
11
|
+
@lat = lat
|
12
|
+
@lng = lng
|
13
|
+
@poi_template = poi_template
|
14
|
+
end
|
15
|
+
|
16
|
+
# we only need to define this method if we plan to wait for poi tuples
|
17
|
+
# locally using this template, i.e. read(template) or take(template)
|
18
|
+
def === tuple
|
19
|
+
@poi_template === tuple and
|
20
|
+
!@lat || @lat === tuple[:lat] and
|
21
|
+
!@lng || @lng === tuple[:lng]
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_in table, distinct_from: []
|
25
|
+
where_terms = {}
|
26
|
+
where_terms[:lat] = @lat if @lat
|
27
|
+
where_terms[:lng] = @lng if @lng
|
28
|
+
|
29
|
+
matches = table.
|
30
|
+
select(:lat, :lng, :desc).
|
31
|
+
where(where_terms).
|
32
|
+
limit(distinct_from.size + 1).all
|
33
|
+
|
34
|
+
# Note: everything in this method up to and including where(...) can
|
35
|
+
# be called once and reused as a sequel dataset.
|
36
|
+
|
37
|
+
distinct_from.each do |tuple|
|
38
|
+
if i=matches.index(tuple)
|
39
|
+
matches.delete_at i
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
matches.first
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_all_in table, &bl
|
47
|
+
where_terms = {}
|
48
|
+
where_terms[:lat] = @lat if @lat
|
49
|
+
where_terms[:lng] = @lng if @lng
|
50
|
+
|
51
|
+
matches = table.
|
52
|
+
select(:lat, :lng, :desc).
|
53
|
+
where(where_terms)
|
54
|
+
|
55
|
+
if bl
|
56
|
+
matches.each(&bl)
|
57
|
+
else
|
58
|
+
matches.all
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/example/sqlite/poi-v2.rb
CHANGED
@@ -1,58 +1,88 @@
|
|
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
|
-
# See example/subspaces/addr-book.rb for more comments on the command/response
|
15
|
-
# subspace pattern.
|
16
|
-
#
|
17
|
-
# gem install sequel sqlite3
|
18
|
-
|
19
1
|
require 'tupelo/app'
|
20
|
-
require_relative 'poi-
|
2
|
+
require_relative 'poi-client'
|
21
3
|
|
22
4
|
Tupelo.application do
|
23
5
|
local do
|
24
6
|
POISPACE = PoiStore.define_poispace(self)
|
25
7
|
define_subspace("cmd", {id: nil, cmd: String, arg: nil})
|
26
8
|
define_subspace("rsp", {id: nil, result: nil})
|
9
|
+
# Note: this id is request id, not related to sqlite table id.
|
27
10
|
end
|
28
11
|
|
29
|
-
child
|
30
|
-
subscribe: ["poi", "cmd"], passive: true do
|
12
|
+
child PoiClient, poispace: POISPACE, subscribe: "cmd", passive: true do
|
31
13
|
log.progname = "poi-store #{client_id}"
|
14
|
+
|
15
|
+
poispace = subspace("poi")
|
16
|
+
# get local copy to replace poispace argument, which has wrong kind of
|
17
|
+
# keys (string, not symbol)
|
32
18
|
|
33
19
|
# handle custom queries here, using poi template
|
34
20
|
loop do
|
35
21
|
req = take subspace("cmd")
|
36
|
-
case req[
|
22
|
+
case req[:cmd]
|
37
23
|
when "find box"
|
38
|
-
arg = req[
|
39
|
-
lat = arg[
|
40
|
-
template = PoiTemplate.new(
|
24
|
+
arg = req[:arg] ## should validate args
|
25
|
+
lat = arg[:lat]; lng = arg[:lng]
|
26
|
+
template = PoiTemplate.new(poi_template: poispace,
|
27
|
+
lat: lat[0]..lat[1], lng: lng[0]..lng[1])
|
28
|
+
write id: req[:id], result: read_all(template)
|
29
|
+
|
30
|
+
when "delete box"
|
31
|
+
arg = req[:arg]
|
32
|
+
lat = arg[:lat]; lng = arg[:lng]
|
33
|
+
template = PoiTemplate.new(poi_template: poispace,
|
41
34
|
lat: lat[0]..lat[1], lng: lng[0]..lng[1])
|
42
|
-
|
35
|
+
|
36
|
+
deleted = []
|
37
|
+
transaction do
|
38
|
+
while poi = take_nowait(template)
|
39
|
+
log "preparing to delete: #{poi}"
|
40
|
+
deleted << poi
|
41
|
+
end
|
42
|
+
# Wrapping this in a transaction not really necessary, but more
|
43
|
+
# efficient (only one round-trip to network). Watch out for huge sets
|
44
|
+
# of tuples, though.
|
45
|
+
end
|
46
|
+
|
47
|
+
write id: req[:id], result: deleted
|
43
48
|
end
|
44
49
|
end
|
45
50
|
end
|
46
51
|
|
47
52
|
child subscribe: "rsp" do
|
48
|
-
write lat:
|
49
|
-
write lat:
|
50
|
-
write lat:
|
51
|
-
write lat:
|
53
|
+
write lat: 1.2, lng: 3.4, desc: "foo"
|
54
|
+
write lat: 5.6, lng: 7.8, desc: "bar"
|
55
|
+
write lat: 1.2, lng: 3.4, desc: "foo" # dup is ok
|
56
|
+
write lat: 1.3, lng: 3.5, desc: "baz"
|
52
57
|
|
53
|
-
|
54
|
-
|
58
|
+
cmd_id = 0 # manually managing cmd id is a pain!
|
59
|
+
|
60
|
+
log "finding in box"
|
61
|
+
cmd_id += 1
|
62
|
+
req_id = [client_id, cmd_id]
|
63
|
+
write id: req_id, cmd: "find box", arg: {lat: [1.0, 1.4], lng: [3.0, 4.0]}
|
64
|
+
rsp = take id: req_id, result: nil
|
65
|
+
log "result: #{rsp["result"]}"
|
66
|
+
|
67
|
+
log "deleting in box"
|
68
|
+
cmd_id += 1
|
69
|
+
req_id = [client_id, cmd_id]
|
70
|
+
write id: req_id, cmd: "delete box", arg: {lat: [1.0, 1.4], lng: [3.0, 4.0]}
|
71
|
+
rsp = take id: req_id, result: nil
|
72
|
+
log "result: #{rsp["result"]}"
|
73
|
+
|
74
|
+
log "finding in box"
|
75
|
+
cmd_id += 1
|
76
|
+
req_id = [client_id, cmd_id]
|
77
|
+
write id: req_id, cmd: "find box", arg: {lat: [1.0, 1.4], lng: [3.0, 4.0]}
|
78
|
+
rsp = take id: req_id, result: nil
|
79
|
+
log "result: #{rsp["result"]}"
|
80
|
+
|
81
|
+
log "finding in BIGGER box"
|
82
|
+
cmd_id += 1
|
83
|
+
req_id = [client_id, cmd_id]
|
84
|
+
write id: req_id, cmd: "find box", arg: {lat: [0, 100], lng: [0, 100]}
|
55
85
|
rsp = take id: req_id, result: nil
|
56
|
-
log rsp["result"]
|
86
|
+
log "result: #{rsp["result"]}"
|
57
87
|
end
|
58
88
|
end
|
data/example/sqlite/poi.rb
CHANGED
@@ -1,40 +1,28 @@
|
|
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
1
|
require 'tupelo/app'
|
17
|
-
require_relative 'poi-
|
2
|
+
require_relative 'poi-client'
|
18
3
|
|
19
4
|
Tupelo.application do
|
20
5
|
local do
|
21
6
|
POISPACE = PoiStore.define_poispace(self)
|
22
7
|
end
|
23
8
|
|
24
|
-
child
|
9
|
+
child PoiClient, poispace: POISPACE, passive: true do
|
25
10
|
log.progname = "poi-store"
|
26
|
-
|
11
|
+
|
12
|
+
# At this point, the client already accepts writes in the space and stores
|
13
|
+
# them in a sqlite table. For the sake of a simple example, we add
|
14
|
+
# one feature to this mix: just show everything for each new tuple
|
27
15
|
read do
|
28
|
-
log read_all
|
16
|
+
log read_all
|
29
17
|
end
|
30
18
|
end
|
31
19
|
|
32
20
|
child subscribe: nil do
|
33
|
-
|
34
|
-
sleep 0.5 #
|
35
|
-
|
36
|
-
sleep 0.5
|
37
|
-
|
38
|
-
sleep 0.5
|
21
|
+
write lat: 1.2, lng: 3.4, desc: "foo"
|
22
|
+
sleep 0.5 # delay to make the demo interesting
|
23
|
+
write lat: 5.6, lng: 7.8, desc: "bar"
|
24
|
+
sleep 0.5
|
25
|
+
write lat: 1.2, lng: 3.4, desc: "foo" # dup is ok
|
26
|
+
sleep 0.5
|
39
27
|
end
|
40
28
|
end
|
@@ -13,13 +13,13 @@ end
|
|
13
13
|
|
14
14
|
poi = db[:poi]
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
10.times do |i|
|
17
|
+
poi << {
|
18
|
+
lat: rand,
|
19
|
+
lng: rand,
|
20
|
+
desc: "point #{i}"
|
21
|
+
}
|
22
|
+
end
|
23
23
|
|
24
24
|
#p poi.all
|
25
25
|
#exit
|
@@ -28,6 +28,8 @@ poi << {lat: 1, lng: 2, desc: "a"}
|
|
28
28
|
poi << {lat: 1, lng: 2, desc: "b"}
|
29
29
|
|
30
30
|
#p poi.count
|
31
|
-
|
31
|
+
p poi.select(:lat, :lng, :desc).
|
32
|
+
where(lat: 0..1, lng: 0..1)
|
33
|
+
|
32
34
|
p poi.select(:lat, :lng, :desc).
|
33
35
|
where(lat: 0..1, lng: 0..1).limit(5).all
|
@@ -20,7 +20,7 @@
|
|
20
20
|
# clients can each use their own data structure for these tuples.
|
21
21
|
|
22
22
|
require 'tupelo/app'
|
23
|
-
require_relative 'sorted-set-
|
23
|
+
require_relative 'sorted-set-store'
|
24
24
|
|
25
25
|
SHOW_HANDLERS = ARGV.delete("--show-handlers")
|
26
26
|
|
@@ -66,7 +66,7 @@ Tupelo.application do
|
|
66
66
|
N_REPLICAS.times do |i|
|
67
67
|
# Inserts are just writes, which are handled by Worker and SortedSetSpace,
|
68
68
|
# so this child's app loop only needs to handle the special commands.
|
69
|
-
child
|
69
|
+
child tuplestore: [SortedSetStore, ab_tag],
|
70
70
|
subscribe: [ab_tag, cmd_tag], passive: true do
|
71
71
|
|
72
72
|
log.progname = "replica ##{i}"
|
@@ -2,10 +2,10 @@ require 'rbtree'
|
|
2
2
|
|
3
3
|
## TODO
|
4
4
|
##
|
5
|
-
## generalize
|
5
|
+
## generalize SortedSetStore to accept params that indicate which fields
|
6
6
|
## are key and value
|
7
7
|
##
|
8
|
-
## unify with
|
8
|
+
## unify with store used in ../riemann v2, generalize
|
9
9
|
|
10
10
|
# This is a template class, but it doesn't just match tuples. It can
|
11
11
|
# be used to find the *next* tuple after a given one, using the rbtree
|
@@ -74,8 +74,8 @@ end
|
|
74
74
|
# (See memo2.rb.)
|
75
75
|
#
|
76
76
|
# This store also manages command and meta tuples, which it keeps in an array,
|
77
|
-
# just like the default
|
78
|
-
class
|
77
|
+
# just like the default Tuplestore class does.
|
78
|
+
class SortedSetStore
|
79
79
|
include Enumerable
|
80
80
|
|
81
81
|
attr_reader :tag, :tree, :metas
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Tags can be any object that can be serialized by msgpack. Strings and
|
2
|
+
# numbers are typical, but it can be useful to encode a bit more structure.
|
3
|
+
# In this case we can use a complex tag to limit communication to particular
|
4
|
+
# client groups (use groups of clients, so
|
5
|
+
# that the lifespan of one process does not affect the system as a whole).
|
6
|
+
|
7
|
+
require 'tupelo/app'
|
8
|
+
|
9
|
+
Tupelo.application do
|
10
|
+
num_tag = "input numbers"#["input numbers", 1]
|
11
|
+
str_tag = "input strings"#["input strings", 2]
|
12
|
+
|
13
|
+
local do
|
14
|
+
define_subspace num_tag, [Numeric]
|
15
|
+
define_subspace str_tag, [String]
|
16
|
+
end
|
17
|
+
|
18
|
+
child subscribe: num_tag, passive: true do
|
19
|
+
read subspace(num_tag) do |num, _|
|
20
|
+
write [num.to_s]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
child subscribe: str_tag, passive: true do
|
25
|
+
read subspace(str_tag) do |str, _|
|
26
|
+
begin
|
27
|
+
num = Integer(str) rescue Float(str)
|
28
|
+
write [num]
|
29
|
+
rescue
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
local do # subscribed to everything
|
35
|
+
write [42]
|
36
|
+
write ["17.5"]
|
37
|
+
|
38
|
+
count = 0
|
39
|
+
read do |tuple|
|
40
|
+
log tuple
|
41
|
+
count += 1
|
42
|
+
break if count == 4
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/tupelo/app/trace.rb
CHANGED
data/lib/tupelo/app.rb
CHANGED
@@ -105,14 +105,14 @@ module Tupelo
|
|
105
105
|
ez.service :arcd, **arcd_addr do |sv|
|
106
106
|
require 'tupelo/archiver'
|
107
107
|
if persist_dir
|
108
|
-
require 'tupelo/archiver/persistent-
|
108
|
+
require 'tupelo/archiver/persistent-tuplestore'
|
109
109
|
arc = Archiver.new sv, seq: arc_to_seq_sock,
|
110
|
-
|
110
|
+
tuplestore: Archiver::PersistentTupleStore,
|
111
111
|
persist_dir: persist_dir,
|
112
112
|
cseq: arc_to_cseq_sock, log: log
|
113
113
|
else
|
114
114
|
arc = Archiver.new sv, seq: arc_to_seq_sock,
|
115
|
-
|
115
|
+
tuplestore: Archiver::TupleStore,
|
116
116
|
cseq: arc_to_cseq_sock, log: log
|
117
117
|
end
|
118
118
|
arc.start
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Tupelo::Archiver
|
2
|
-
# Faster than the default
|
2
|
+
# Faster than the default tuplestore, but does not support some things
|
3
3
|
# that are not needed in the Archiver: template searches, for example.
|
4
|
-
class
|
4
|
+
class TupleStore
|
5
5
|
include Enumerable
|
6
6
|
|
7
7
|
attr_reader :zero_tolerance
|
@@ -10,19 +10,19 @@ class Tupelo::Archiver
|
|
10
10
|
@opts = opts
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
@
|
15
|
-
if client.
|
16
|
-
client.
|
13
|
+
def tuplestore
|
14
|
+
@tuplestore ||= begin
|
15
|
+
if client.tuplestore.respond_to? :new
|
16
|
+
client.tuplestore.new **@opts
|
17
17
|
else
|
18
|
-
client.
|
18
|
+
client.tuplestore
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
def stop
|
24
24
|
super
|
25
|
-
|
25
|
+
tuplestore.flush global_tick
|
26
26
|
end
|
27
27
|
|
28
28
|
def handle_client_request req
|
@@ -67,8 +67,8 @@ class Tupelo::Archiver
|
|
67
67
|
raise "Unimplemented" ###
|
68
68
|
when "get range" ### handle this in Funl::HistoryWorker
|
69
69
|
raise "Unimplemented" ###
|
70
|
-
when
|
71
|
-
|
70
|
+
when GET_TUPLESTORE
|
71
|
+
send_tuplestore stream, sub_delta
|
72
72
|
else
|
73
73
|
raise "Unknown operation: #{op.inspect}"
|
74
74
|
end
|
@@ -94,9 +94,9 @@ class Tupelo::Archiver
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
def
|
97
|
+
def send_tuplestore stream, sub_delta
|
98
98
|
log.info {
|
99
|
-
"
|
99
|
+
"send_tuplestore to #{stream.peer_name} " +
|
100
100
|
"at tick #{global_tick.inspect} " +
|
101
101
|
(sub_delta ? " with sub_delta #{sub_delta.inspect}" : "")}
|
102
102
|
|
@@ -106,7 +106,7 @@ class Tupelo::Archiver
|
|
106
106
|
## has to be sent.
|
107
107
|
|
108
108
|
if sub_delta["request_all"]
|
109
|
-
|
109
|
+
tuplestore.each do |tuple, count|
|
110
110
|
count.times do ## just dump and send str * count?
|
111
111
|
stream << tuple ## optimize this, and cache the serial
|
112
112
|
## optimization: use stream.write_to_buffer
|
@@ -117,7 +117,7 @@ class Tupelo::Archiver
|
|
117
117
|
tags = sub_delta["request_tags"] ## use set
|
118
118
|
subs = subspaces.select {|sub| tags.include? sub.tag}
|
119
119
|
|
120
|
-
|
120
|
+
tuplestore.each do |tuple, count|
|
121
121
|
## alternately, store tags with tuples (risk if dynamic spaces)
|
122
122
|
if subs.any? {|sub| sub === tuple}
|
123
123
|
count.times do
|
data/lib/tupelo/archiver.rb
CHANGED
@@ -9,7 +9,7 @@ require 'funl/history-client'
|
|
9
9
|
class Tupelo::Archiver < Tupelo::Client; end
|
10
10
|
|
11
11
|
require 'tupelo/archiver/worker'
|
12
|
-
require 'tupelo/archiver/
|
12
|
+
require 'tupelo/archiver/tuplestore' ## unless persistent?
|
13
13
|
|
14
14
|
module Tupelo
|
15
15
|
class Archiver
|
@@ -23,11 +23,13 @@ module Tupelo
|
|
23
23
|
ZERO_TOLERANCE = 1000
|
24
24
|
|
25
25
|
def initialize server,
|
26
|
-
|
26
|
+
tuplestore: Tupelo::Archiver::TupleStore,
|
27
27
|
persist_dir: nil, **opts
|
28
|
+
## when ruby does GC symbols, add this:
|
29
|
+
## symbolize_keys: true
|
28
30
|
@server = server
|
29
31
|
@persist_dir = persist_dir
|
30
|
-
super arc: nil,
|
32
|
+
super arc: nil, tuplestore: tuplestore, **opts
|
31
33
|
end
|
32
34
|
|
33
35
|
# three kinds of requests:
|
@@ -37,12 +39,12 @@ module Tupelo
|
|
37
39
|
#
|
38
40
|
# 2. accept tcp/unix socket connection and fork, and then:
|
39
41
|
#
|
40
|
-
# a. dump
|
42
|
+
# a. dump tuples matching given templates OR
|
41
43
|
#
|
42
44
|
# b. dump all ops in a given range of the global sequence
|
43
45
|
# matching given templates
|
44
46
|
#
|
45
|
-
# the fork happens when
|
47
|
+
# the fork happens when tuplestore is consistent; we
|
46
48
|
# do this by passing cmd to worker thread, with conn
|
47
49
|
class ForkRequest
|
48
50
|
attr_reader :io
|
data/lib/tupelo/client/reader.rb
CHANGED
@@ -3,18 +3,21 @@ require 'tupelo/client/common'
|
|
3
3
|
class Tupelo::Client
|
4
4
|
# Include into class that defines #worker and #log.
|
5
5
|
module Api
|
6
|
-
|
6
|
+
def not_meta
|
7
|
+
k = tupelo_meta_key
|
8
|
+
@not_meta ||= proc {|t| not defined? t.key? or not t.key? k}
|
9
|
+
end
|
7
10
|
|
8
11
|
# If no block given, return one matching tuple, blocking if necessary.
|
9
12
|
# If block given, yield each matching tuple that is found
|
10
|
-
# locally and then yield each new match as it is written to the
|
13
|
+
# locally and then yield each new match as it is written to the store.
|
11
14
|
# Guaranteed not to miss tuples, even if they arrive and are immediately
|
12
15
|
# taken. (Note that simply doing read(template) in a loop would not
|
13
16
|
# have this guarantee.)
|
14
|
-
# The template defaults to
|
17
|
+
# The template defaults to not_meta, which matches any tuple except metas.
|
15
18
|
# The first phase of this method, reading existing tuples, is essentially
|
16
19
|
# the same as read_all, and subject to the same warnings.
|
17
|
-
def read_wait template =
|
20
|
+
def read_wait template = not_meta
|
18
21
|
waiter = Waiter.new(worker.make_template(template), self, !block_given?)
|
19
22
|
worker << waiter
|
20
23
|
if block_given?
|
@@ -31,15 +34,15 @@ class Tupelo::Client
|
|
31
34
|
end
|
32
35
|
alias read read_wait
|
33
36
|
|
34
|
-
# The template defaults to
|
35
|
-
def read_nowait template =
|
37
|
+
# The template defaults to not_meta, which matches any tuple except metas.
|
38
|
+
def read_nowait template = not_meta
|
36
39
|
matcher = Matcher.new(worker.make_template(template), self)
|
37
40
|
worker << matcher
|
38
41
|
matcher.wait
|
39
42
|
end
|
40
43
|
|
41
|
-
# Returns all matching tuples currently in the
|
42
|
-
# tuples to arrive. The template defaults to
|
44
|
+
# Returns all matching tuples currently in the store. Does not wait for more
|
45
|
+
# tuples to arrive. The template defaults to not_meta, which matches any
|
43
46
|
# tuple except metas. To read all matches of more than one template, use
|
44
47
|
# the #or method from util/boolean.rb.
|
45
48
|
# Matches are guaranteed to exist at the same tick (even if they no longer
|
@@ -47,11 +50,11 @@ class Tupelo::Client
|
|
47
50
|
# blocked from all other activity for some time, so be careful about
|
48
51
|
# using read_all when large numbers of tuples match. If a block is given,
|
49
52
|
# it runs after the worker has unblocked.
|
50
|
-
def read_all template =
|
53
|
+
def read_all template = not_meta
|
51
54
|
matcher = Matcher.new(worker.make_template(template), self, :all => true)
|
52
55
|
worker << matcher
|
53
56
|
a = []
|
54
|
-
while tuple = matcher.wait ## inefficient?
|
57
|
+
while tuple = matcher.wait ## inefficient to wait one at a time?
|
55
58
|
yield tuple if block_given?
|
56
59
|
a << tuple
|
57
60
|
end
|
@@ -1,11 +1,19 @@
|
|
1
1
|
class Tupelo::Client
|
2
2
|
module Api
|
3
3
|
TUPELO_SUBSPACE_TAG = "tupelo subspace".freeze
|
4
|
-
|
4
|
+
|
5
|
+
def tupelo_subspace_tag
|
6
|
+
@tupelo_subspace_tag ||=
|
7
|
+
symbolize_keys ? TUPELO_SUBSPACE_TAG.to_sym : TUPELO_SUBSPACE_TAG
|
8
|
+
end
|
9
|
+
|
10
|
+
def tupelo_meta_key
|
11
|
+
@tupelo_meta_key ||= symbolize_keys ? :__tupelo__ : "__tupelo__".freeze
|
12
|
+
end
|
5
13
|
|
6
14
|
def define_subspace tag, template, addr: nil
|
7
15
|
metatuple = {
|
8
|
-
|
16
|
+
tupelo_meta_key => "subspace",
|
9
17
|
tag: tag,
|
10
18
|
template: PortableObjectTemplate.spec_from(template),
|
11
19
|
addr: addr
|
@@ -19,7 +27,7 @@ class Tupelo::Client
|
|
19
27
|
def use_subspaces!
|
20
28
|
return if find_subspace_by_tag(TUPELO_SUBSPACE_TAG)
|
21
29
|
define_subspace(TUPELO_SUBSPACE_TAG, {
|
22
|
-
|
30
|
+
tupelo_meta_key => "subspace",
|
23
31
|
tag: nil,
|
24
32
|
template: nil,
|
25
33
|
addr: nil
|
@@ -30,7 +38,7 @@ class Tupelo::Client
|
|
30
38
|
tag = tag.to_s
|
31
39
|
find_subspace_by_tag(tag) or begin
|
32
40
|
if subscribed_tags.include? tag
|
33
|
-
read
|
41
|
+
read tupelo_meta_key => "subspace",
|
34
42
|
tag: tag,
|
35
43
|
template: nil,
|
36
44
|
addr: nil
|