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