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.
@@ -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