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 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