zack 0.3.1 → 0.3.2

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 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: []