zack 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
+ = 0.3.2 / 31Aug2011
2
+
3
+ . gemspec fix
4
+
1
5
  = 0.3.1 / 31Aug2011
2
6
 
3
7
  . small refactorings
data/example/client.rb ADDED
@@ -0,0 +1,7 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+ require 'zack'
3
+
4
+ client = Zack::Client.new('sample', :with_answer => [:get_time])
5
+
6
+ client.announce
7
+ puts client.get_time
data/example/pubsub.rb ADDED
@@ -0,0 +1,35 @@
1
+
2
+ class Handler
3
+ def foo
4
+ puts "Foo was called."
5
+ end
6
+ def bar
7
+ Process.pid
8
+ end
9
+ def shutdown
10
+ exit 0
11
+ end
12
+ end
13
+
14
+ source = Zack::Notifier.new(
15
+ 'football',
16
+ server: 'localhost:11300',
17
+ with_answer: [:bar])
18
+
19
+ %w(foo bar).each do |filter|
20
+ fork do
21
+ handler = Zack::Listener.new(
22
+ 'football',
23
+ simple: Handler,
24
+ server: 'localhost:11300')
25
+
26
+ handler.run
27
+ end
28
+ end
29
+
30
+ source.foo
31
+ p source.bar
32
+
33
+ source.shutdown
34
+
35
+ Process.waitall
data/example/server.rb ADDED
@@ -0,0 +1,16 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+ require 'zack'
3
+
4
+ class ChunkyBaconAnnouncer
5
+ def announce
6
+ puts 'chunky bacon'
7
+ end
8
+ def get_time
9
+ "I wouldn't give you the time"
10
+ end
11
+ end
12
+
13
+ Zack::Server.new(
14
+ 'sample',
15
+ :simple => ChunkyBaconAnnouncer,
16
+ ).run
@@ -0,0 +1,68 @@
1
+
2
+ module Zack
3
+ # Abstract base class for everything that is an RPC target. This implements
4
+ # some common mechanisms like a run loop, exception handling and argument
5
+ # handling.
6
+ #
7
+ class Target
8
+ attr_reader :factory
9
+ attr_reader :server
10
+
11
+ # Initializes #factory and #server.
12
+ #
13
+ def initialize(tube_name, opts={})
14
+ @server = opts[:server] || 'beanstalk:11300'
15
+
16
+ if opts.has_key? :factory
17
+ @factory = opts[:factory]
18
+ elsif opts.has_key? :simple
19
+ klass = opts[:simple]
20
+ @factory = lambda { klass.new }
21
+ else
22
+ raise ArgumentError, "Either :factory or :simple argument must be given."
23
+ end
24
+ end
25
+
26
+ # Processes exactly one request, but doesn't define how the request gets
27
+ # here.
28
+ #
29
+ def process_request(sym, args)
30
+ instance = factory.call
31
+
32
+ instance.send(sym, *args)
33
+ end
34
+
35
+ # Handles one request. This is specific to implementors.
36
+ #
37
+ def handle_request
38
+ raise NotImplementedError,
39
+ "Abstract base class doesn't implement #handle_request."
40
+ end
41
+
42
+ # Runs the server and keeps running until the world ends (or the process,
43
+ # whichever comes first).
44
+ #
45
+ def run(&block)
46
+ loop do
47
+ exception_handling(block) do
48
+ handle_request
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+ # Defines how the server handles exception.
55
+ #
56
+ def exception_handling(exception_handler)
57
+ if exception_handler
58
+ begin
59
+ yield
60
+ rescue => exception
61
+ exception_handler.call(exception)
62
+ end
63
+ else
64
+ yield
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,22 @@
1
+
2
+ # A method missing implementation that will use respond_to? to see wether a
3
+ # message should be answered. If yes, it delegates the message to service,
4
+ # which is supposed to return one of Cods RPC client primitives. Depending on
5
+ # the value of has_answer?(symbol), either #call or #notify is used.
6
+ #
7
+ module Zack::TransparentProxy
8
+ def method_missing(sym, *args, &block)
9
+ super unless respond_to?(sym)
10
+
11
+ raise ArgumentError, "Can't call methods remotely with a block" if block
12
+
13
+ if has_answer?(sym)
14
+ return service.call([sym, args])
15
+ else
16
+ service.notify [sym, args]
17
+ return nil
18
+ end
19
+ rescue Cod::Channel::TimeoutError
20
+ raise Zack::ServiceTimeout, "No response from server in the allowed time."
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ require 'uuid'
2
+
3
+ # A UUID based unique name based on base_name.
4
+ #
5
+ class Zack::UniqueName
6
+ def initialize(base_name)
7
+ @name = unique_tube_name(base_name)
8
+ end
9
+
10
+ attr_reader :name
11
+
12
+ alias to_s name
13
+
14
+ private
15
+ # Pretend that UUIDs don't collide for now.
16
+ #
17
+ def unique_tube_name(name)
18
+ "name.#{uuid}"
19
+ end
20
+ def uuid
21
+ uuid_generator.generate
22
+ end
23
+ def uuid_generator
24
+ generator=Thread.current[:zack_uuid_generator]
25
+ return generator if generator
26
+
27
+ # assert: generator is nil
28
+
29
+ # Pretend we've just forked, because that might be the case.
30
+ UUID.generator.next_sequence
31
+
32
+ Thread.current[:zack_uuid_generator]=generator=UUID.new
33
+ end
34
+
35
+ end
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+
3
+ require 'timeout'
4
+
5
+ describe "Regression: " do
6
+ def self.fork_server(server_class)
7
+ # Fork a server in the background
8
+ before(:each) {
9
+ # Clear old messages
10
+ conn = Beanstalk::Connection.new(BEANSTALK_CONNECTION, 'regression')
11
+ while conn.peek_ready
12
+ conn.reserve.delete
13
+ end
14
+
15
+ # Start a new server
16
+ @pid = fork do
17
+ $stderr.reopen('/dev/null')
18
+ Zack::Server.new(
19
+ 'regression',
20
+ :server => BEANSTALK_CONNECTION,
21
+ :simple => server_class
22
+ ).run
23
+ end
24
+ }
25
+ after(:each) {
26
+ Process.kill('KILL', @pid)
27
+ Process.waitpid(@pid)
28
+ }
29
+ end
30
+ def get_client(timeout, *with_answer)
31
+ Zack::Client.new(
32
+ 'regression',
33
+ :timeout => timeout,
34
+ :server => BEANSTALK_CONNECTION,
35
+ :with_answer => with_answer)
36
+ end
37
+
38
+ def print_stats
39
+ connection = Beanstalk::Connection.new(BEANSTALK_CONNECTION)
40
+
41
+ puts "Stats: "
42
+ pp connection.stats
43
+
44
+ puts "Tubes: "
45
+ connection.list_tubes.each do |tube|
46
+ p tube
47
+ pp connection.stats_tube(tube)
48
+ end
49
+
50
+ connection.close
51
+ end
52
+
53
+ describe "asynchronous long running message, followed by a short running reader message (bug)" do
54
+ class Regression1Server
55
+ def reader; 42; end
56
+ def long_running; sleep 0.1 end
57
+ end
58
+ fork_server Regression1Server
59
+
60
+ let(:client) { get_client(10, :reader) }
61
+
62
+ # Wait for the server to launch
63
+ before(:each) {
64
+ sleep 0.01 # This is the same bug that we expose below...
65
+ # wait till the server works
66
+ client.reader.should == 42
67
+ }
68
+
69
+ it "should correctly call the reader" do
70
+ # Because the code used to watch ALL tubes, not just the relevant ones,
71
+ # we got our own request back from the service tube when waiting for an
72
+ # answer on the answer tube.
73
+ client.long_running
74
+ client.reader.should == 42
75
+ end
76
+ end
77
+ describe "server crash during a message that has an answer" do
78
+ class CrashingServer
79
+ def crash_and_burn
80
+ fail
81
+ end
82
+ end
83
+
84
+ fork_server CrashingServer
85
+ let(:client) { get_client(1, :crash_and_burn) }
86
+
87
+ RSpec::Matchers.define :take_long do
88
+ match(&lambda do |block|
89
+ begin
90
+ Timeout::timeout(2) do
91
+ block.call
92
+ end
93
+ rescue Timeout::Error
94
+ return true
95
+ end
96
+ false
97
+ end)
98
+ end
99
+
100
+ it "should timeout a blocking call" do
101
+ lambda {
102
+ lambda {
103
+ client.crash_and_burn
104
+ }.should_not take_long
105
+ }.should raise_error(Zack::ServiceTimeout)
106
+ end
107
+ end
108
+ describe "server that takes a long time, timeout in client stops the operation" do
109
+ class LongRunningServer
110
+ def long_running(time, answer)
111
+ sleep time
112
+ return answer
113
+ end
114
+ end
115
+
116
+ fork_server LongRunningServer
117
+ let(:client) { get_client(1, :long_running) }
118
+
119
+ it "should pass a sanity check" do
120
+ client.long_running(0, 42).should == 42
121
+ end
122
+ context "when the first call takes longer than the client timeout" do
123
+ before(:each) {
124
+ begin
125
+ client.long_running(1.1, 10)
126
+ rescue Zack::ServiceTimeout
127
+ end
128
+ }
129
+
130
+ it "should correctly handle subsequent messages" do
131
+ client.long_running(0, 42).should == 42
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Server#run" do
4
+ class RunServerRun
5
+ def crash
6
+ raise "Some Exception"
7
+ end
8
+ def message
9
+ # ...
10
+ end
11
+ end
12
+
13
+ let(:server) { Zack::Server.new(
14
+ 'server_run',
15
+ :simple => RunServerRun,
16
+ :server => BEANSTALK_CONNECTION)
17
+ }
18
+ let(:client) { Zack::Client.new(
19
+ 'server_run', :server => BEANSTALK_CONNECTION)
20
+ }
21
+
22
+ it "should loop forever" do
23
+ flexmock(server).should_receive(:loop).and_yield
24
+
25
+ # Post a message for the server to handle
26
+ client.message
27
+
28
+ # Run just once
29
+ server.run
30
+ end
31
+
32
+ context "when given a block" do
33
+ it "should yield exceptions" do
34
+ flexmock(server).should_receive(:loop).and_yield
35
+ client.crash # this will raise an exception as soon as we run
36
+
37
+ called = false
38
+ server.run { |exception|
39
+ called = true
40
+ exception.should be_a(RuntimeError)
41
+ exception.message.should == "Some Exception"
42
+ }
43
+
44
+ called.should == true
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Zack::Client do
5
+ let(:beanstalk) { Beanstalk::Connection.new(BEANSTALK_CONNECTION, 'zack_client_test') }
6
+ before(:each) do
7
+ while beanstalk.peek_ready
8
+ beanstalk.reserve.delete
9
+ end
10
+ end
11
+
12
+ context "when not waiting for answers" do
13
+ let(:client) { Zack::Client.new(
14
+ 'zack_client_test',
15
+ :server => BEANSTALK_CONNECTION,
16
+ :only => { :foo => true, :foobar => true },
17
+ :with_answer => [:bar]) }
18
+
19
+ # Replace Cod with a mock
20
+ let(:service) { flexmock(:service) }
21
+ before(:each) { flexmock(client).should_receive(:service).and_return(service) }
22
+
23
+ describe "return value for asynchronous calls" do
24
+ it "should be nil" do
25
+ service.should_receive(:notify)
26
+
27
+ client.foo.should be_nil
28
+ end
29
+ end
30
+ describe "calling rpc method with a block" do
31
+ it "should raise ArgumentError" do
32
+ lambda {
33
+ client.foo { something }
34
+ }.should raise_error(ArgumentError)
35
+ end
36
+ end
37
+ context "when calling #foobar(123, '123', :a123)" do
38
+ it "should queue the message [:foobar, [123, '123', :a123]]" do
39
+ service.should_receive(:notify).with([:foobar, [123, '123', :a123]]).once
40
+
41
+ client.foobar(123, '123', :a123)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zack::Server do
4
+ context "when constructing without :factory / :simple" do
5
+ it "should raise ArgumentError" do
6
+ lambda {
7
+ Zack::Server.new('foobar')
8
+ }.should raise_error(ArgumentError)
9
+ end
10
+ end
11
+ context "instance" do
12
+ let(:implementation) { flexmock(:implementation) }
13
+
14
+ # Replacing the Cod::Service with a mock
15
+ let(:service) { flexmock(:service) }
16
+ before(:each) { flexmock(server, :service => service) }
17
+
18
+ context "with a factory" do
19
+ # A small factory that always returns instance.
20
+ class ImplFactory < Struct.new(:instance)
21
+ def call
22
+ instance
23
+ end
24
+ end
25
+
26
+ let(:server) {
27
+ Zack::Server.new(
28
+ 'zack_server_test',
29
+ :factory => ImplFactory.new(implementation),
30
+ :server => BEANSTALK_CONNECTION
31
+ )
32
+ }
33
+
34
+ describe "<- #handle_request" do
35
+ context "when receiving [:foobar, [123, '123', :a123]]" do
36
+ after(:each) { server.handle_request }
37
+
38
+ it "should call the right message on implementation" do
39
+ service.should_receive(:one).
40
+ and_yield([:foobar, [123, '123', :a123]])
41
+
42
+ implementation.
43
+ should_receive(:foobar).
44
+ with(123, '123', :a123).
45
+ once
46
+ end
47
+ end
48
+ end
49
+ end
50
+ context "with a simple class" do
51
+ let(:implementation_klass) { flexmock(:new => implementation) }
52
+
53
+ let(:server) {
54
+ Zack::Server.new(
55
+ 'zack_server_test',
56
+ :simple => implementation_klass,
57
+ :server => BEANSTALK_CONNECTION
58
+ )
59
+ }
60
+
61
+ describe "<- #handle_request" do
62
+ context "when receiving [1, :foobar, [123, '123', :a123]]" do
63
+ after(:each) { server.handle_request }
64
+
65
+ it "should call the right message on implementation" do
66
+ service.should_receive(:one).
67
+ and_yield([:foobar, [123, '123', :a123]])
68
+
69
+ implementation.
70
+ should_receive(:foobar).
71
+ with(123, '123', :a123).
72
+ once
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,8 @@
1
+
2
+ require 'zack'
3
+
4
+ RSpec.configure do |config|
5
+ config.mock_with :flexmock
6
+ end
7
+
8
+ BEANSTALK_CONNECTION = 'localhost:11300'
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.1
4
+ version: 0.3.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -15,7 +15,7 @@ default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: cod
18
- requirement: &2166707740 !ruby/object:Gem::Requirement
18
+ requirement: &2165838080 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
21
  - - ~>
@@ -23,10 +23,10 @@ dependencies:
23
23
  version: '0.3'
24
24
  type: :runtime
25
25
  prerelease: false
26
- version_requirements: *2166707740
26
+ version_requirements: *2165838080
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: beanstalk-client
29
- requirement: &2166707260 !ruby/object:Gem::Requirement
29
+ requirement: &2165837600 !ruby/object:Gem::Requirement
30
30
  none: false
31
31
  requirements:
32
32
  - - ~>
@@ -34,10 +34,10 @@ dependencies:
34
34
  version: '1.0'
35
35
  type: :runtime
36
36
  prerelease: false
37
- version_requirements: *2166707260
37
+ version_requirements: *2165837600
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: uuid
40
- requirement: &2166706800 !ruby/object:Gem::Requirement
40
+ requirement: &2165837140 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ~>
@@ -45,7 +45,7 @@ dependencies:
45
45
  version: '2.3'
46
46
  type: :runtime
47
47
  prerelease: false
48
- version_requirements: *2166706800
48
+ version_requirements: *2165837140
49
49
  description:
50
50
  email:
51
51
  - kaspar.schiess@absurd.li
@@ -61,7 +61,18 @@ files:
61
61
  - README
62
62
  - lib/zack/client.rb
63
63
  - lib/zack/server.rb
64
+ - lib/zack/target.rb
65
+ - lib/zack/transparent_proxy.rb
66
+ - lib/zack/unique_name.rb
64
67
  - lib/zack.rb
68
+ - spec/integration/regression_spec.rb
69
+ - spec/integration/server_runner_spec.rb
70
+ - spec/lib/zack/client_spec.rb
71
+ - spec/lib/zack/server_spec.rb
72
+ - spec/spec_helper.rb
73
+ - example/client.rb
74
+ - example/pubsub.rb
75
+ - example/server.rb
65
76
  has_rdoc: true
66
77
  homepage: http://github.com/kschiess/zack
67
78
  licenses: []