zack 0.3.3 → 0.3.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.
- data/History.txt +8 -0
- data/Rakefile +7 -1
- data/lib/zack.rb +9 -0
- data/lib/zack/client.rb +23 -10
- data/lib/zack/server.rb +36 -15
- data/spec/acceptance/reconnect_handling_spec.rb +109 -0
- metadata +103 -11
data/History.txt
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
= 0.3.5 / ??? (next version)
|
2
|
+
|
3
|
+
= 0.3.4 / 23Feb2012
|
4
|
+
|
5
|
+
+ Reconnecting server
|
6
|
+
|
7
|
+
+ Client notifies called of lost connection, reconnects automatically.
|
8
|
+
|
1
9
|
= 0.3.3 / 29Nov2011
|
2
10
|
+ Updates to the cod rewrite, this eliminates the dependency on
|
3
11
|
beanstalk-client.
|
data/Rakefile
CHANGED
@@ -7,6 +7,9 @@ require 'rspec/core/rake_task'
|
|
7
7
|
RSpec::Core::RakeTask.new
|
8
8
|
task :default => :spec
|
9
9
|
|
10
|
+
require 'yard'
|
11
|
+
YARD::Rake::YardocTask.new
|
12
|
+
|
10
13
|
# This task actually builds the gem.
|
11
14
|
task :gem => :spec
|
12
15
|
spec = eval(File.read('zack.gemspec'))
|
@@ -14,4 +17,7 @@ spec = eval(File.read('zack.gemspec'))
|
|
14
17
|
desc "Generate the gem package."
|
15
18
|
Gem::PackageTask.new(spec) do |pkg|
|
16
19
|
# pkg.need_tar = true
|
17
|
-
end
|
20
|
+
end
|
21
|
+
|
22
|
+
CLEAN << 'doc'
|
23
|
+
CLEAN << 'pkg'
|
data/lib/zack.rb
CHANGED
@@ -6,6 +6,15 @@ module Zack
|
|
6
6
|
# timeout for a message that waits for an answer.
|
7
7
|
#
|
8
8
|
class ServiceTimeout < StandardError; end
|
9
|
+
|
10
|
+
# Gets raised when the connection to the beanstalk server is lost during a
|
11
|
+
# client call. In some cases you might not loose an answer, but you might
|
12
|
+
# have lost the request itself.
|
13
|
+
#
|
14
|
+
# The client object reconnects and will work as soon as the beanstalkd
|
15
|
+
# server comes back.
|
16
|
+
#
|
17
|
+
class AnswerLost < StandardError; end
|
9
18
|
end
|
10
19
|
|
11
20
|
require 'zack/unique_name'
|
data/lib/zack/client.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require 'timeout'
|
2
2
|
|
3
3
|
module Zack
|
4
4
|
# Client for a simple client-server RPC connection.
|
@@ -6,13 +6,14 @@ module Zack
|
|
6
6
|
class Client
|
7
7
|
attr_reader :service
|
8
8
|
|
9
|
-
# Constructs a client for the service given by tube_name.
|
10
|
-
# arguments are:
|
9
|
+
# Constructs a client for the service given by tube_name.
|
11
10
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
11
|
+
# @param tube_name [String] the tube to communicate with
|
12
|
+
# @option opts [String] :server beanstalkd server location url
|
13
|
+
# @option opts [Array<Symbol>] :only ignores all messages not in this hash
|
14
|
+
# @option opts [Array<Symbol>] :with_answer these messages wait for an
|
15
|
+
# answer from the service
|
16
|
+
# @option opts [Fixint] :timeout How long to wait for an answer
|
16
17
|
#
|
17
18
|
def initialize(tube_name, opts={})
|
18
19
|
# Only respond to these messages
|
@@ -26,15 +27,18 @@ module Zack
|
|
26
27
|
|
27
28
|
connect
|
28
29
|
end
|
29
|
-
|
30
|
+
|
31
|
+
# @private
|
30
32
|
def respond_to?(msg)
|
31
33
|
!! @only[msg]
|
32
34
|
end
|
33
|
-
|
35
|
+
|
36
|
+
# @private
|
34
37
|
def has_answer?(sym)
|
35
38
|
@with_answer.include?(sym.to_sym)
|
36
39
|
end
|
37
40
|
|
41
|
+
# @private
|
38
42
|
def method_missing(sym, *args, &block)
|
39
43
|
super unless respond_to?(sym)
|
40
44
|
|
@@ -48,8 +52,17 @@ module Zack
|
|
48
52
|
service.notify [sym, args]
|
49
53
|
return nil
|
50
54
|
end
|
55
|
+
|
56
|
+
rescue Cod::ConnectionLost
|
57
|
+
# Don't resend, just reconnect. This might loose one or two messages,
|
58
|
+
# but we don't make such guarantees at this protocol level.
|
59
|
+
reconnect
|
60
|
+
|
61
|
+
raise Zack::AnswerLost
|
62
|
+
|
51
63
|
rescue Timeout::Error => ex
|
52
|
-
raise Zack::ServiceTimeout,
|
64
|
+
raise Zack::ServiceTimeout,
|
65
|
+
"The service took too long to answer (>#{@timeout || 1}s)."
|
53
66
|
end
|
54
67
|
|
55
68
|
def close
|
data/lib/zack/server.rb
CHANGED
@@ -11,22 +11,19 @@ module Zack
|
|
11
11
|
# of the RPC call, you must either give the :factory or the :simple
|
12
12
|
# argument.
|
13
13
|
#
|
14
|
-
# :simple expects a class. This class will be constructed each time a
|
15
|
-
# request is made. Then the method will be called on the class.
|
16
|
-
#
|
17
|
-
# :factory expects a callable (a block or something that has #call) and is
|
18
|
-
# passed the control object for the request (see Cod for an explanation of
|
19
|
-
# this). You can chose to ignore the control and just use the block to
|
20
|
-
# produce an object that is linked to the rest of your program. Or you can
|
21
|
-
# link to the rest of the program and the control at the same time.
|
22
|
-
#
|
23
14
|
# Note that in any case, one object instance _per call_ is created. This
|
24
|
-
# is to discourage creating stateful servers.
|
25
|
-
# that, well you will just have to code around the limitation, now won't
|
26
|
-
# you.
|
15
|
+
# is to discourage creating stateful servers.
|
27
16
|
#
|
17
|
+
# @param tube_name [String] the tube to communicate with
|
18
|
+
# @option opts [Class] :simple class will be constructed for each request.
|
19
|
+
# The request will then be handed to the class.
|
20
|
+
# @option opts [#call] :factory factory for request handler instances. One
|
21
|
+
# parameter gets passed to this call, the control structure for the
|
22
|
+
# beanstalk connection.
|
23
|
+
#
|
28
24
|
def initialize(tube_name, opts={})
|
29
25
|
@server = opts[:server]
|
26
|
+
@tube_name = tube_name
|
30
27
|
|
31
28
|
if opts.has_key? :factory
|
32
29
|
@factory = opts[:factory]
|
@@ -36,12 +33,12 @@ module Zack
|
|
36
33
|
else
|
37
34
|
raise ArgumentError, "Either :factory or :simple argument must be given."
|
38
35
|
end
|
39
|
-
|
40
|
-
|
41
|
-
@service = channel.service
|
36
|
+
|
37
|
+
reconnect
|
42
38
|
end
|
43
39
|
|
44
40
|
# Handles exactly one request.
|
41
|
+
# @private
|
45
42
|
#
|
46
43
|
def handle_request(exception_handler=nil)
|
47
44
|
service.one { |(sym, args), control|
|
@@ -50,10 +47,18 @@ module Zack
|
|
50
47
|
process_request(control, sym, args)
|
51
48
|
end
|
52
49
|
}
|
50
|
+
|
51
|
+
# Reconnect when the connection is lost
|
52
|
+
rescue Cod::ConnectionLost
|
53
|
+
@channel.close
|
54
|
+
reconnect
|
55
|
+
|
56
|
+
retry
|
53
57
|
end
|
54
58
|
|
55
59
|
# Processes exactly one request, but doesn't define how the request gets
|
56
60
|
# here.
|
61
|
+
# @private
|
57
62
|
#
|
58
63
|
def process_request(control, sym, args)
|
59
64
|
instance = factory.call(control)
|
@@ -78,6 +83,10 @@ module Zack
|
|
78
83
|
# If you don't reraise exceptions from the exception handler block, they
|
79
84
|
# will be caught and the server will stay running.
|
80
85
|
#
|
86
|
+
# @param messages [Number] how many messages to process, or nil for endless
|
87
|
+
# operation
|
88
|
+
# @yield [Exception, Cod::Beanstalk::Service::Control]
|
89
|
+
#
|
81
90
|
def run(messages=nil, &exception_handler)
|
82
91
|
loop do
|
83
92
|
handle_request(exception_handler)
|
@@ -89,12 +98,19 @@ module Zack
|
|
89
98
|
end
|
90
99
|
end
|
91
100
|
|
101
|
+
# Closes the connection and shuts down the server.
|
102
|
+
#
|
103
|
+
def close
|
104
|
+
@channel.close
|
105
|
+
end
|
106
|
+
|
92
107
|
private
|
93
108
|
# Defines how the server handles exception.
|
94
109
|
#
|
95
110
|
def exception_handling(exception_handler, control)
|
96
111
|
begin
|
97
112
|
yield
|
113
|
+
|
98
114
|
rescue => exception
|
99
115
|
# If we have an exception handler, it gets handed all the exceptions.
|
100
116
|
# No exceptions stop the operation.
|
@@ -105,5 +121,10 @@ module Zack
|
|
105
121
|
end
|
106
122
|
end
|
107
123
|
end
|
124
|
+
|
125
|
+
def reconnect
|
126
|
+
@channel = Cod.beanstalk(@tube_name, @server)
|
127
|
+
@service = @channel.service
|
128
|
+
end
|
108
129
|
end
|
109
130
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'tcp_proxy'
|
4
|
+
|
5
|
+
describe "Connection interruption:" do
|
6
|
+
# Clear old messages (test isolation)
|
7
|
+
before(:each) {
|
8
|
+
conn = Beanstalk::Connection.new(BEANSTALK_CONNECTION, 'reconnect')
|
9
|
+
while conn.peek_ready
|
10
|
+
conn.reserve.delete
|
11
|
+
end
|
12
|
+
conn.close
|
13
|
+
}
|
14
|
+
|
15
|
+
class SimpleFlagServer
|
16
|
+
def signal
|
17
|
+
@signalled = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def clear
|
21
|
+
@signalled = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def signalled?
|
25
|
+
@signalled
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def sub_port(connstr, new_port)
|
30
|
+
parts = BEANSTALK_CONNECTION.split(':')
|
31
|
+
parts[1] = new_port
|
32
|
+
parts.join(':')
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:flag_server) { SimpleFlagServer.new }
|
36
|
+
let(:client) { Zack::Client.new('reconnect', server: BEANSTALK_CONNECTION) }
|
37
|
+
let(:server) { Zack::Server.new('reconnect',
|
38
|
+
server: BEANSTALK_CONNECTION,
|
39
|
+
factory: proc { |c| flag_server }) }
|
40
|
+
|
41
|
+
after(:each) { client.close; server.close }
|
42
|
+
|
43
|
+
it "smoke test to verify the setup" do
|
44
|
+
client.signal
|
45
|
+
server.run(1)
|
46
|
+
|
47
|
+
flag_server.should be_signalled
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'when the connection between the client and the beanstalkd server disappears' do
|
51
|
+
let!(:proxy) { TCPProxy.new('localhost', 11301, 11300) }
|
52
|
+
after(:each) { proxy.close }
|
53
|
+
|
54
|
+
let(:client) { Zack::Client.new('reconnect',
|
55
|
+
server: sub_port(BEANSTALK_CONNECTION, 11301)) }
|
56
|
+
|
57
|
+
# Try out the connection before interrupting it
|
58
|
+
before(:each) {
|
59
|
+
client.signal
|
60
|
+
server.run(1)
|
61
|
+
|
62
|
+
flag_server.should be_signalled
|
63
|
+
flag_server.clear
|
64
|
+
}
|
65
|
+
|
66
|
+
it "reconnects automatically" do
|
67
|
+
proxy.drop_all
|
68
|
+
|
69
|
+
expect {
|
70
|
+
client.signal
|
71
|
+
}.to raise_error(Zack::AnswerLost)
|
72
|
+
|
73
|
+
# The client should have reconnected, so this should now work:
|
74
|
+
client.signal
|
75
|
+
server.run(1)
|
76
|
+
|
77
|
+
flag_server.should be_signalled
|
78
|
+
end
|
79
|
+
end
|
80
|
+
describe 'when the connection between the server and the beanstalkd server disappears' do
|
81
|
+
let!(:proxy) { TCPProxy.new('localhost', 11301, 11300) }
|
82
|
+
after(:each) { proxy.close }
|
83
|
+
|
84
|
+
let(:server) { Zack::Server.new('reconnect',
|
85
|
+
server: sub_port(BEANSTALK_CONNECTION, 11301),
|
86
|
+
factory: proc { |c| flag_server }) }
|
87
|
+
|
88
|
+
# Try out the connection before interrupting it
|
89
|
+
before(:each) {
|
90
|
+
client.signal
|
91
|
+
server.run(1)
|
92
|
+
|
93
|
+
flag_server.should be_signalled
|
94
|
+
|
95
|
+
flag_server.clear
|
96
|
+
}
|
97
|
+
|
98
|
+
it "reconnects automatically" do
|
99
|
+
# Post one message to the queue
|
100
|
+
client.signal
|
101
|
+
proxy.drop_all
|
102
|
+
|
103
|
+
# Try to process a message (connection is interrupted)
|
104
|
+
server.run(1)
|
105
|
+
|
106
|
+
flag_server.should be_signalled
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,22 +10,22 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2012-02-28 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: cod
|
17
|
-
requirement: &
|
17
|
+
requirement: &70158540161560 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ~>
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version:
|
22
|
+
version: 0.4.3
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70158540161560
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: uuid
|
28
|
-
requirement: &
|
28
|
+
requirement: &70158540160960 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ~>
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: '2.3'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70158540160960
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: beanstalk-client
|
39
|
-
requirement: &
|
39
|
+
requirement: &70158540160360 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
@@ -44,7 +44,95 @@ dependencies:
|
|
44
44
|
version: '1.0'
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70158540160360
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rake
|
50
|
+
requirement: &70158540159960 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *70158540159960
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: rspec
|
61
|
+
requirement: &70158540159360 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *70158540159360
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: flexmock
|
72
|
+
requirement: &70158540158900 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
type: :development
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: *70158540158900
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: guard
|
83
|
+
requirement: &70158540158440 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: *70158540158440
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: guard-rspec
|
94
|
+
requirement: &70158540158020 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
type: :development
|
101
|
+
prerelease: false
|
102
|
+
version_requirements: *70158540158020
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: growl
|
105
|
+
requirement: &70158540174660 !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: *70158540174660
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: rb-fsevent
|
116
|
+
requirement: &70158540174180 !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
type: :development
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: *70158540174180
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: &70158540173560 !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: *70158540173560
|
48
136
|
description:
|
49
137
|
email:
|
50
138
|
- kaspar.schiess@absurd.li
|
@@ -63,6 +151,7 @@ files:
|
|
63
151
|
- lib/zack/unique_name.rb
|
64
152
|
- lib/zack.rb
|
65
153
|
- spec/acceptance/exception_handling_spec.rb
|
154
|
+
- spec/acceptance/reconnect_handling_spec.rb
|
66
155
|
- spec/acceptance/regression_spec.rb
|
67
156
|
- spec/acceptance/server_runner_spec.rb
|
68
157
|
- spec/lib/zack/client_spec.rb
|
@@ -86,16 +175,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
175
|
version: '0'
|
87
176
|
segments:
|
88
177
|
- 0
|
89
|
-
hash:
|
178
|
+
hash: -4472510661863580508
|
90
179
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
180
|
none: false
|
92
181
|
requirements:
|
93
182
|
- - ! '>='
|
94
183
|
- !ruby/object:Gem::Version
|
95
184
|
version: '0'
|
185
|
+
segments:
|
186
|
+
- 0
|
187
|
+
hash: -4472510661863580508
|
96
188
|
requirements: []
|
97
189
|
rubyforge_project:
|
98
|
-
rubygems_version: 1.8.
|
190
|
+
rubygems_version: 1.8.16
|
99
191
|
signing_key:
|
100
192
|
specification_version: 3
|
101
193
|
summary: Ruby RPC calls via Cod
|