cocaine-framework-ruby 0.11.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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/LICENSE +165 -0
- data/README.md +16 -0
- data/cocaine.gemspec +27 -0
- data/examples/echo/echo.rb +56 -0
- data/examples/echo/manifest.json +3 -0
- data/lib/cocaine.rb +6 -0
- data/lib/cocaine/asio/channel.rb +88 -0
- data/lib/cocaine/asio/channel/combiner.rb +16 -0
- data/lib/cocaine/asio/channel/manager.rb +19 -0
- data/lib/cocaine/asio/connection.rb +58 -0
- data/lib/cocaine/client/dispatcher.rb +46 -0
- data/lib/cocaine/client/error.rb +7 -0
- data/lib/cocaine/client/service.rb +130 -0
- data/lib/cocaine/decoder.rb +11 -0
- data/lib/cocaine/dispatcher.rb +19 -0
- data/lib/cocaine/error.rb +2 -0
- data/lib/cocaine/future.rb +26 -0
- data/lib/cocaine/namespace.rb +4 -0
- data/lib/cocaine/protocol.rb +155 -0
- data/lib/cocaine/server/dispatcher.rb +72 -0
- data/lib/cocaine/server/health.rb +76 -0
- data/lib/cocaine/server/http.rb +63 -0
- data/lib/cocaine/server/request.rb +9 -0
- data/lib/cocaine/server/response.rb +31 -0
- data/lib/cocaine/server/sandbox.rb +38 -0
- data/lib/cocaine/server/worker.rb +73 -0
- data/lib/cocaine/synchrony/channel.rb +72 -0
- data/lib/cocaine/synchrony/service.rb +53 -0
- data/lib/cocaine/testing/mock_server.rb +123 -0
- data/lib/version.rb +3 -0
- data/spec/channel_spec.rb +160 -0
- data/spec/connection_spec.rb +31 -0
- data/spec/health_spec.rb +19 -0
- data/spec/service_spec.rb +322 -0
- data/spec/stub_server.rb +60 -0
- data/spec/worker_spec.rb +37 -0
- metadata +126 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
|
4
|
+
$log = Logger.new(STDERR)
|
5
|
+
$log.level = Logger::DEBUG
|
6
|
+
|
7
|
+
|
8
|
+
class Timer
|
9
|
+
def initialize(timeout)
|
10
|
+
@timeout = timeout
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
class DisownTimer < Timer
|
16
|
+
def initialize(timeout = 10.0)
|
17
|
+
super timeout
|
18
|
+
end
|
19
|
+
|
20
|
+
def start(&block)
|
21
|
+
@timer = EM::Timer.new @timeout do
|
22
|
+
block.call
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def cancel
|
27
|
+
@timer.cancel if @timer
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
class HeartbeatTimer < Timer
|
33
|
+
def initialize(timeout = 30.0)
|
34
|
+
super timeout
|
35
|
+
end
|
36
|
+
|
37
|
+
def start(&block)
|
38
|
+
@timer = EM::PeriodicTimer.new @timeout do
|
39
|
+
block.call
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def cancel
|
44
|
+
@timer.cancel if @timer
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
class Cocaine::HealthManager
|
50
|
+
def initialize(dispatcher, options={})
|
51
|
+
@dispatcher = dispatcher
|
52
|
+
options = {disown: 2.0, heartbeat: 10.0}.merge options
|
53
|
+
@disown = DisownTimer.new(options[:disown])
|
54
|
+
@heartbeat = HeartbeatTimer.new(options[:heartbeat])
|
55
|
+
end
|
56
|
+
|
57
|
+
def start
|
58
|
+
$log.debug 'health manager has been started'
|
59
|
+
@heartbeat.start { exhale }
|
60
|
+
end
|
61
|
+
|
62
|
+
def breath
|
63
|
+
$log.debug '[->] doing breath'
|
64
|
+
@disown.cancel
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def exhale
|
69
|
+
$log.debug '[<-] doing exhale'
|
70
|
+
@disown.start {
|
71
|
+
$log.error 'worker has been disowned'
|
72
|
+
EM.next_tick { EM.stop }
|
73
|
+
}
|
74
|
+
@dispatcher.send_heartbeat 0
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
require 'cocaine/namespace'
|
5
|
+
|
6
|
+
$log = Logger.new(STDERR)
|
7
|
+
$log.level = Logger::DEBUG
|
8
|
+
|
9
|
+
module Cocaine::Http
|
10
|
+
class Request
|
11
|
+
attr_reader :method, :version, :query, :headers, :body
|
12
|
+
|
13
|
+
def initialize(rq)
|
14
|
+
@ch = rq.read
|
15
|
+
end
|
16
|
+
|
17
|
+
def read
|
18
|
+
ch = Cocaine::Channel.new
|
19
|
+
@ch.callback { |msg|
|
20
|
+
@method, url, @version, headers, @body = MessagePack::unpack msg
|
21
|
+
@headers = Hash[headers]
|
22
|
+
tmp_query = URI.parse(url).query
|
23
|
+
@query = tmp_query == nil ? {} : CGI.parse(tmp_query)
|
24
|
+
ch.trigger self
|
25
|
+
}.errback { |err|
|
26
|
+
ch.error err
|
27
|
+
}
|
28
|
+
ch
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Response
|
33
|
+
def initialize(response)
|
34
|
+
@response = response
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_headers(code, headers)
|
38
|
+
@response.write [code, headers]
|
39
|
+
end
|
40
|
+
|
41
|
+
def body=(body)
|
42
|
+
@response.write body
|
43
|
+
end
|
44
|
+
|
45
|
+
def error(errno, reason)
|
46
|
+
@response.error errno, reason
|
47
|
+
end
|
48
|
+
|
49
|
+
def close
|
50
|
+
@response.close
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def http(method_name)
|
55
|
+
old_method_name = "#{method_name}_old".to_sym
|
56
|
+
alias_method(old_method_name, method_name)
|
57
|
+
define_method(method_name) do |rq, rs|
|
58
|
+
request = Request.new rq
|
59
|
+
response = Response.new rs
|
60
|
+
send(old_method_name, request, response)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'cocaine/error'
|
2
|
+
|
3
|
+
|
4
|
+
class Cocaine::Response
|
5
|
+
def initialize(session, dispatcher)
|
6
|
+
@session = session
|
7
|
+
@dispatcher = dispatcher
|
8
|
+
@closed = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(data)
|
12
|
+
check_closed
|
13
|
+
@dispatcher.send_chunk @session, data.to_msgpack
|
14
|
+
end
|
15
|
+
|
16
|
+
def error(errno, reason)
|
17
|
+
check_closed
|
18
|
+
@dispatcher.send_error @session, errno, reason
|
19
|
+
end
|
20
|
+
|
21
|
+
def close
|
22
|
+
check_closed
|
23
|
+
@closed = true
|
24
|
+
@dispatcher.send_choke @session
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def check_closed
|
29
|
+
raise IllegalStateError.new 'Response is already closed' if @closed
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
$log = Logger.new(STDERR)
|
2
|
+
$log.level = Logger::DEBUG
|
3
|
+
|
4
|
+
|
5
|
+
class Cocaine::Sandbox
|
6
|
+
module Errno
|
7
|
+
NO_SUCH_EVENT = 1
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@handlers = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def on(event, handler)
|
15
|
+
@handlers[event] = handler
|
16
|
+
end
|
17
|
+
|
18
|
+
def invoke(event, request, response)
|
19
|
+
if @handlers.has_key? event
|
20
|
+
invoke_handler event, request, response
|
21
|
+
else
|
22
|
+
write_error Errno::NO_SUCH_EVENT, "application has no event '#{event}'", response
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def invoke_handler(event, request, response)
|
28
|
+
$log.debug "invoking '#{event}'"
|
29
|
+
handler = @handlers[event]
|
30
|
+
handler.execute(request, response)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def write_error(errno, reason, response)
|
35
|
+
response.error errno, reason
|
36
|
+
response.close
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'cocaine/asio/connection'
|
4
|
+
require 'cocaine/server/dispatcher'
|
5
|
+
require 'cocaine/server/sandbox'
|
6
|
+
|
7
|
+
$log = Logger.new(STDERR)
|
8
|
+
$log.level = Logger::DEBUG
|
9
|
+
|
10
|
+
|
11
|
+
class Cocaine::Worker
|
12
|
+
def initialize(options={})
|
13
|
+
options = {
|
14
|
+
endpoint: '',
|
15
|
+
uuid: '',
|
16
|
+
}.merge(options)
|
17
|
+
|
18
|
+
@endpoint = options[:endpoint]
|
19
|
+
@uuid = options[:uuid]
|
20
|
+
|
21
|
+
@sandbox = Cocaine::Sandbox.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def on(event, handler)
|
25
|
+
@sandbox.on event, handler
|
26
|
+
end
|
27
|
+
|
28
|
+
def run
|
29
|
+
EM.error_handler { |error|
|
30
|
+
short_reason = error.inspect
|
31
|
+
traceback = error.backtrace.join("\n")
|
32
|
+
$log.warn "error caught at the top of event loop:\n#{short_reason}\n#{traceback}"
|
33
|
+
}
|
34
|
+
|
35
|
+
EM.run do
|
36
|
+
$log.debug 'starting worker'
|
37
|
+
$log.debug "connecting to the #{@endpoint}"
|
38
|
+
EM.connect @endpoint, nil, Cocaine::Connection do |conn|
|
39
|
+
$log.debug "connection established with #{@endpoint}"
|
40
|
+
@dispatcher = Cocaine::WorkerDispatcher.new self, conn
|
41
|
+
@dispatcher.send_handshake 0, @uuid
|
42
|
+
@dispatcher.send_heartbeat 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def invoke(event, request, response)
|
48
|
+
@sandbox.invoke(event, request, response)
|
49
|
+
end
|
50
|
+
|
51
|
+
def terminate(errno, reason)
|
52
|
+
$log.debug "terminating with: [#{errno}] #{reason}"
|
53
|
+
@dispatcher.send_terminate 0, errno, reason
|
54
|
+
EM.stop
|
55
|
+
exit(errno)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
class Cocaine::WorkerFactory
|
61
|
+
def self.create
|
62
|
+
options = {}
|
63
|
+
OptionParser.new do |opts|
|
64
|
+
opts.banner = 'Usage: <your_worker.rb> --app NAME --locator ADDRESS --uuid UUID --endpoint ENDPOINT'
|
65
|
+
|
66
|
+
opts.on('--app NAME', 'Worker name') { |a| options[:app] = a }
|
67
|
+
opts.on('--locator ADDRESS', 'Locator address') { |a| options[:locator] = a }
|
68
|
+
opts.on('--uuid UUID', 'Worker uuid') { |a| options[:uuid] = a }
|
69
|
+
opts.on('--endpoint ENDPOINT', 'Worker endpoint') { |a| options[:endpoint] = a }
|
70
|
+
end.parse!
|
71
|
+
return Cocaine::Worker.new options
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'fiber'
|
2
|
+
require 'em-synchrony'
|
3
|
+
|
4
|
+
require 'cocaine/namespace'
|
5
|
+
|
6
|
+
class Cocaine::Synchrony::Channel
|
7
|
+
def initialize(channel)
|
8
|
+
@pending = []
|
9
|
+
register_fiber channel
|
10
|
+
end
|
11
|
+
|
12
|
+
def read
|
13
|
+
if @pending.empty?
|
14
|
+
future = Fiber.yield
|
15
|
+
else
|
16
|
+
future = @pending.pop
|
17
|
+
end
|
18
|
+
future.get
|
19
|
+
end
|
20
|
+
|
21
|
+
def each
|
22
|
+
loop do
|
23
|
+
begin
|
24
|
+
yield Fiber.yield.get
|
25
|
+
rescue ChokeEvent
|
26
|
+
return
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def collect(count=0)
|
32
|
+
if count == 0
|
33
|
+
collect_until_choke
|
34
|
+
else
|
35
|
+
collect_until_count count
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def register_fiber(channel)
|
41
|
+
fb = Fiber.current
|
42
|
+
channel.callback { |future|
|
43
|
+
if fb.alive?
|
44
|
+
fb.resume future
|
45
|
+
else
|
46
|
+
@pending.push future
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def collect_until_count(count)
|
53
|
+
results = []
|
54
|
+
each do |chunk|
|
55
|
+
results.push chunk
|
56
|
+
count -= 1
|
57
|
+
if count == 0
|
58
|
+
break
|
59
|
+
end
|
60
|
+
end
|
61
|
+
results
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def collect_until_choke
|
66
|
+
results = []
|
67
|
+
each do |chunk|
|
68
|
+
results.push chunk
|
69
|
+
end
|
70
|
+
results
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'cocaine/client/service'
|
2
|
+
require 'cocaine/synchrony/channel'
|
3
|
+
|
4
|
+
|
5
|
+
module Cocaine::Synchrony
|
6
|
+
def self.sync(df)
|
7
|
+
fb = Fiber.current
|
8
|
+
df.callback do |result|
|
9
|
+
if fb == Fiber.current
|
10
|
+
return result
|
11
|
+
else
|
12
|
+
fb.resume result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
df.errback do |err|
|
17
|
+
if fb == Fiber.current
|
18
|
+
raise err
|
19
|
+
else
|
20
|
+
fb.resume err
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
result = Fiber.yield
|
25
|
+
if result.is_a? Exception
|
26
|
+
raise result
|
27
|
+
else
|
28
|
+
return result
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
class Cocaine::Synchrony::Service
|
35
|
+
def initialize(name, host='localhost', port=10053)
|
36
|
+
@service = Cocaine::Service.new name, host, port
|
37
|
+
connect
|
38
|
+
create_proxy_methods
|
39
|
+
end
|
40
|
+
|
41
|
+
def connect
|
42
|
+
Cocaine::Synchrony.sync @service.connect
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def create_proxy_methods
|
47
|
+
@service.api.each do |id, name|
|
48
|
+
self.metaclass.send(:define_method, name) do |*args|
|
49
|
+
Cocaine::Synchrony::Channel.new @service.send name, *args
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'em-synchrony'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require 'cocaine/synchrony/service'
|
5
|
+
|
6
|
+
$log = Logger.new(STDOUT)
|
7
|
+
$log.level = Logger::DEBUG
|
8
|
+
|
9
|
+
|
10
|
+
class Hook
|
11
|
+
attr_reader :callbacks
|
12
|
+
def initialize
|
13
|
+
@callbacks = {
|
14
|
+
:connected => lambda {},
|
15
|
+
:message => {}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def connected(&block)
|
20
|
+
@callbacks[:connected] = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def message(request, &block)
|
24
|
+
@callbacks[:message][request] = block
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class CocaineRuntimeMock
|
30
|
+
module Server
|
31
|
+
def initialize(name, responses=nil, hook=nil, options={})
|
32
|
+
$log.debug "new connection for '#{name}'"
|
33
|
+
@name = name
|
34
|
+
@responses = responses || {}
|
35
|
+
@hook = hook || Hook.new
|
36
|
+
@options = options
|
37
|
+
end
|
38
|
+
|
39
|
+
def post_init
|
40
|
+
@hook.callbacks[:connected].call
|
41
|
+
end
|
42
|
+
|
43
|
+
def receive_data(data)
|
44
|
+
unpacker ||= MessagePack::Unpacker.new
|
45
|
+
unpacker.feed_each data do |chunk|
|
46
|
+
id, session, data = chunk
|
47
|
+
$log.debug "received message: [#{id}, #{session}, #{data}]"
|
48
|
+
|
49
|
+
if @hook.callbacks[:message].has_key? chunk
|
50
|
+
response = @hook.callbacks[:message][chunk].call
|
51
|
+
send_response session, response
|
52
|
+
elsif @responses.has_key? chunk
|
53
|
+
response = @responses[chunk]
|
54
|
+
send_response session, response
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def send_response(session, response)
|
61
|
+
$log.debug "iterating over response: #{response}"
|
62
|
+
response.each do |chunk|
|
63
|
+
if chunk.is_a? Error
|
64
|
+
send_data chunk.pack session
|
65
|
+
else
|
66
|
+
send_data (Chunk.new chunk.to_msgpack).pack session
|
67
|
+
end
|
68
|
+
end
|
69
|
+
send_data Choke.new.pack session
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(options = {})
|
74
|
+
options = {:host => 'localhost', :port => 10053}.merge(options)
|
75
|
+
|
76
|
+
@host = options[:host]
|
77
|
+
@port = options[:port]
|
78
|
+
|
79
|
+
@services = {}
|
80
|
+
@responses = {}
|
81
|
+
@hooks = {}
|
82
|
+
|
83
|
+
@servers = []
|
84
|
+
|
85
|
+
register 'locator', 0, endpoint: [@host, @port], version: 1, api: {}
|
86
|
+
end
|
87
|
+
|
88
|
+
def register(name, session, options={})
|
89
|
+
raise "service '#{name}' already registered" if @services.has_key? name
|
90
|
+
|
91
|
+
$log.debug "registering '#{name}' service"
|
92
|
+
|
93
|
+
options = {endpoint: ['localhost', 0], version: 1, api: {}}.merge options
|
94
|
+
@services[name] = options
|
95
|
+
|
96
|
+
on 'locator',
|
97
|
+
[0, session, [name]],
|
98
|
+
[[options[:endpoint], options[:version], options[:api]]]
|
99
|
+
end
|
100
|
+
|
101
|
+
def on(name, request, response)
|
102
|
+
@responses[name] ||= {}
|
103
|
+
@responses[name][request] = response
|
104
|
+
end
|
105
|
+
|
106
|
+
def when(name)
|
107
|
+
@hooks[name] ||= Hook.new
|
108
|
+
end
|
109
|
+
|
110
|
+
def run
|
111
|
+
@services.each do |name, options|
|
112
|
+
$log.debug "starting '#{name}' service at #{options[:endpoint]}"
|
113
|
+
sig = EM::start_server *options[:endpoint], Server, name, @responses[name], @hooks[name], options
|
114
|
+
@servers.push sig
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def stop
|
119
|
+
@servers.each do |sig|
|
120
|
+
EM.stop_server sig
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|