funl 0.2 → 0.3

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