zack 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'
@@ -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'
@@ -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. Optional
10
- # arguments are:
9
+ # Constructs a client for the service given by tube_name.
11
10
  #
12
- # :server :: beanstalkd server location url
13
- # :only :: ignores all messages not in this hash
14
- # :with_answer :: these messages wait for an answer from the service
15
- # :timeout :: How long to wait for an answer
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, "The service took too long to answer (>#{@timeout || 1}s)."
64
+ raise Zack::ServiceTimeout,
65
+ "The service took too long to answer (>#{@timeout || 1}s)."
53
66
  end
54
67
 
55
68
  def close
@@ -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. If you still want to do
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
- channel = Cod.beanstalk(tube_name, server)
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.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: 2011-11-29 00:00:00.000000000Z
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: &70152213379200 !ruby/object:Gem::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: '0.4'
22
+ version: 0.4.3
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70152213379200
25
+ version_requirements: *70158540161560
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: uuid
28
- requirement: &70152213378720 !ruby/object:Gem::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: *70152213378720
36
+ version_requirements: *70158540160960
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: beanstalk-client
39
- requirement: &70152213378240 !ruby/object:Gem::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: *70152213378240
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: 2009193566488392526
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.10
190
+ rubygems_version: 1.8.16
99
191
  signing_key:
100
192
  specification_version: 3
101
193
  summary: Ruby RPC calls via Cod