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 +4 -0
- data/example/client.rb +7 -0
- data/example/pubsub.rb +35 -0
- data/example/server.rb +16 -0
- data/lib/zack/target.rb +68 -0
- data/lib/zack/transparent_proxy.rb +22 -0
- data/lib/zack/unique_name.rb +35 -0
- data/spec/integration/regression_spec.rb +135 -0
- data/spec/integration/server_runner_spec.rb +47 -0
- data/spec/lib/zack/client_spec.rb +45 -0
- data/spec/lib/zack/server_spec.rb +78 -0
- data/spec/spec_helper.rb +8 -0
- metadata +18 -7
data/History.txt
CHANGED
data/example/client.rb
ADDED
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
|
data/lib/zack/target.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
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.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: &
|
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: *
|
26
|
+
version_requirements: *2165838080
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: beanstalk-client
|
29
|
-
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: *
|
37
|
+
version_requirements: *2165837600
|
38
38
|
- !ruby/object:Gem::Dependency
|
39
39
|
name: uuid
|
40
|
-
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: *
|
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: []
|