funl 0.3 → 0.4
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/lib/funl/client.rb +39 -7
- data/lib/funl/message-sequencer.rb +14 -16
- data/lib/funl/subscription-tracker.rb +93 -0
- data/lib/funl/version.rb +1 -1
- data/test/test-client.rb +71 -19
- data/test/test-subscribe.rb +16 -4
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ac4ecb6a3ca14c04c14cc5af85e34ba3d421123
|
4
|
+
data.tar.gz: a9a6a618dfb48a4baed545c71767b5ac544cba98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1794536193b70a6a965357d64c9d53b58f7c168d1e3c4a17a8b588d1b2e5550fae9b3bccbe5fb2393be397a1482e088b0784dd6aff5dfc4b5fd2ba00989864f0
|
7
|
+
data.tar.gz: abadb9d061fba91923f6781b2132526468943ba848ddb01477008ae77cbc56a2af12cbfe709de9700ac3630177a2211e8e8bc7ad4577361a508dc96ad091cf3e
|
data/lib/funl/client.rb
CHANGED
@@ -2,6 +2,7 @@ require 'logger'
|
|
2
2
|
require 'funl/stream'
|
3
3
|
require 'funl/message'
|
4
4
|
require 'funl/blobber'
|
5
|
+
require 'funl/subscription-tracker'
|
5
6
|
|
6
7
|
module Funl
|
7
8
|
# Generic client base class. Manages the setup and handshake on the streams
|
@@ -33,6 +34,8 @@ module Funl
|
|
33
34
|
@seq = client_stream_for(seq)
|
34
35
|
@cseq = client_stream_for(cseq)
|
35
36
|
@arcio = arc
|
37
|
+
|
38
|
+
@sub_tracker = SubscriptionTracker.new(self)
|
36
39
|
end
|
37
40
|
|
38
41
|
# Handshake with both cseq and seq. Does not start any threads--that is left
|
@@ -43,25 +46,54 @@ module Funl
|
|
43
46
|
yield if block_given?
|
44
47
|
seq_read_greeting
|
45
48
|
end
|
49
|
+
|
50
|
+
def subscribed_all
|
51
|
+
@sub_tracker.subscribed_all
|
52
|
+
end
|
53
|
+
|
54
|
+
def subscribed_tags
|
55
|
+
@sub_tracker.subscribed_tags
|
56
|
+
end
|
46
57
|
|
58
|
+
# Send a subscribe message registering interest in +tags+. Seq will respond
|
59
|
+
# with an ack message containing the tick on which subscription took effect.
|
60
|
+
# Waits for the specified +tags+ to be subscribed (assuming #handle_ack is
|
61
|
+
# called regularly, such as in worker thread).
|
47
62
|
def subscribe tags
|
48
|
-
|
49
|
-
## wait for ack which has tick, ask arc for older messages
|
63
|
+
@sub_tracker.subscribe tags
|
50
64
|
end
|
51
65
|
|
66
|
+
# Send a subscribe message registering interest in all messages. Seq will
|
67
|
+
# respond with an ack message containing the tick on which subscription took
|
68
|
+
# effect. Waits for the subscription to start (assuming #handle_ack is
|
69
|
+
# called regularly).
|
52
70
|
def subscribe_all
|
53
|
-
|
54
|
-
## wait for ack which has tick, ask arc for older messages
|
71
|
+
@sub_tracker.subscribe_all
|
55
72
|
end
|
56
73
|
|
74
|
+
# Unsubscribe from +tags+. Seq will respond with an ack message containing
|
75
|
+
# the tick on which subscription ended. Waits for the subscription to end
|
76
|
+
# (assuming #handle_ack is called regularly).
|
57
77
|
def unsubscribe tags
|
58
|
-
|
78
|
+
@sub_tracker.unsubscribe tags
|
59
79
|
end
|
60
80
|
|
81
|
+
# Unsubscribe from all messages. Any tag subscriptions remain in effect. Seq
|
82
|
+
# will respond with an ack message containing the tick on which subscription
|
83
|
+
# ended. Waits for the subscription to end (assuming #handle_ack is called
|
84
|
+
# regularly).
|
61
85
|
def unsubscribe_all
|
62
|
-
|
86
|
+
@sub_tracker.unsubscribe_all
|
63
87
|
end
|
64
|
-
|
88
|
+
|
89
|
+
# Maintain subscription status. Must be called by the user (or subclass)
|
90
|
+
# of this class, most likely in the thread created by #start.
|
91
|
+
def handle_ack ack
|
92
|
+
raise ArgumentError unless ack.control?
|
93
|
+
op_type, tags = ack.control_op
|
94
|
+
@sub_tracker.update op_type, tags
|
95
|
+
end
|
96
|
+
|
65
97
|
def cseq_read_client_id
|
66
98
|
log.debug "getting client_id from cseq"
|
67
99
|
@client_id = cseq.read["client_id"]
|
@@ -125,18 +125,12 @@ module Funl
|
|
125
125
|
case op_type
|
126
126
|
when SUBSCRIBE_ALL
|
127
127
|
@subscribers_to_all += [stream]
|
128
|
-
ack = Message.control(op_type)
|
129
|
-
ack.global_tick = tick
|
130
|
-
write_succeeds?(ack, stream)
|
131
128
|
|
132
129
|
when SUBSCRIBE
|
133
130
|
tags.each do |tag|
|
134
131
|
@subscribers[tag] += [stream]
|
135
132
|
end
|
136
133
|
@tags[stream] += tags
|
137
|
-
ack = Message.control(op_type, tags)
|
138
|
-
ack.global_tick = tick
|
139
|
-
write_succeeds?(ack, stream)
|
140
134
|
|
141
135
|
when UNSUBSCRIBE_ALL
|
142
136
|
@subscribers_to_all.delete stream
|
@@ -151,6 +145,10 @@ module Funl
|
|
151
145
|
log.error "bad operation: #{op_type.inspect}"
|
152
146
|
return
|
153
147
|
end
|
148
|
+
|
149
|
+
ack = Message.control(op_type, tags)
|
150
|
+
ack.global_tick = tick
|
151
|
+
write_succeeds?(ack, stream)
|
154
152
|
end
|
155
153
|
|
156
154
|
def handle_message msg
|
@@ -183,17 +181,17 @@ module Funl
|
|
183
181
|
false
|
184
182
|
end
|
185
183
|
private :write_succeeds?
|
186
|
-
end
|
187
184
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
185
|
+
def reject_stream stream
|
186
|
+
stream.close unless stream.closed?
|
187
|
+
if streams.include? stream
|
188
|
+
streams.delete stream
|
189
|
+
@subscribers_to_all.delete stream
|
190
|
+
tags = @tags.delete stream
|
191
|
+
if tags
|
192
|
+
tags.each do |tag|
|
193
|
+
@subscribers[tag].delete stream
|
194
|
+
end
|
197
195
|
end
|
198
196
|
end
|
199
197
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'funl/message'
|
3
|
+
|
4
|
+
module Funl
|
5
|
+
# Threadsafe manager for a client's subscriptions.
|
6
|
+
class SubscriptionTracker
|
7
|
+
attr_reader :client
|
8
|
+
attr_reader :subscribed_all
|
9
|
+
|
10
|
+
def subscribed_tags
|
11
|
+
@subscribed_tags.dup
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize client
|
15
|
+
@client = client
|
16
|
+
|
17
|
+
@subscribed_tags = []
|
18
|
+
@subscribed_all = false
|
19
|
+
|
20
|
+
@waiters = []
|
21
|
+
@mon = Monitor.new
|
22
|
+
@cvar = @mon.new_cond
|
23
|
+
end
|
24
|
+
|
25
|
+
def subscribe tags
|
26
|
+
@mon.synchronize do
|
27
|
+
if (tags - @subscribed_tags).empty?
|
28
|
+
return false
|
29
|
+
else
|
30
|
+
client.seq << Message.control(SUBSCRIBE, tags)
|
31
|
+
wait {(tags - @subscribed_tags).empty?}
|
32
|
+
return true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def subscribe_all
|
38
|
+
@mon.synchronize do
|
39
|
+
if @subscribed_all
|
40
|
+
return false
|
41
|
+
else
|
42
|
+
client.seq << Message.control(SUBSCRIBE_ALL)
|
43
|
+
wait {@subscribed_all}
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def unsubscribe tags
|
50
|
+
@mon.synchronize do
|
51
|
+
if (tags & @subscribed_tags).empty?
|
52
|
+
return false
|
53
|
+
else
|
54
|
+
client.seq << Message.control(UNSUBSCRIBE, tags)
|
55
|
+
wait {(tags & @subscribed_tags).empty?}
|
56
|
+
return true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def unsubscribe_all
|
62
|
+
@mon.synchronize do
|
63
|
+
if !@subscribed_all
|
64
|
+
return false
|
65
|
+
else
|
66
|
+
client.seq << Message.control(UNSUBSCRIBE_ALL)
|
67
|
+
wait {!@subscribed_all}
|
68
|
+
return true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def update op_type, tags=nil
|
74
|
+
@mon.synchronize do
|
75
|
+
case op_type
|
76
|
+
when SUBSCRIBE; @subscribed_tags |= tags
|
77
|
+
when SUBSCRIBE_ALL; @subscribed_all = true
|
78
|
+
when UNSUBSCRIBE; @subscribed_tags -= tags
|
79
|
+
when UNSUBSCRIBE_ALL; @subscribed_all = false
|
80
|
+
else raise ArgumentError
|
81
|
+
end
|
82
|
+
|
83
|
+
@cvar.broadcast
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def wait
|
88
|
+
until yield
|
89
|
+
@cvar.wait ## timeout?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/funl/version.rb
CHANGED
data/test/test-client.rb
CHANGED
@@ -10,7 +10,7 @@ include Funl
|
|
10
10
|
require 'minitest/autorun'
|
11
11
|
|
12
12
|
class TestClient < Minitest::Test
|
13
|
-
attr_reader :log
|
13
|
+
attr_reader :log, :client
|
14
14
|
|
15
15
|
def setup
|
16
16
|
@dir = Dir.mktmpdir "funl-test-client-"
|
@@ -18,33 +18,85 @@ class TestClient < Minitest::Test
|
|
18
18
|
@seq_path = File.join(@dir, "seq")
|
19
19
|
@log = Logger.new($stderr)
|
20
20
|
log.level = Logger::WARN
|
21
|
+
|
22
|
+
@cseq_sock = UNIXServer.new(@cseq_path)
|
23
|
+
@cseq = ClientSequencer.new @cseq_sock, log: log
|
24
|
+
@cseq.start
|
25
|
+
|
26
|
+
@seq_sock = UNIXServer.new(@seq_path)
|
27
|
+
@seq = MessageSequencer.new @seq_sock, log: log
|
28
|
+
@seq.start
|
29
|
+
|
30
|
+
@client = Client.new(
|
31
|
+
seq: UNIXSocket.new(@seq_path),
|
32
|
+
cseq: UNIXSocket.new(@cseq_path),
|
33
|
+
log: log)
|
34
|
+
|
35
|
+
@client.start
|
21
36
|
end
|
22
37
|
|
23
38
|
def teardown
|
39
|
+
cseq.stop rescue nil
|
40
|
+
seq.stop rescue nil
|
24
41
|
FileUtils.remove_entry @dir
|
25
42
|
end
|
26
43
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
44
|
+
def test_client_state_at_start
|
45
|
+
assert_equal(0, client.client_id)
|
46
|
+
assert_equal(0, client.start_tick)
|
47
|
+
assert_equal(Funl::Blobber::MSGPACK_TYPE, client.blob_type)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_subscription_tracking
|
51
|
+
Thread.new do
|
52
|
+
client.subscribe ["foo"]
|
53
|
+
end ## Fiber?
|
54
|
+
|
55
|
+
ack = client.seq.read
|
56
|
+
assert ack.control?
|
57
|
+
assert_equal 0, ack.global_tick
|
31
58
|
|
32
|
-
|
33
|
-
|
34
|
-
|
59
|
+
assert_equal [], client.subscribed_tags
|
60
|
+
client.handle_ack ack
|
61
|
+
Thread.pass
|
62
|
+
assert_equal ["foo"], client.subscribed_tags
|
63
|
+
|
64
|
+
Thread.new do
|
65
|
+
client.unsubscribe ["foo"]
|
66
|
+
end
|
67
|
+
|
68
|
+
ack = client.seq.read
|
69
|
+
assert ack.control?
|
70
|
+
assert_equal 0, ack.global_tick
|
35
71
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
72
|
+
assert_equal ["foo"], client.subscribed_tags
|
73
|
+
client.handle_ack ack
|
74
|
+
Thread.pass
|
75
|
+
assert_equal [], client.subscribed_tags
|
76
|
+
|
77
|
+
Thread.new do
|
78
|
+
client.subscribe_all
|
79
|
+
end
|
80
|
+
|
81
|
+
ack = client.seq.read
|
82
|
+
assert ack.control?
|
83
|
+
assert_equal 0, ack.global_tick
|
40
84
|
|
41
|
-
client.
|
85
|
+
assert_equal false, client.subscribed_all
|
86
|
+
client.handle_ack ack
|
87
|
+
Thread.pass
|
88
|
+
assert_equal true, client.subscribed_all
|
89
|
+
|
90
|
+
Thread.new do
|
91
|
+
client.unsubscribe_all
|
92
|
+
end
|
93
|
+
ack = client.seq.read
|
94
|
+
assert ack.control?
|
95
|
+
assert_equal 0, ack.global_tick
|
42
96
|
|
43
|
-
assert_equal
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
cseq.stop rescue nil
|
48
|
-
seq.stop rescue nil
|
97
|
+
assert_equal true, client.subscribed_all
|
98
|
+
client.handle_ack ack
|
99
|
+
Thread.pass
|
100
|
+
assert_equal false, client.subscribed_all
|
49
101
|
end
|
50
102
|
end
|
data/test/test-subscribe.rb
CHANGED
@@ -63,6 +63,7 @@ class TestSubscribe < Minitest::Test
|
|
63
63
|
ack = rcv.read
|
64
64
|
assert ack.control?
|
65
65
|
assert_equal 0, ack.global_tick
|
66
|
+
assert_equal ["foo", "bar"], ack.control_op[1]
|
66
67
|
|
67
68
|
snd << Message[
|
68
69
|
client: 0, local: 0, global: 0, delta: 1,
|
@@ -86,6 +87,7 @@ class TestSubscribe < Minitest::Test
|
|
86
87
|
ack = stream.read
|
87
88
|
assert ack.control?
|
88
89
|
assert_equal 0, ack.global_tick
|
90
|
+
assert_equal ["foo"], ack.control_op[1]
|
89
91
|
end
|
90
92
|
|
91
93
|
snd << Message[
|
@@ -105,6 +107,7 @@ class TestSubscribe < Minitest::Test
|
|
105
107
|
ack = rcv.read
|
106
108
|
assert ack.control?
|
107
109
|
assert_equal 0, ack.global_tick
|
110
|
+
assert_equal ["foo"], ack.control_op[1]
|
108
111
|
|
109
112
|
snd << Message[
|
110
113
|
client: 0, local: 0, global: 0, delta: 1,
|
@@ -115,17 +118,21 @@ class TestSubscribe < Minitest::Test
|
|
115
118
|
assert_equal ["foo"], m.tags
|
116
119
|
|
117
120
|
rcv << Message.control(UNSUBSCRIBE, ["foo"])
|
121
|
+
ack = rcv.read
|
122
|
+
assert ack.control?
|
123
|
+
assert_equal 1, ack.global_tick
|
124
|
+
assert_equal ["foo"], ack.control_op[1]
|
118
125
|
|
119
126
|
snd << Message[
|
120
127
|
client: 0, local: 0, global: 0, delta: 1,
|
121
128
|
tags: ["foo"], blob: ""]
|
122
|
-
|
123
|
-
sleep 0.2
|
129
|
+
Thread.pass
|
124
130
|
|
125
131
|
rcv << Message.control(SUBSCRIBE, ["foo"])
|
126
132
|
ack = rcv.read
|
127
133
|
assert ack.control?
|
128
134
|
assert_equal 2, ack.global_tick
|
135
|
+
assert_equal ["foo"], ack.control_op[1]
|
129
136
|
|
130
137
|
snd << Message[
|
131
138
|
client: 0, local: 0, global: 0, delta: 1,
|
@@ -142,6 +149,7 @@ class TestSubscribe < Minitest::Test
|
|
142
149
|
ack = rcv.read
|
143
150
|
assert ack.control?
|
144
151
|
assert_equal 0, ack.global_tick
|
152
|
+
assert_equal nil, ack.control_op[1]
|
145
153
|
|
146
154
|
snd << Message[
|
147
155
|
client: 0, local: 0, global: 0, delta: 1,
|
@@ -152,17 +160,21 @@ class TestSubscribe < Minitest::Test
|
|
152
160
|
assert_equal ["foo"], m.tags
|
153
161
|
|
154
162
|
rcv << Message.control(UNSUBSCRIBE_ALL)
|
163
|
+
ack = rcv.read
|
164
|
+
assert ack.control?
|
165
|
+
assert_equal 1, ack.global_tick
|
166
|
+
assert_equal nil, ack.control_op[1]
|
155
167
|
|
156
168
|
snd << Message[
|
157
169
|
client: 0, local: 0, global: 0, delta: 1,
|
158
170
|
tags: ["foo"], blob: ""]
|
159
|
-
|
160
|
-
sleep 0.2
|
171
|
+
Thread.pass
|
161
172
|
|
162
173
|
rcv << Message.control(SUBSCRIBE_ALL)
|
163
174
|
ack = rcv.read
|
164
175
|
assert ack.control?
|
165
176
|
assert_equal 2, ack.global_tick
|
177
|
+
assert_equal nil, ack.control_op[1]
|
166
178
|
|
167
179
|
snd << Message[
|
168
180
|
client: 0, local: 0, global: 0, delta: 1,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: funl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.4'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel VanderWerf
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-10-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: object-stream
|
@@ -37,6 +37,7 @@ files:
|
|
37
37
|
- Rakefile
|
38
38
|
- lib/funl/stream.rb
|
39
39
|
- lib/funl/blobber.rb
|
40
|
+
- lib/funl/subscription-tracker.rb
|
40
41
|
- lib/funl/history-client.rb
|
41
42
|
- lib/funl/history-worker.rb
|
42
43
|
- lib/funl/client.rb
|