funl 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cbb42a88d076acb7f545c98acbeeae62d94accae
4
- data.tar.gz: f78b9b4f60684598d01b387fb56d57e4320d3c1c
3
+ metadata.gz: 6ac4ecb6a3ca14c04c14cc5af85e34ba3d421123
4
+ data.tar.gz: a9a6a618dfb48a4baed545c71767b5ac544cba98
5
5
  SHA512:
6
- metadata.gz: 6735b03e0e9a8833dcfbad3ef8ad1ad139b06b12483d3d525b50fd24df4c0ec42f5f8c198eaf19e0f16d3a4bdba1958a72a1bae3279717bebcad68ecd2bfe085
7
- data.tar.gz: 71d767408b0eaa9e1d1f818cf45271d23e8a44ab262bd0f70a413de35cbb17cf229b4d480abb3d6c9996cfae3b9a3cb76bfa08a09aa0c8673fc4aa843a37656f
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
- seq << Message.control(SUBSCRIBE, tags)
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
- seq << Message.control(SUBSCRIBE_ALL)
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
- seq << Message.control(UNSUBSCRIBE, tags)
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
- seq << Message.control(UNSUBSCRIBE_ALL)
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
- def reject_stream stream
189
- stream.close unless stream.closed?
190
- if streams.include? stream
191
- streams.delete stream
192
- @subscribers_to_all.delete stream
193
- tags = @tags.delete stream
194
- if tags
195
- tags.each do |tag|
196
- @subscribers[tag].delete stream
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
@@ -1,3 +1,3 @@
1
1
  module Funl
2
- VERSION = "0.3"
2
+ VERSION = "0.4"
3
3
  end
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 test_client
28
- cseq_sock = UNIXServer.new(@cseq_path)
29
- cseq = ClientSequencer.new cseq_sock, log: log
30
- cseq.start
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
- seq_sock = UNIXServer.new(@seq_path)
33
- seq = MessageSequencer.new seq_sock, log: log
34
- seq.start
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
- client = Client.new(
37
- seq: UNIXSocket.new(@seq_path),
38
- cseq: UNIXSocket.new(@cseq_path),
39
- log: log)
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.start
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(0, client.client_id)
44
- assert_equal(0, client.start_tick)
45
- assert_equal(Funl::Blobber::MSGPACK_TYPE, client.blob_type)
46
- ensure
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
@@ -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.3'
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-09-24 00:00:00.000000000 Z
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