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.
@@ -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,9 @@
1
+ class Cocaine::Request
2
+ def initialize(channel)
3
+ @ch = channel
4
+ end
5
+
6
+ def read
7
+ @ch
8
+ end
9
+ 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