funl 0.2 → 0.3

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: bef263a447c026a1fdf3e7597c0e36b9b2d6c0ab
4
- data.tar.gz: af313f5626ae330a3392ee84824074303f6dc141
3
+ metadata.gz: cbb42a88d076acb7f545c98acbeeae62d94accae
4
+ data.tar.gz: f78b9b4f60684598d01b387fb56d57e4320d3c1c
5
5
  SHA512:
6
- metadata.gz: 832827e2caa92174648705c8a406b61a8b1d4b4847e543cf8b0a36598ee6cbfb3c6c524f34c696ab7508fd6571607026614e12298cdb6ef80d0c4732041b9f0f
7
- data.tar.gz: b3a1d31d1af4122f644c7543e803b5ce612f2cdf3929372c365522948cac93b91a1eaaf8491b8929fb88c2f951669fff0f65d2d0c75e958151086387484d02a9
6
+ metadata.gz: 6735b03e0e9a8833dcfbad3ef8ad1ad139b06b12483d3d525b50fd24df4c0ec42f5f8c198eaf19e0f16d3a4bdba1958a72a1bae3279717bebcad68ecd2bfe085
7
+ data.tar.gz: 71d767408b0eaa9e1d1f818cf45271d23e8a44ab262bd0f70a413de35cbb17cf229b4d480abb3d6c9996cfae3b9a3cb76bfa08a09aa0c8673fc4aa843a37656f
data/Rakefile CHANGED
@@ -1,12 +1,19 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
3
 
4
+ PRJ = "funl"
5
+
4
6
  def version
5
- require 'funl/version'
6
- @version ||= Funl::VERSION
7
+ @version ||= begin
8
+ require 'funl/version'
9
+ warn "Funl::VERSION not a string" unless Funl::VERSION.kind_of? String
10
+ Funl::VERSION
11
+ end
7
12
  end
8
13
 
9
- prj = "funl"
14
+ def tag
15
+ @tag ||= "#{PRJ}-#{version}"
16
+ end
10
17
 
11
18
  desc "Run tests"
12
19
  Rake::TestTask.new :test do |t|
@@ -15,16 +22,43 @@ Rake::TestTask.new :test do |t|
15
22
  t.test_files = FileList["test/**/*.rb"]
16
23
  end
17
24
 
18
- desc "commit, tag, and push repo; build and push gem"
19
- task :release do
20
- tag = "#{prj}-#{version}"
25
+ desc "Commit, tag, and push repo; build and push gem"
26
+ task :release => "release:is_new_version" do
27
+ require 'tempfile'
28
+
29
+ sh "gem build #{PRJ}.gemspec"
21
30
 
22
- sh "gem build #{prj}.gemspec"
31
+ file = Tempfile.new "template"
32
+ begin
33
+ file.puts "release #{version}"
34
+ file.close
35
+ sh "git commit --allow-empty -a -v -t #{file.path}"
36
+ ensure
37
+ file.close unless file.closed?
38
+ file.unlink
39
+ end
23
40
 
24
- sh "git commit -a -m 'release #{version}'"
25
- sh "git tag #{prj}-#{version}"
41
+ sh "git tag #{tag}"
26
42
  sh "git push"
27
43
  sh "git push --tags"
28
44
 
29
- sh "gem push #{prj}-#{version}.gem"
45
+ sh "gem push #{tag}.gem"
46
+ end
47
+
48
+ namespace :release do
49
+ desc "Diff to latest release"
50
+ task :diff do
51
+ latest = `git describe --abbrev=0 --tags --match '#{PRJ}-*'`.chomp
52
+ sh "git diff #{latest}"
53
+ end
54
+
55
+ desc "Log to latest release"
56
+ task :log do
57
+ latest = `git describe --abbrev=0 --tags --match '#{PRJ}-*'`.chomp
58
+ sh "git log #{latest}.."
59
+ end
60
+
61
+ task :is_new_version do
62
+ abort "#{tag} exists; update version!" unless `git tag -l #{tag}`.empty?
63
+ end
30
64
  end
data/lib/funl/client.rb CHANGED
@@ -44,6 +44,24 @@ module Funl
44
44
  seq_read_greeting
45
45
  end
46
46
 
47
+ def subscribe tags
48
+ seq << Message.control(SUBSCRIBE, tags)
49
+ ## wait for ack which has tick, ask arc for older messages
50
+ end
51
+
52
+ def subscribe_all
53
+ seq << Message.control(SUBSCRIBE_ALL)
54
+ ## wait for ack which has tick, ask arc for older messages
55
+ end
56
+
57
+ def unsubscribe tags
58
+ seq << Message.control(UNSUBSCRIBE, tags)
59
+ end
60
+
61
+ def unsubscribe_all
62
+ seq << Message.control(UNSUBSCRIBE_ALL)
63
+ end
64
+
47
65
  def cseq_read_client_id
48
66
  log.debug "getting client_id from cseq"
49
67
  @client_id = cseq.read["client_id"]
@@ -18,6 +18,7 @@ module Funl
18
18
  attr_reader :message_class
19
19
  attr_reader :blob_type
20
20
  attr_reader :greeting
21
+ attr_reader :subscribers
21
22
 
22
23
  def initialize server, *conns, log: Logger.new($stderr),
23
24
  stream_type: ObjectStream::MSGPACK_TYPE,
@@ -37,6 +38,10 @@ module Funl
37
38
  conns.each do |conn|
38
39
  try_conn conn
39
40
  end
41
+
42
+ @subscribers_to_all = [] # [conn, ...]
43
+ @subscribers = Hash.new {|h, tag| h[tag] = []} # tag => [conn, ...]
44
+ @tags = Hash.new {|h, conn| h[conn] = []} # conn => [tag, ...]
40
45
  end
41
46
 
42
47
  def default_greeting
@@ -49,8 +54,8 @@ module Funl
49
54
  stream = message_server_stream_for(conn)
50
55
  current_greeting = greeting.merge({"tick" => tick})
51
56
  if write_succeeds?(current_greeting, stream)
52
- log.debug "connected #{stream.inspect}"
53
- @streams << stream
57
+ log.debug {"connected #{stream.inspect}"}
58
+ streams << stream
54
59
  end
55
60
  end
56
61
  private :try_conn
@@ -78,7 +83,7 @@ module Funl
78
83
  when server
79
84
  begin
80
85
  conn, addr = readable.accept_nonblock
81
- log.debug "accepted #{conn.inspect} from #{addr.inspect}"
86
+ log.debug {"accepted #{conn.inspect} from #{addr.inspect}"}
82
87
  try_conn conn
83
88
  rescue IO::WaitReadable
84
89
  next
@@ -93,15 +98,18 @@ module Funl
93
98
  end
94
99
  rescue IOError, SystemCallError => ex
95
100
  log.debug {"closing #{readable}: #{ex}"}
96
- @streams.delete readable
97
- readable.close unless readable.closed?
101
+ reject_stream readable
98
102
  else
99
103
  log.debug {
100
104
  "read #{msgs.size} messages from #{readable.peer_name}"}
101
105
  end
102
106
 
103
107
  msgs.each do |msg|
104
- handle_message msg
108
+ if msg.control?
109
+ handle_control readable, *msg.control_op
110
+ else
111
+ handle_message msg
112
+ end
105
113
  end
106
114
  end
107
115
  end
@@ -111,12 +119,56 @@ module Funl
111
119
  raise
112
120
  end
113
121
 
122
+ def handle_control stream, op_type, tags = nil
123
+ log.debug {"#{stream} #{op_type} #{tags}"}
124
+
125
+ case op_type
126
+ when SUBSCRIBE_ALL
127
+ @subscribers_to_all += [stream]
128
+ ack = Message.control(op_type)
129
+ ack.global_tick = tick
130
+ write_succeeds?(ack, stream)
131
+
132
+ when SUBSCRIBE
133
+ tags.each do |tag|
134
+ @subscribers[tag] += [stream]
135
+ end
136
+ @tags[stream] += tags
137
+ ack = Message.control(op_type, tags)
138
+ ack.global_tick = tick
139
+ write_succeeds?(ack, stream)
140
+
141
+ when UNSUBSCRIBE_ALL
142
+ @subscribers_to_all.delete stream
143
+
144
+ when UNSUBSCRIBE
145
+ tags.each do |tag|
146
+ @subscribers[tag].delete stream
147
+ end
148
+ @tags[stream] -= tags
149
+
150
+ else
151
+ log.error "bad operation: #{op_type.inspect}"
152
+ return
153
+ end
154
+ end
155
+
114
156
  def handle_message msg
115
157
  log.debug {"handling message #{msg.inspect}"}
158
+
116
159
  @tick += 1
117
160
  msg.global_tick = tick
118
161
  msg.delta = nil
119
- @streams.keep_if do |stream|
162
+
163
+ tags = msg.tags
164
+ dest_streams =
165
+ if !tags or (tags.empty? rescue true)
166
+ @subscribers_to_all.dup
167
+ else
168
+ tags.inject(@subscribers_to_all) {|a,tag| a + @subscribers[tag]}
169
+ end
170
+
171
+ dest_streams.each do |stream|
120
172
  write_succeeds? msg, stream
121
173
  end
122
174
  end
@@ -127,9 +179,23 @@ module Funl
127
179
  true
128
180
  rescue IOError, SystemCallError => ex
129
181
  log.debug {"closing #{stream}: #{ex}"}
130
- stream.close unless stream.closed?
182
+ reject_stream stream
131
183
  false
132
184
  end
133
185
  private :write_succeeds?
134
186
  end
187
+
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
197
+ end
198
+ end
199
+ end
200
+ end
135
201
  end
data/lib/funl/message.rb CHANGED
@@ -29,6 +29,20 @@ module Funl
29
29
  client: nil, local: nil, global: nil, delta: nil, tags: nil, blob: nil)
30
30
  new client, local, global, delta, tags, blob
31
31
  end
32
+
33
+ def self.control op_type, *args
34
+ Message.new.tap {|m| m.client_id = [op_type, *args]}
35
+ end
36
+
37
+ # Is this a control packet rather than a data packet?
38
+ def control?
39
+ @client_id.kind_of? Array
40
+ end
41
+
42
+ # Array of [op_type, *args] for the control operation.
43
+ def control_op
44
+ @client_id
45
+ end
32
46
 
33
47
  def inspect
34
48
  d = delta ? "+#{delta}" : nil
data/lib/funl/stream.rb CHANGED
@@ -1,6 +1,11 @@
1
1
  require 'object-stream-wrapper'
2
2
 
3
3
  module Funl
4
+ SUBSCRIBE = "subscribe".freeze
5
+ UNSUBSCRIBE = "unsubscribe".freeze
6
+ SUBSCRIBE_ALL = "subscribe_all".freeze
7
+ UNSUBSCRIBE_ALL = "unsubscribe_all".freeze
8
+
4
9
  # Mixin depends on stream_type, log, client_id, message_class.
5
10
  module Stream
6
11
  def client_stream_for io, type: stream_type
data/lib/funl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Funl
2
- VERSION = "0.2"
2
+ VERSION = "0.3"
3
3
  end
@@ -47,9 +47,16 @@ class TestMessageSequencer < Minitest::Test
47
47
  streams = (0...@n_clients).map do
48
48
  conn = UNIXSocket.new(@path)
49
49
  stream = ObjectStreamWrapper.new(conn, type: stream_type)
50
- stream.write_to_outbox{{"client_id" => "test_later_conns"}}
50
+ stream.write_to_outbox({"client_id" => "test_later_conns"})
51
+ stream.write(Message.control(SUBSCRIBE_ALL))
51
52
  global_tick = stream.read["tick"]
52
53
  assert_equal 0, global_tick
54
+
55
+ stream.expect Message
56
+ ack = stream.read
57
+ assert ack.control?
58
+ assert_equal 0, ack.global_tick
59
+
53
60
  stream
54
61
  end
55
62
 
@@ -73,7 +80,6 @@ class TestMessageSequencer < Minitest::Test
73
80
  src << message
74
81
 
75
82
  replies = dst.map do |stream|
76
- stream.expect Message
77
83
  stream.read
78
84
  end
79
85
 
@@ -103,10 +109,16 @@ class TestMessageSequencer < Minitest::Test
103
109
 
104
110
  conn = UNIXSocket.new(path)
105
111
  stream = ObjectStreamWrapper.new(conn, type: mseq.stream_type)
106
- stream.write_to_outbox{{"client_id" => "test_persist"}} # not needed
112
+ stream.write_to_outbox({"client_id" => "test_persist"}) # not needed
113
+ stream.write(Message.control(SUBSCRIBE_ALL))
107
114
  tick = stream.read["tick"]
108
115
  assert_equal n_write, tick
109
116
 
117
+ stream.expect Message
118
+ ack = stream.read
119
+ assert ack.control?
120
+ assert_equal n_write, ack.global_tick
121
+
110
122
  stream.write Message.new
111
123
  stream.read
112
124
  n_write += 1
@@ -0,0 +1,175 @@
1
+ require 'funl/message-sequencer'
2
+ require 'socket'
3
+
4
+ include Funl
5
+
6
+ require 'minitest/autorun'
7
+
8
+ class TestSubscribe < Minitest::Test
9
+ attr_reader :log, :mseq
10
+
11
+ def setup
12
+ @log = Logger.new($stderr)
13
+ log.level = Logger::WARN
14
+
15
+ client_socks = []
16
+ server_socks = []
17
+ 2.times do
18
+ cl, sv = UNIXSocket.pair
19
+ client_socks << cl
20
+ server_socks << sv
21
+ end
22
+
23
+ dummy, _ = UNIXSocket.pair
24
+
25
+ @mseq = MessageSequencer.new dummy, *server_socks, log: log
26
+ mseq.start
27
+
28
+ @streams = client_socks.each_with_index.map do |s,i|
29
+ stream = ObjectStreamWrapper.new(s, type: mseq.stream_type)
30
+ stream.write_to_outbox({"client_id" => "client #{i}"})
31
+ global_tick = stream.read["tick"]
32
+ stream.expect Message
33
+ stream
34
+ end
35
+ end
36
+
37
+ def teardown
38
+ mseq.stop rescue nil
39
+ end
40
+
41
+ def test_single_tag
42
+ snd, rcv = @streams
43
+ rcv << Message.control(SUBSCRIBE, ["foo"])
44
+ ack = rcv.read
45
+ assert ack.control?
46
+ assert_equal 0, ack.global_tick
47
+
48
+ snd << Message[
49
+ client: 0, local: 0, global: 0, delta: 1,
50
+ tags: ["bar"], blob: ""]
51
+ snd << Message[
52
+ client: 0, local: 0, global: 0, delta: 2,
53
+ tags: ["foo"], blob: ""]
54
+
55
+ m = rcv.read
56
+ assert_equal 2, m.global_tick
57
+ assert_equal ["foo"], m.tags
58
+ end
59
+
60
+ def test_multiple_tag
61
+ snd, rcv = @streams
62
+ rcv << Message.control(SUBSCRIBE, ["foo", "bar"])
63
+ ack = rcv.read
64
+ assert ack.control?
65
+ assert_equal 0, ack.global_tick
66
+
67
+ snd << Message[
68
+ client: 0, local: 0, global: 0, delta: 1,
69
+ tags: ["bar"], blob: ""]
70
+ snd << Message[
71
+ client: 0, local: 0, global: 0, delta: 2,
72
+ tags: ["foo"], blob: ""]
73
+
74
+ m = rcv.read
75
+ assert_equal 1, m.global_tick
76
+ assert_equal ["bar"], m.tags
77
+ m = rcv.read
78
+ assert_equal 2, m.global_tick
79
+ assert_equal ["foo"], m.tags
80
+ end
81
+
82
+ def test_multiple_receiver
83
+ snd, rcv = @streams
84
+ @streams.each do |stream|
85
+ stream << Message.control(SUBSCRIBE, ["foo"])
86
+ ack = stream.read
87
+ assert ack.control?
88
+ assert_equal 0, ack.global_tick
89
+ end
90
+
91
+ snd << Message[
92
+ client: 0, local: 0, global: 0, delta: 1,
93
+ tags: ["foo"], blob: ""]
94
+
95
+ @streams.each do |stream|
96
+ m = stream.read
97
+ assert_equal 1, m.global_tick
98
+ assert_equal ["foo"], m.tags
99
+ end
100
+ end
101
+
102
+ def test_unsubscribe
103
+ snd, rcv = @streams
104
+ rcv << Message.control(SUBSCRIBE, ["foo"])
105
+ ack = rcv.read
106
+ assert ack.control?
107
+ assert_equal 0, ack.global_tick
108
+
109
+ snd << Message[
110
+ client: 0, local: 0, global: 0, delta: 1,
111
+ tags: ["foo"], blob: ""]
112
+
113
+ m = rcv.read
114
+ assert_equal 1, m.global_tick
115
+ assert_equal ["foo"], m.tags
116
+
117
+ rcv << Message.control(UNSUBSCRIBE, ["foo"])
118
+
119
+ snd << Message[
120
+ client: 0, local: 0, global: 0, delta: 1,
121
+ tags: ["foo"], blob: ""]
122
+
123
+ sleep 0.2
124
+
125
+ rcv << Message.control(SUBSCRIBE, ["foo"])
126
+ ack = rcv.read
127
+ assert ack.control?
128
+ assert_equal 2, ack.global_tick
129
+
130
+ snd << Message[
131
+ client: 0, local: 0, global: 0, delta: 1,
132
+ tags: ["foo"], blob: ""]
133
+
134
+ m = rcv.read
135
+ assert_equal 3, m.global_tick
136
+ assert_equal ["foo"], m.tags
137
+ end
138
+
139
+ def test_subscribe_all
140
+ snd, rcv = @streams
141
+ rcv << Message.control(SUBSCRIBE_ALL)
142
+ ack = rcv.read
143
+ assert ack.control?
144
+ assert_equal 0, ack.global_tick
145
+
146
+ snd << Message[
147
+ client: 0, local: 0, global: 0, delta: 1,
148
+ tags: ["foo"], blob: ""]
149
+
150
+ m = rcv.read
151
+ assert_equal 1, m.global_tick
152
+ assert_equal ["foo"], m.tags
153
+
154
+ rcv << Message.control(UNSUBSCRIBE_ALL)
155
+
156
+ snd << Message[
157
+ client: 0, local: 0, global: 0, delta: 1,
158
+ tags: ["foo"], blob: ""]
159
+
160
+ sleep 0.2
161
+
162
+ rcv << Message.control(SUBSCRIBE_ALL)
163
+ ack = rcv.read
164
+ assert ack.control?
165
+ assert_equal 2, ack.global_tick
166
+
167
+ snd << Message[
168
+ client: 0, local: 0, global: 0, delta: 1,
169
+ tags: ["foo"], blob: ""]
170
+
171
+ m = rcv.read
172
+ assert_equal 3, m.global_tick
173
+ assert_equal ["foo"], m.tags
174
+ end
175
+ end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: funl
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.2'
4
+ version: '0.3'
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-07-17 00:00:00.000000000 Z
11
+ date: 2013-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: object-stream
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  description: Sequences messages.
@@ -48,6 +48,7 @@ files:
48
48
  - test/test-client-sequencer.rb
49
49
  - test/test-message.rb
50
50
  - test/test-stream.rb
51
+ - test/test-subscribe.rb
51
52
  - test/test-client.rb
52
53
  homepage: https://github.com/vjoel/funl
53
54
  licenses:
@@ -55,28 +56,28 @@ licenses:
55
56
  metadata: {}
56
57
  post_install_message:
57
58
  rdoc_options:
58
- - --quiet
59
- - --line-numbers
60
- - --inline-source
61
- - --title
59
+ - "--quiet"
60
+ - "--line-numbers"
61
+ - "--inline-source"
62
+ - "--title"
62
63
  - funl
63
- - --main
64
+ - "--main"
64
65
  - README.md
65
66
  require_paths:
66
67
  - lib
67
68
  required_ruby_version: !ruby/object:Gem::Requirement
68
69
  requirements:
69
- - - '>='
70
+ - - ">="
70
71
  - !ruby/object:Gem::Version
71
72
  version: '0'
72
73
  required_rubygems_version: !ruby/object:Gem::Requirement
73
74
  requirements:
74
- - - '>='
75
+ - - ">="
75
76
  - !ruby/object:Gem::Version
76
77
  version: '0'
77
78
  requirements: []
78
79
  rubyforge_project:
79
- rubygems_version: 2.0.4
80
+ rubygems_version: 2.1.4
80
81
  signing_key:
81
82
  specification_version: 4
82
83
  summary: Sequences messages
@@ -85,5 +86,6 @@ test_files:
85
86
  - test/test-client-sequencer.rb
86
87
  - test/test-message.rb
87
88
  - test/test-stream.rb
89
+ - test/test-subscribe.rb
88
90
  - test/test-client.rb
89
91
  has_rdoc: