zack 0.3.3 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|