spider-gazelle 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,151 +1,105 @@
1
+ require 'spider-gazelle/const'
1
2
  require 'stringio'
2
3
 
3
-
4
4
  module SpiderGazelle
5
- class Request
6
-
7
- # Based on http://rack.rubyforge.org/doc/SPEC.html
8
- PATH_INFO = 'PATH_INFO'.freeze # Request path from the script name up
9
- QUERY_STRING = 'QUERY_STRING'.freeze # portion of the request following a '?' (empty if none)
10
- SERVER_NAME = 'SERVER_NAME'.freeze # required although HTTP_HOST takes priority if set
11
- SERVER_PORT = 'SERVER_PORT'.freeze # required (set in spider.rb init)
12
- REQUEST_URI = 'REQUEST_URI'.freeze
13
- REQUEST_PATH = 'REQUEST_PATH'.freeze
14
- RACK_URLSCHEME = 'rack.url_scheme'.freeze # http or https
15
- RACK_INPUT = 'rack.input'.freeze # an IO like object containing all the request body
16
- RACK_HIJACKABLE = 'rack.hijack?'.freeze # hijacking IO is supported
17
- RACK_HIJACK = 'rack.hijack'.freeze # callback for indicating that this socket will be hijacked
18
- RACK_HIJACK_IO = 'rack.hijack_io'.freeze # the object for performing IO on after hijack is called
19
- RACK_ASYNC = 'async.callback'.freeze
20
-
21
- GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
22
- CGI_VER = "CGI/1.2".freeze
23
-
24
- EMPTY = ''.freeze
25
-
26
- HTTP_11 = 'HTTP/1.1'.freeze # used in PROTO_ENV
27
- HTTP_URL_SCHEME = 'http'.freeze
28
- HTTPS_URL_SCHEME = 'https'.freeze
29
- HTTP_HOST = 'HTTP_HOST'.freeze
30
- LOCALHOST = 'localhost'.freeze
31
-
32
- KEEP_ALIVE = "Keep-Alive".freeze
33
-
34
- CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
35
- CONTENT_TYPE = 'CONTENT_TYPE'.freeze
36
- DEFAULT_TYPE = 'text/plain'.freeze
37
- HTTP_CONTENT_LENGTH = 'HTTP_CONTENT_LENGTH'.freeze
38
- HTTP_CONTENT_TYPE = 'HTTP_CONTENT_TYPE'.freeze
39
-
40
- SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
41
- SERVER = 'SpiderGazelle'.freeze
42
- REMOTE_ADDR = 'REMOTE_ADDR'.freeze
43
-
44
-
45
- #
46
- # TODO:: Add HTTP headers to the env and capitalise them and prefix them with HTTP_
47
- # convert - signs to underscores
48
- # => copy puma with a const file
49
- #
50
- PROTO_ENV = {
51
- 'rack.version'.freeze => ::Rack::VERSION, # Should be an array of integers
52
- 'rack.errors'.freeze => $stderr, # An error stream that supports: puts, write and flush
53
- 'rack.multithread'.freeze => true, # can the app be simultaneously invoked by another thread?
54
- 'rack.multiprocess'.freeze => false, # will the app be simultaneously be invoked in a separate process?
55
- 'rack.run_once'.freeze => false, # this isn't CGI so will always be false
56
-
57
- 'SCRIPT_NAME'.freeze => ENV['SCRIPT_NAME'] || EMPTY, # The virtual path of the app base (empty if root)
58
- 'SERVER_PROTOCOL'.freeze => HTTP_11,
59
-
60
- GATEWAY_INTERFACE => CGI_VER,
61
- SERVER_SOFTWARE => SERVER
62
- }
63
-
64
-
65
- attr_accessor :env, :url, :header, :body, :keep_alive, :upgrade, :deferred
66
- attr_reader :hijacked, :response
67
-
68
-
69
- def initialize(connection, app)
70
- @app = app
71
- @body = ''
72
- @header = ''
73
- @url = ''
74
- @execute = method(:execute)
75
- @env = PROTO_ENV.dup
76
- @loop = connection.loop
77
- @env[SERVER_PORT] = connection.port
78
- @env[REMOTE_ADDR] = connection.remote_ip
79
- @env[RACK_URLSCHEME] = connection.tls ? HTTPS_URL_SCHEME : HTTP_URL_SCHEME
80
- @env[RACK_ASYNC] = connection.async_callback
81
- end
82
-
83
- def execute!
84
- @env[CONTENT_LENGTH] = @env.delete(HTTP_CONTENT_LENGTH) || @body.length
85
- @env[CONTENT_TYPE] = @env.delete(HTTP_CONTENT_TYPE) || DEFAULT_TYPE
86
- @env[REQUEST_URI] = @url.freeze
87
-
88
- # For Rack::Lint on 1.9, ensure that the encoding is always for spec
89
- @body.force_encoding('ASCII-8BIT') if @body.respond_to?(:force_encoding)
90
- @env[RACK_INPUT] = StringIO.new(@body)
91
-
92
- # Break the request into its components
93
- query_start = @url.index('?')
94
- if query_start
95
- path = @url[0...query_start].freeze
96
- @env[PATH_INFO] = path
97
- @env[REQUEST_PATH] = path
98
- @env[QUERY_STRING] = @url[query_start + 1..-1].freeze
99
- else
100
- @env[PATH_INFO] = @url
101
- @env[REQUEST_PATH] = @url
102
- @env[QUERY_STRING] = ''
103
- end
104
-
105
- # Grab the host name from the request
106
- if host = @env[HTTP_HOST]
107
- if colon = host.index(':')
108
- @env[SERVER_NAME] = host[0, colon]
109
- @env[SERVER_PORT] = host[colon+1, host.bytesize]
110
- else
111
- @env[SERVER_NAME] = host
112
- @env[SERVER_PORT] = PROTO_ENV[SERVER_PORT]
113
- end
114
- else
115
- @env[SERVER_NAME] = LOCALHOST
116
- @env[SERVER_PORT] = PROTO_ENV[SERVER_PORT]
117
- end
118
-
119
- # Provide hijack options if this is an upgrade request
120
- if @upgrade == true
121
- @env[RACK_HIJACKABLE] = true
122
- @env[RACK_HIJACK] = method(:hijack)
123
- end
5
+ class Request
6
+ include Const
7
+
8
+ # TODO:: Add HTTP headers to the env and capitalise them and prefix them with HTTP_
9
+ # convert - signs to underscores
10
+ PROTO_ENV = {
11
+ RACK_VERSION => ::Rack::VERSION, # Should be an array of integers
12
+ RACK_ERRORS => $stderr, # An error stream that supports: puts, write and flush
13
+ RACK_MULTITHREAD => true, # can the app be simultaneously invoked by another thread?
14
+ RACK_MULTIPROCESS => false, # will the app be simultaneously be invoked in a separate process?
15
+ RACK_RUN_ONCE => false, # this isn't CGI so will always be false
16
+
17
+ SCRIPT_NAME => ENV['SCRIPT_NAME'] || EMPTY, # The virtual path of the app base (empty if root)
18
+ SERVER_PROTOCOL => HTTP_11,
19
+
20
+ GATEWAY_INTERFACE => CGI_VER,
21
+ SERVER_SOFTWARE => SERVER
22
+ }
23
+
24
+ attr_accessor :env, :url, :header, :body, :keep_alive, :upgrade, :deferred
25
+ attr_reader :hijacked, :response
26
+
27
+ def initialize(connection, app)
28
+ @app = app
29
+ @body = ''
30
+ @header = ''
31
+ @url = ''
32
+ @execute = method :execute
33
+ @env = PROTO_ENV.dup
34
+ @loop = connection.loop
35
+ @env[SERVER_PORT] = connection.port
36
+ @env[REMOTE_ADDR] = connection.remote_ip
37
+ @env[RACK_URL_SCHEME] = connection.tls ? HTTPS : HTTP
38
+ @env[ASYNC] = connection.async_callback
39
+ end
124
40
 
125
- # Execute the request
126
- @response = catch(:async, &@execute)
127
- if @response.nil? || @response[0] == -1
128
- @deferred = @loop.defer
129
- end
130
- @response
41
+ def execute!
42
+ @env[CONTENT_LENGTH] = @env.delete(HTTP_CONTENT_LENGTH) || @body.length
43
+ @env[CONTENT_TYPE] = @env.delete(HTTP_CONTENT_TYPE) || DEFAULT_TYPE
44
+ @env[REQUEST_URI] = @url.freeze
45
+
46
+ # For Rack::Lint on 1.9, ensure that the encoding is always for spec
47
+ @body.force_encoding('ASCII-8BIT') if @body.respond_to?(:force_encoding)
48
+ @env[RACK_INPUT] = StringIO.new @body
49
+
50
+ # Break the request into its components
51
+ query_start = @url.index '?'
52
+ if query_start
53
+ path = @url[0...query_start].freeze
54
+ @env[PATH_INFO] = path
55
+ @env[REQUEST_PATH] = path
56
+ @env[QUERY_STRING] = @url[query_start + 1..-1].freeze
57
+ else
58
+ @env[PATH_INFO] = @url
59
+ @env[REQUEST_PATH] = @url
60
+ @env[QUERY_STRING] = ''
61
+ end
62
+
63
+ # Grab the host name from the request
64
+ if host = @env[HTTP_HOST]
65
+ if colon = host.index(':')
66
+ @env[SERVER_NAME] = host[0, colon]
67
+ @env[SERVER_PORT] = host[colon+1, host.bytesize]
68
+ else
69
+ @env[SERVER_NAME] = host
70
+ @env[SERVER_PORT] = PROTO_ENV[SERVER_PORT]
131
71
  end
72
+ else
73
+ @env[SERVER_NAME] = LOCALHOST
74
+ @env[SERVER_PORT] = PROTO_ENV[SERVER_PORT]
75
+ end
76
+
77
+ # Provide hijack options if this is an upgrade request
78
+ if @upgrade == true
79
+ @env[HIJACK_P] = true
80
+ @env[HIJACK] = method :hijack
81
+ end
82
+
83
+ # Execute the request
84
+ @response = catch :async, &@execute
85
+ @deferred = @loop.defer if (@response.nil? || @response[0] == -1)
86
+ @response
87
+ end
132
88
 
89
+ protected
133
90
 
134
- protected
135
-
136
-
137
- # Execute the request then close the body
138
- # NOTE:: closing the body here might cause issues (see connection.rb)
139
- def execute(*args)
140
- result = @app.call(@env)
141
- body = result[2]
142
- body.close if body.respond_to?(:close)
143
- result
144
- end
91
+ # Execute the request then close the body
92
+ # NOTE:: closing the body here might cause issues (see connection.rb)
93
+ def execute(*args)
94
+ result = @app.call @env
95
+ body = result[2]
96
+ body.close if body.respond_to?(:close)
97
+ result
98
+ end
145
99
 
146
- def hijack
147
- @hijacked = @loop.defer
148
- @env[RACK_HIJACK_IO] = @hijacked.promise
149
- end
100
+ def hijack
101
+ @hijacked = @loop.defer
102
+ @env[HIJACK_IO] = @hijacked.promise
150
103
  end
104
+ end
151
105
  end
@@ -1,408 +1,392 @@
1
+ require 'spider-gazelle/const'
1
2
  require 'set'
2
3
  require 'thread'
3
4
  require 'logger'
4
5
  require 'singleton'
5
- require 'fileutils' # mkdir_p
6
+ require 'fileutils' # mkdir_p
6
7
  require 'forwardable' # run method
7
8
 
8
-
9
9
  module SpiderGazelle
10
- class Spider
11
- include Singleton
12
-
10
+ class Spider
11
+ include Const
12
+ include Singleton
13
13
 
14
- USE_TLS = 'T'.freeze
15
- NO_TLS = 'F'.freeze
16
- KILL_GAZELLE = 'k'.freeze
14
+ STATES = [:reanimating, :running, :squashing, :dead]
15
+ # TODO:: implement clustering using processes
16
+ MODES = [:thread, :process, :no_ipc]
17
+ DEFAULT_OPTIONS = {
18
+ Host: "0.0.0.0",
19
+ Port: 8080,
20
+ Verbose: false,
21
+ tls: false,
22
+ optimize_for_latency: true,
23
+ backlog: 1024
24
+ }
17
25
 
18
- STATES = [:reanimating, :running, :squashing, :dead]
19
- MODES = [:thread, :process, :no_ipc] # TODO:: implement clustering using processes
26
+ attr_reader :state, :mode, :threads, :logger
20
27
 
21
- DEFAULT_OPTIONS = {
22
- :Host => '0.0.0.0',
23
- :Port => 8080,
24
- :Verbose => false,
25
- :tls => false,
26
- :optimize_for_latency => true,
27
- :backlog => 1024
28
- }
28
+ extend Forwardable
29
+ def_delegators :@web, :run
29
30
 
30
- def self.run(app, options = {})
31
- options = DEFAULT_OPTIONS.merge(options)
31
+ def self.run(app, options = {})
32
+ options = DEFAULT_OPTIONS.merge options
32
33
 
33
- ENV['RACK_ENV'] = options[:environment].to_s if options[:environment]
34
+ ENV['RACK_ENV'] = options[:environment].to_s if options[:environment]
34
35
 
35
- puts "Look out! Here comes Spider-Gazelle #{::SpiderGazelle::VERSION}!"
36
- puts "* Environment: #{ENV['RACK_ENV']} on #{RUBY_ENGINE || 'ruby'} #{RUBY_VERSION}"
36
+ puts "Look out! Here comes Spider-Gazelle #{SPIDER_GAZELLE_VERSION}!"
37
+ puts "* Environment: #{ENV['RACK_ENV']} on #{RUBY_ENGINE || 'ruby'} #{RUBY_VERSION}"
37
38
 
38
- server = instance
39
- server.run do |logger|
40
- logger.progress server.method(:log)
41
- server.loaded.then do
42
- puts "* Loading: #{app}"
39
+ server = instance
40
+ server.run do |logger|
41
+ logger.progress server.method(:log)
42
+ server.loaded.then do
43
+ puts "* Loading: #{app}"
43
44
 
44
- # yield server if block_given?
45
+ # yield server if block_given?
45
46
 
46
- server.load(app, options).catch(proc {|e|
47
- puts "#{e.message}\n#{e.backtrace.join("\n") unless e.backtrace.nil?}\n"
48
- }).finally do
49
- # This will execute if the TCP binding is lost
50
- # Terminating the application
51
- Process.kill 'INT', 0
52
- end
47
+ caught = proc { |e| puts("#{e.message}\n#{e.backtrace.join("\n")}") unless e.backtrace.nil? }
48
+ server.load(app, options).catch(caught)
49
+ .finally { Process.kill('INT', 0) } # Terminate the application if the TCP binding is lost
53
50
 
54
- puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}"
55
- end
56
- end
51
+ puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}"
57
52
  end
53
+ end
54
+ end
58
55
 
56
+ def initialize
57
+ # Threaded mode by default
58
+ reanimating!
59
+ @bindings = {}
60
+ # @bindings = {
61
+ # id => [bind1, bind2]
62
+ # }
63
+
64
+ mode = ENV['SG_MODE'] || :thread
65
+ @mode = mode.to_sym
66
+
67
+ @delegate = method(no_ipc? ? :direct_delegate : :delegate)
68
+ @squash = method(:squash)
69
+
70
+ # FIXME The expanded path path seems a bit risky
71
+ log_path = ENV['SG_LOG'] || File.expand_path('../../../logs/server.log', __FILE__)
72
+ dirname = File.dirname(log_path)
73
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
74
+ @logger = ::Logger.new(log_path.to_s, 10, 4194304)
75
+
76
+ # Keep track of the loading process
77
+ @waiting_gazelle = 0
78
+ @gazelle_count = 0
79
+
80
+ # Spider always runs on the default loop
81
+ @web = ::Libuv::Loop.default
82
+ @gazelles_loaded = @web.defer
83
+
84
+ # Start the server
85
+ if @web.reactor_running?
86
+ # Call run so we can be notified of errors
87
+ @web.run &method(:reanimate)
88
+ else
89
+ # Don't block on this thread if default reactor not running
90
+ Thread.new { @web.run(&method(:reanimate)) }
91
+ end
92
+ end
59
93
 
60
- attr_reader :state, :mode, :threads, :logger
61
-
62
- extend Forwardable
63
- def_delegators :@web, :run
64
-
65
-
66
- def initialize
67
- # Threaded mode by default
68
- @status = :reanimating
69
- @bindings = {
70
- # id => [bind1, bind2]
71
- }
94
+ # Modes
95
+ def thread?
96
+ @mode == :thread
97
+ end
98
+ def process?
99
+ @mode == :process
100
+ end
101
+ def no_ipc?
102
+ @mode == :no_ipc
103
+ end
72
104
 
73
- mode = ENV['SG_MODE'] || :thread
74
- @mode = mode.to_sym
105
+ # Statuses
106
+ def reanimating?
107
+ @status == :reanimating
108
+ end
109
+ def reanimating!
110
+ @status = :reanimating
111
+ end
112
+ def running?
113
+ @status == :running
114
+ end
115
+ def running!
116
+ @status = :running
117
+ end
118
+ def squashing?
119
+ @status == :squashing
120
+ end
121
+ def squashing!
122
+ @status = :squashing
123
+ end
124
+ def dead?
125
+ @status == :dead
126
+ end
127
+ def dead!
128
+ @status = :dead
129
+ end
75
130
 
76
- if @mode == :no_ipc
77
- @delegate = method(:direct_delegate)
78
- else
79
- @delegate = method(:delegate)
80
- end
81
- @squash = method(:squash)
131
+ # Provides a promise that resolves when we are read to start binding applications
132
+ #
133
+ # @return [::Libuv::Q::Promise] that indicates when the gazelles are loaded
134
+ def loaded
135
+ @gazelles_loaded.promise unless @gazelles_loaded.nil?
136
+ end
82
137
 
138
+ # A thread safe method for loading and binding rack apps. The app can be pre-loaded or a rackup file
139
+ #
140
+ # @param app [String, Object] rackup filename or rack app
141
+ # @param ports [Hash, Array] binding config or array of binding config
142
+ # @return [::Libuv::Q::Promise] resolves once the app is loaded (and bound if SG is running)
143
+ def load(app, ports = [])
144
+ defer = @web.defer
83
145
 
84
- log_path = ENV['SG_LOG'] || File.expand_path('../../../logs/server.log', __FILE__)
85
- dirname = File.dirname(log_path)
86
- unless File.directory?(dirname)
87
- FileUtils.mkdir_p(dirname)
88
- end
89
- @logger = ::Logger.new(log_path.to_s, 10, 4194304)
90
-
91
- # Keep track of the loading process
92
- @waiting_gazelle = 0
93
- @gazelle_count = 0
94
-
95
- # Spider always runs on the default loop
96
- @web = ::Libuv::Loop.default
97
- @gazelles_loaded = @web.defer
98
-
99
- # Start the server
100
- if @web.reactor_running?
101
- # Call run so we can be notified of errors
102
- @web.run &method(:reanimate)
103
- else
104
- # Don't block on this thread if default reactor not running
105
- Thread.new do
106
- @web.run &method(:reanimate)
107
- end
108
- end
109
- end
146
+ ports = [ports] if ports.is_a?(Hash)
147
+ ports << {} if ports.empty?
110
148
 
111
- # Provides a promise that resolves when we are read to start binding applications
112
- #
113
- # @return [::Libuv::Q::Promise] that indicates when the gazelles are loaded
114
- def loaded
115
- @gazelles_loaded.promise unless @gazelles_loaded.nil?
116
- end
149
+ @web.schedule do
150
+ begin
151
+ app_id = AppStore.load app
152
+ bindings = @bindings[app_id] ||= []
117
153
 
118
- # A thread safe method for loading and binding rack apps. The app can be pre-loaded or a rackup file
119
- #
120
- # @param app [String, Object] rackup filename or rack app
121
- # @param ports [Hash, Array] binding config or array of binding config
122
- # @return [::Libuv::Q::Promise] resolves once the app is loaded (and bound if SG is running)
123
- def load(app, ports = [])
124
- defer = @web.defer
125
-
126
- ports = [ports] if ports.is_a? Hash
127
- ports << {} if ports.empty?
128
-
129
- @web.schedule do
130
- begin
131
- app_id = AppStore.load(app)
132
- bindings = @bindings[app_id] ||= []
133
-
134
- ports.each do |options|
135
- binding = Binding.new(@web, @delegate, app_id, options)
136
- bindings << binding
137
- end
138
-
139
- if @status == :running
140
- defer.resolve(start(app, app_id))
141
- else
142
- defer.resolve(true)
143
- end
144
- rescue Exception => e
145
- defer.reject(e)
146
- end
147
- end
154
+ ports.each { |options| bindings << Binding.new(@web, @delegate, app_id, options) }
148
155
 
149
- defer.promise
156
+ defer.resolve(running? ? start(app, app_id) : true)
157
+ rescue Exception => e
158
+ defer.reject e
150
159
  end
160
+ end
151
161
 
152
- # Starts the app specified. It must already be loaded
153
- #
154
- # @param app [String, Object] rackup filename or the rack app instance
155
- # @return [::Libuv::Q::Promise] resolves once the app is bound to the port
156
- def start(app, app_id = nil)
157
- defer = @web.defer
158
- app_id = app_id || AppStore.lookup(app)
159
-
160
- if app_id != nil && @status == :running
161
- @web.schedule do
162
- bindings = @bindings[app_id] ||= []
163
- starting = []
164
-
165
- bindings.each do |binding|
166
- starting << binding.bind
167
- end
168
- defer.resolve(::Libuv::Q.all(@web, *starting))
169
- end
170
- elsif app_id.nil?
171
- defer.reject('application not loaded')
172
- else
173
- defer.reject('server not running')
174
- end
162
+ defer.promise
163
+ end
175
164
 
176
- defer.promise
165
+ # Starts the app specified. It must already be loaded
166
+ #
167
+ # @param app [String, Object] rackup filename or the rack app instance
168
+ # @return [::Libuv::Q::Promise] resolves once the app is bound to the port
169
+ def start(app, app_id = nil)
170
+ defer = @web.defer
171
+ app_id ||= AppStore.lookup app
172
+
173
+ if !app_id.nil? && running?
174
+ @web.schedule do
175
+ bindings = @bindings[app_id] ||= []
176
+ starting = []
177
+
178
+ bindings.each { |binding| starting << binding.bind }
179
+ defer.resolve ::Libuv::Q.all(@web, *starting)
177
180
  end
181
+ elsif app_id.nil?
182
+ defer.reject 'application not loaded'
183
+ else
184
+ defer.reject 'server not running'
185
+ end
178
186
 
179
- # Stops the app specified. If loaded
180
- #
181
- # @param app [String, Object] rackup filename or the rack app instance
182
- # @return [::Libuv::Q::Promise] resolves once the app is no longer bound to the port
183
- def stop(app, app_id = nil)
184
- defer = @web.defer
185
- app_id = app_id || AppStore.lookup(app)
186
-
187
- if !app_id.nil?
188
- @web.schedule do
189
- bindings = @bindings[app_id]
190
- closing = []
191
-
192
- if bindings != nil
193
- bindings.each do |binding|
194
- result = binding.unbind
195
- closing << result unless result.nil?
196
- end
197
- end
198
- defer.resolve(::Libuv::Q.all(@web, *closing))
199
- end
200
- else
201
- defer.reject('application not loaded')
202
- end
187
+ defer.promise
188
+ end
203
189
 
204
- defer.promise
190
+ # Stops the app specified. If loaded
191
+ #
192
+ # @param app [String, Object] rackup filename or the rack app instance
193
+ # @return [::Libuv::Q::Promise] resolves once the app is no longer bound to the port
194
+ def stop(app, app_id = nil)
195
+ defer = @web.defer
196
+ app_id ||= AppStore.lookup app
197
+
198
+ if !app_id.nil?
199
+ @web.schedule do
200
+ bindings = @bindings[app_id]
201
+ closing = []
202
+
203
+ bindings.each do |binding|
204
+ result = binding.unbind
205
+ closing << result unless result.nil?
206
+ end unless bindings.nil?
207
+
208
+ defer.resolve ::Libuv::Q.all(@web, *closing)
205
209
  end
210
+ else
211
+ defer.reject 'application not loaded'
212
+ end
206
213
 
207
- # Signals spider gazelle to shutdown gracefully
208
- def shutdown
209
- @signal_squash.call
210
- end
214
+ defer.promise
215
+ end
211
216
 
217
+ # Signals spider gazelle to shutdown gracefully
218
+ def shutdown
219
+ @signal_squash.call
220
+ end
212
221
 
213
- protected
222
+ protected
214
223
 
224
+ # Called from the binding for sending to gazelles
225
+ def delegate(client, tls, port, app_id)
226
+ indicator = tls ? USE_TLS : NO_TLS
227
+ @select_handler.next.write2(client, "#{indicator} #{port} #{app_id}")
228
+ end
215
229
 
216
- # Called from the binding for sending to gazelles
217
- def delegate(client, tls, port, app_id)
218
- indicator = tls ? USE_TLS : NO_TLS
219
- loop = @select_handler.next
220
- loop.write2(client, "#{indicator} #{port} #{app_id}")
221
- end
230
+ def direct_delegate(client, tls, port, app_id)
231
+ indicator = tls ? USE_TLS : NO_TLS
232
+ @gazelle.__send__(:new_connection, "#{indicator} #{port} #{app_id}", client)
233
+ end
222
234
 
223
- def direct_delegate(client, tls, port, app_id)
224
- indicator = tls ? USE_TLS : NO_TLS
225
- @gazelle.__send__(:new_connection, "#{indicator} #{port} #{app_id}", client)
235
+ # Triggers the creation of gazelles
236
+ def reanimate(logger)
237
+ # Manage the set of Gazelle socket listeners
238
+ @threads = Set.new
239
+
240
+ if thread?
241
+ cpus = ::Libuv.cpu_count || 1
242
+ cpus.times { @threads << Libuv::Loop.new }
243
+ elsif no_ipc?
244
+ # TODO:: need to perform process mode as well
245
+ @threads << @web
246
+ end
247
+
248
+ @handlers = Set.new
249
+ @select_handler = @handlers.cycle # provides a looping enumerator for our round robin
250
+ @accept_handler = method :accept_handler
251
+
252
+ # Manage the set of Gazelle signal pipes
253
+ @gazella = Set.new
254
+ @accept_gazella = method :accept_gazella
255
+
256
+ # Create a function for stopping the spider from another thread
257
+ @signal_squash = @web.async @squash
258
+
259
+ # Link up the loops logger
260
+ logger.progress method(:log)
261
+
262
+ if no_ipc?
263
+ @gazelle = Gazelle.new @web, @logger, @mode
264
+ @gazelle_count = 1
265
+ start_bindings
266
+ else
267
+ # Bind the pipe for sending sockets to gazelle
268
+ begin
269
+ File.unlink DELEGATE_PIPE
270
+ rescue
226
271
  end
227
-
228
- # Triggers the creation of gazelles
229
- def reanimate(logger)
230
- # Manage the set of Gazelle socket listeners
231
- @threads = Set.new
232
-
233
- if @mode == :thread
234
- cpus = ::Libuv.cpu_count || 1
235
- cpus.times do
236
- @threads << Libuv::Loop.new
237
- end
238
- elsif @mode == :no_ipc
239
- # TODO:: need to perform process mode as well
240
- @threads << @web
241
- end
242
-
243
- @handlers = Set.new
244
- @select_handler = @handlers.cycle # provides a looping enumerator for our round robin
245
- @accept_handler = method(:accept_handler)
246
-
247
- # Manage the set of Gazelle signal pipes
248
- @gazella = Set.new
249
- @accept_gazella = method(:accept_gazella)
250
-
251
- # Create a function for stopping the spider from another thread
252
- @signal_squash = @web.async @squash
253
-
254
- # Link up the loops logger
255
- logger.progress method(:log)
256
-
257
- if @mode == :no_ipc
258
- @gazelle = Gazelle.new(@web, @logger, @mode)
259
- @gazelle_count = 1
260
- start_bindings
261
- else
262
- # Bind the pipe for sending sockets to gazelle
263
- begin
264
- File.unlink(DELEGATE_PIPE)
265
- rescue
266
- end
267
- @delegator = @web.pipe(true)
268
- @delegator.bind(DELEGATE_PIPE) do
269
- @delegator.accept @accept_handler
270
- end
271
- @delegator.listen(16)
272
-
273
- # Bind the pipe for communicating with gazelle
274
- begin
275
- File.unlink(SIGNAL_PIPE)
276
- rescue
277
- end
278
- @signaller = @web.pipe(true)
279
- @signaller.bind(SIGNAL_PIPE) do
280
- @signaller.accept @accept_gazella
281
- end
282
- @signaller.listen(16)
283
-
284
- # Launch the gazelle here
285
- @threads.each do |thread|
286
- Thread.new do
287
- gazelle = Gazelle.new(thread, @logger, @mode)
288
- gazelle.run
289
- end
290
- @waiting_gazelle += 1
291
- end
292
- end
293
-
294
- # Signal gazelle death here
295
- @web.signal :INT, @squash
296
-
297
- # Update state only once the event loop is ready
298
- @gazelles_loaded.promise
272
+ @delegator = @web.pipe true
273
+ @delegator.bind(DELEGATE_PIPE) { @delegator.accept @accept_handler }
274
+ @delegator.listen INTERNAL_PIPE_BACKLOG
275
+
276
+ # Bind the pipe for communicating with gazelle
277
+ begin
278
+ File.unlink SIGNAL_PIPE
279
+ rescue
299
280
  end
300
-
301
- # Triggers a shutdown of the gazelles.
302
- # We ensure the process is running here as signals can be called multiple times
303
- def squash(*args)
304
- if @status == :running
305
-
306
- # Update the state and close the socket
307
- @status = :squashing
308
- @bindings.each_key do |key|
309
- stop(key)
310
- end
311
-
312
- if @mode == :no_ipc
313
- @web.stop
314
- @status = :dead
315
- else
316
- # Signal all the gazelle to shutdown
317
- promises = []
318
- @gazella.each do |gazelle|
319
- promises << gazelle.write(KILL_GAZELLE)
320
- end
321
-
322
- # Once the signal has been sent we can stop the spider loop
323
- @web.finally(*promises).finally do
324
-
325
- # TODO:: need a better system for ensuring these are cleaned up
326
- # Especially when we implement live migrations and process clusters
327
- begin
328
- @delegator.close
329
- File.unlink(DELEGATE_PIPE)
330
- rescue
331
- end
332
- begin
333
- @signaller.close
334
- File.unlink(SIGNAL_PIPE)
335
- rescue
336
- end
337
-
338
- @web.stop
339
- @status = :dead
340
- end
341
- end
342
- end
281
+ @signaller = @web.pipe true
282
+ @signaller.bind(SIGNAL_PIPE) { @signaller.accept @accept_gazella }
283
+ @signaller.listen INTERNAL_PIPE_BACKLOG
284
+
285
+ # Launch the gazelle here
286
+ @threads.each do |thread|
287
+ Thread.new { Gazelle.new(thread, @logger, @mode).run }
288
+ @waiting_gazelle += 1
343
289
  end
290
+ end
344
291
 
345
- # A new gazelle is ready to accept commands
346
- def accept_gazella(gazelle)
347
- #puts "gazelle #{@gazella.size} signal port ready"
292
+ # Signal gazelle death here
293
+ @web.signal :INT, @squash
348
294
 
349
- # add the signal port to the set
350
- @gazella.add gazelle
351
- gazelle.finally do
352
- @gazella.delete gazelle
353
- @waiting_gazelle -= 1
354
- @gazelle_count -= 1
355
- end
295
+ # Update state only once the event loop is ready
296
+ @gazelles_loaded.promise
297
+ end
356
298
 
357
- @gazelle_count += 1
358
- if @waiting_gazelle == @gazelle_count
359
- start_bindings
299
+ # Triggers a shutdown of the gazelles.
300
+ # We ensure the process is running here as signals can be called multiple times
301
+ def squash(*args)
302
+ if running?
303
+ # Update the state and close the socket
304
+ squashing!
305
+ @bindings.each { |key, val| stop(key) }
306
+
307
+ if no_ipc?
308
+ @web.stop
309
+ dead!
310
+ else
311
+ # Signal all the gazelle to shutdown
312
+ promises = @gazella.map { |gazelle| gazelle.write(KILL_GAZELLE) }
313
+
314
+ # Once the signal has been sent we can stop the spider loop
315
+ @web.finally(*promises).finally do
316
+ # TODO:: need a better system for ensuring these are cleaned up
317
+ # Especially when we implement live migrations and process clusters
318
+ begin
319
+ @delegator.close
320
+ File.unlink DELEGATE_PIPE
321
+ rescue
360
322
  end
361
- end
362
-
363
- def start_bindings
364
- @status = :running
365
-
366
- # Start any bindings that are already present
367
- @bindings.each_key do |key|
368
- start(key)
323
+ begin
324
+ @signaller.close
325
+ File.unlink SIGNAL_PIPE
326
+ rescue
369
327
  end
370
328
 
371
- # Inform any listeners that we have completed loading
372
- @gazelles_loaded.resolve(@gazelle_count)
329
+ @web.stop
330
+ dead!
331
+ end
373
332
  end
333
+ end
334
+ end
335
+
336
+ # A new gazelle is ready to accept commands
337
+ def accept_gazella(gazelle)
338
+ # add the signal port to the set
339
+ @gazella.add gazelle
340
+ gazelle.finally do
341
+ @gazella.delete gazelle
342
+ @waiting_gazelle -= 1
343
+ @gazelle_count -= 1
344
+ end
345
+
346
+ @gazelle_count += 1
347
+ start_bindings if @waiting_gazelle == @gazelle_count
348
+ end
374
349
 
375
- # A new gazelle loop is ready to accept sockets
376
- # We start the server as soon as the first gazelle is ready
377
- def accept_handler(handler)
378
- #puts "gazelle #{@handlers.size} loop running"
350
+ def start_bindings
351
+ running!
379
352
 
380
- @handlers.add handler # add the new gazelle to the set
381
- @select_handler.rewind # update the enumerator with the new gazelle
353
+ # Start any bindings that are already present
354
+ @bindings.each { |key, val| start(key) }
382
355
 
383
- # If a gazelle dies or shuts down we update the set
384
- handler.finally do
385
- @handlers.delete handler
386
- @select_handler.rewind
356
+ # Inform any listeners that we have completed loading
357
+ @gazelles_loaded.resolve @gazelle_count
358
+ end
387
359
 
388
- if @status == :running && @handlers.size == 0
389
- # I assume if we made it here something went quite wrong
390
- squash
391
- end
392
- end
393
- end
360
+ # A new gazelle loop is ready to accept sockets.
361
+ # We start the server as soon as the first gazelle is ready
362
+ def accept_handler(handler)
363
+ # Add the new gazelle to the set
364
+ @handlers.add handler
365
+ # Update the enumerator with the new gazelle
366
+ @select_handler.rewind
367
+
368
+ # If a gazelle dies or shuts down we update the set
369
+ handler.finally do
370
+ @handlers.delete handler
371
+ @select_handler.rewind
372
+
373
+ # I assume if we made it here something went quite wrong
374
+ squash if running? && @handlers.empty?
375
+ end
376
+ end
394
377
 
395
- def log(*args)
396
- msg = ''
397
- err = args[-1]
398
- if err && err.respond_to?(:backtrace)
399
- msg << "unhandled exception: #{err.message} (#{args[0..-2]})"
400
- msg << "\n#{err.backtrace.join("\n")}" if err.respond_to?(:backtrace) && err.backtrace
401
- else
402
- msg << "unhandled exception: #{args}"
403
- end
404
- @logger.error msg
405
- ::Libuv::Q.reject(@web, msg)
406
- end
378
+ # TODO FIXME Review this method.
379
+ def log(*args)
380
+ msg = ''
381
+ err = args[-1]
382
+ if err && err.respond_to?(:backtrace)
383
+ msg << "unhandled exception: #{err.message} (#{args[0..-2]})"
384
+ msg << "\n#{err.backtrace.join("\n")}" if err.respond_to?(:backtrace) && err.backtrace
385
+ else
386
+ msg << "unhandled exception: #{args}"
387
+ end
388
+ @logger.error msg
389
+ ::Libuv::Q.reject @web, msg
407
390
  end
391
+ end
408
392
  end