riser 0.1.0
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 +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +926 -0
- data/Rakefile +31 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/example/halo.html +18 -0
- data/example/halo.rb +171 -0
- data/example/halo.yml +21 -0
- data/example/local_services.rb +60 -0
- data/example/multiproc_server.rb +21 -0
- data/example/simple_count.rb +52 -0
- data/example/simple_daemon.rb +22 -0
- data/example/simple_key_count.rb +65 -0
- data/example/simple_server.rb +20 -0
- data/example/simple_services.rb +60 -0
- data/example/simple_tls.rb +32 -0
- data/lib/riser/daemon.rb +579 -0
- data/lib/riser/poll.rb +35 -0
- data/lib/riser/resource.rb +386 -0
- data/lib/riser/server.rb +869 -0
- data/lib/riser/services.rb +631 -0
- data/lib/riser/stream.rb +115 -0
- data/lib/riser/temppath.rb +25 -0
- data/lib/riser/test.rb +42 -0
- data/lib/riser/version.rb +10 -0
- data/lib/riser.rb +28 -0
- data/riser.gemspec +35 -0
- metadata +129 -0
data/lib/riser/server.rb
ADDED
@@ -0,0 +1,869 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'io/wait'
|
4
|
+
require 'socket'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module Riser
|
8
|
+
class TimeoutSizedQueue
|
9
|
+
def initialize(size, name: nil)
|
10
|
+
@size = size
|
11
|
+
@queue = []
|
12
|
+
@closed = false
|
13
|
+
@mutex = Thread::Mutex.new
|
14
|
+
@push_cond = Thread::ConditionVariable.new
|
15
|
+
@pop_cond = Thread::ConditionVariable.new
|
16
|
+
@name = name && name.dup.freeze
|
17
|
+
@stat_enable = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def size
|
21
|
+
@mutex.synchronize{ @queue.size }
|
22
|
+
end
|
23
|
+
|
24
|
+
alias length size
|
25
|
+
|
26
|
+
def empty?
|
27
|
+
@mutex.synchronize{ @queue.empty? }
|
28
|
+
end
|
29
|
+
|
30
|
+
def closed?
|
31
|
+
@mutex.synchronize{ @closed }
|
32
|
+
end
|
33
|
+
|
34
|
+
def close
|
35
|
+
@mutex.synchronize{
|
36
|
+
@closed = true
|
37
|
+
@pop_cond.broadcast
|
38
|
+
}
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def at_end_of_queue?
|
43
|
+
@mutex.synchronize{ @closed && @queue.empty? }
|
44
|
+
end
|
45
|
+
|
46
|
+
def push(value, timeout_seconds)
|
47
|
+
@mutex.synchronize{
|
48
|
+
@closed and raise 'closed'
|
49
|
+
if (@stat_enable) then
|
50
|
+
@stat_push_count += 1
|
51
|
+
@stat_push_average_queue_size = (@stat_push_average_queue_size * (@stat_push_count - 1) + @queue.size) / @stat_push_count
|
52
|
+
end
|
53
|
+
unless (@queue.size < @size) then
|
54
|
+
@stat_push_wait_count += 1 if @stat_enable
|
55
|
+
@push_cond.wait(@mutex, timeout_seconds)
|
56
|
+
unless (@queue.size < @size) then
|
57
|
+
@stat_push_timeout_count += 1 if @stat_enable
|
58
|
+
return
|
59
|
+
end
|
60
|
+
end
|
61
|
+
@pop_cond.signal
|
62
|
+
@queue.push(value)
|
63
|
+
self
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def pop
|
68
|
+
@mutex.synchronize{
|
69
|
+
if (@stat_enable) then
|
70
|
+
@stat_pop_count += 1
|
71
|
+
@stat_pop_average_queue_size = (@stat_pop_average_queue_size * (@stat_pop_count - 1) + @queue.size) / @stat_pop_count
|
72
|
+
end
|
73
|
+
while (@queue.empty?)
|
74
|
+
@closed and return
|
75
|
+
@stat_pop_wait_count += 1 if @stat_enable
|
76
|
+
@pop_cond.wait(@mutex)
|
77
|
+
end
|
78
|
+
@push_cond.signal
|
79
|
+
@queue.shift
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def stat_reset_no_lock
|
84
|
+
@stat_start_time = Time.now
|
85
|
+
@stat_push_average_queue_size = 0.0
|
86
|
+
@stat_push_count = 0
|
87
|
+
@stat_push_wait_count = 0
|
88
|
+
@stat_push_timeout_count = 0
|
89
|
+
@stat_pop_average_queue_size = 0.0
|
90
|
+
@stat_pop_count = 0
|
91
|
+
@stat_pop_wait_count = 0
|
92
|
+
end
|
93
|
+
private :stat_reset_no_lock
|
94
|
+
|
95
|
+
def stat_start
|
96
|
+
@mutex.synchronize{
|
97
|
+
unless (@stat_enable) then
|
98
|
+
stat_reset_no_lock
|
99
|
+
@stat_enable = true
|
100
|
+
end
|
101
|
+
}
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
def stat_stop
|
106
|
+
@mutex.synchronize{
|
107
|
+
@stat_enable = false
|
108
|
+
}
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
|
112
|
+
def stat_get(reset: true)
|
113
|
+
if (@stat_enable) then
|
114
|
+
info = nil
|
115
|
+
@mutex.synchronize{
|
116
|
+
info = {
|
117
|
+
queue_name: @name,
|
118
|
+
queue_size: @size,
|
119
|
+
closed: @closed,
|
120
|
+
start_time: @stat_start_time,
|
121
|
+
push_average_queue_size: @stat_push_average_queue_size,
|
122
|
+
push_count: @stat_push_count,
|
123
|
+
push_wait_count: @stat_push_wait_count,
|
124
|
+
push_timeout_count: @stat_push_timeout_count,
|
125
|
+
pop_average_queue_size: @stat_pop_average_queue_size,
|
126
|
+
pop_count: @stat_pop_count,
|
127
|
+
pop_wait_count: @stat_pop_wait_count
|
128
|
+
}
|
129
|
+
|
130
|
+
if (reset) then
|
131
|
+
stat_reset_no_lock
|
132
|
+
end
|
133
|
+
}
|
134
|
+
|
135
|
+
info[:get_time] = Time.now
|
136
|
+
info[:elapsed_seconds] = info[:get_time] - info[:start_time]
|
137
|
+
info[:push_wait_ratio] = info[:push_wait_count].to_f / info[:push_count]
|
138
|
+
info[:push_timeout_ratio] = info[:push_timeout_count].to_f / info[:push_count]
|
139
|
+
info[:pop_wait_ratio] = info[:pop_wait_count].to_f / info[:pop_count]
|
140
|
+
|
141
|
+
# sort
|
142
|
+
[ :queue_name,
|
143
|
+
:queue_size,
|
144
|
+
:closed,
|
145
|
+
:start_time,
|
146
|
+
:get_time,
|
147
|
+
:elapsed_seconds,
|
148
|
+
:push_average_queue_size,
|
149
|
+
:push_count,
|
150
|
+
:push_wait_count,
|
151
|
+
:push_wait_ratio,
|
152
|
+
:push_timeout_count,
|
153
|
+
:push_timeout_ratio,
|
154
|
+
:pop_average_queue_size,
|
155
|
+
:pop_count,
|
156
|
+
:pop_wait_count,
|
157
|
+
:pop_wait_ratio
|
158
|
+
].each do |name|
|
159
|
+
info[name] = info.delete(name)
|
160
|
+
end
|
161
|
+
|
162
|
+
info
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class SocketAddress
|
168
|
+
def initialize(type)
|
169
|
+
@type = type
|
170
|
+
end
|
171
|
+
|
172
|
+
attr_reader :type
|
173
|
+
|
174
|
+
def to_a
|
175
|
+
[ @type ]
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_s
|
179
|
+
to_a.map{|s|
|
180
|
+
if (s.to_s.include? ':') then
|
181
|
+
"[#{s}]"
|
182
|
+
else
|
183
|
+
s
|
184
|
+
end
|
185
|
+
}.join(':')
|
186
|
+
end
|
187
|
+
|
188
|
+
def ==(other)
|
189
|
+
if (other.is_a? SocketAddress) then
|
190
|
+
self.to_a == other.to_a
|
191
|
+
else
|
192
|
+
false
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def eql?(other)
|
197
|
+
self == other
|
198
|
+
end
|
199
|
+
|
200
|
+
def hash
|
201
|
+
to_a.hash ^ self.class.hash
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.parse(config)
|
205
|
+
unsquare = proc{|s| s.sub(/\A \[/x, '').sub(/\] \z/x, '') }
|
206
|
+
case (config)
|
207
|
+
when String
|
208
|
+
case (config)
|
209
|
+
when /\A tcp:/x
|
210
|
+
uri = URI(config)
|
211
|
+
if (uri.host && uri.port) then
|
212
|
+
return TCPSocketAddress.new(unsquare.call(uri.host), uri.port)
|
213
|
+
end
|
214
|
+
when /\A unix:/x
|
215
|
+
uri = URI(config)
|
216
|
+
if (uri.path && ! uri.path.empty?) then
|
217
|
+
return UNIXSocketAddress.new(uri.path)
|
218
|
+
end
|
219
|
+
when %r"\A [A-Za-z]+:/"x
|
220
|
+
# unknown URI scheme
|
221
|
+
when /\A (\S+):(\d+) \z/x
|
222
|
+
host = $1
|
223
|
+
port = $2.to_i
|
224
|
+
return TCPSocketAddress.new(unsquare.call(host), port)
|
225
|
+
end
|
226
|
+
when Hash
|
227
|
+
if (type = config[:type] || config['type']) then
|
228
|
+
case (type.to_s)
|
229
|
+
when 'tcp'
|
230
|
+
host = config[:host] || config['host']
|
231
|
+
port = config[:port] || config['port']
|
232
|
+
if (host && (host.is_a? String) && port && (port.is_a? Integer)) then
|
233
|
+
return TCPSocketAddress.new(unsquare.call(host), port)
|
234
|
+
end
|
235
|
+
when 'unix'
|
236
|
+
path = config[:path] || config['path']
|
237
|
+
if (path && (path.is_a? String) && ! path.empty?) then
|
238
|
+
return UNIXSocketAddress.new(path)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
return
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
class TCPSocketAddress < SocketAddress
|
249
|
+
def initialize(host, port)
|
250
|
+
super(:tcp)
|
251
|
+
@host = host
|
252
|
+
@port = port
|
253
|
+
end
|
254
|
+
|
255
|
+
attr_reader :host
|
256
|
+
attr_reader :port
|
257
|
+
|
258
|
+
def to_a
|
259
|
+
super << @host << @port
|
260
|
+
end
|
261
|
+
|
262
|
+
def open_server
|
263
|
+
TCPServer.new(@host, @port)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class UNIXSocketAddress < SocketAddress
|
268
|
+
def initialize(path)
|
269
|
+
super(:unix)
|
270
|
+
@path = path
|
271
|
+
end
|
272
|
+
|
273
|
+
attr_reader :path
|
274
|
+
|
275
|
+
def to_a
|
276
|
+
super << @path
|
277
|
+
end
|
278
|
+
|
279
|
+
def open_server
|
280
|
+
UNIXServer.new(@path)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
module ServerSignal
|
285
|
+
SIGNAL_STOP_GRACEFUL = :TERM
|
286
|
+
SIGNAL_STOP_FORCED = :INT
|
287
|
+
SIGNAL_STAT_GET_AND_RESET = :USR1
|
288
|
+
SIGNAL_STAT_GET_NO_RESET = :USR2
|
289
|
+
SIGNAL_STAT_STOP = :WINCH
|
290
|
+
SIGNAL_RESTART_GRACEFUL = :HUP
|
291
|
+
SIGNAL_RESTART_FORCED = :QUIT
|
292
|
+
end
|
293
|
+
|
294
|
+
class SocketThreadDispatcher
|
295
|
+
def initialize(thread_queue_name)
|
296
|
+
@thread_num = nil
|
297
|
+
@thread_queue_name = thread_queue_name
|
298
|
+
@thread_queue_size = nil
|
299
|
+
@thread_queue_polling_timeout_seconds = nil
|
300
|
+
@at_stop = nil
|
301
|
+
@at_stat = nil
|
302
|
+
@at_stat_get = nil
|
303
|
+
@at_stat_stop = nil
|
304
|
+
@preprocess = nil
|
305
|
+
@postprocess = nil
|
306
|
+
@accept = nil
|
307
|
+
@accept_return = nil
|
308
|
+
@dispatch = nil
|
309
|
+
@stop_state = nil
|
310
|
+
@stat_operation_queue = []
|
311
|
+
end
|
312
|
+
|
313
|
+
attr_accessor :thread_num
|
314
|
+
attr_accessor :thread_queue_size
|
315
|
+
attr_accessor :thread_queue_polling_timeout_seconds
|
316
|
+
|
317
|
+
def at_stop(&block) # :yields: stop_state
|
318
|
+
@at_stop = block
|
319
|
+
nil
|
320
|
+
end
|
321
|
+
|
322
|
+
def at_stat(&block) # :yields: stat_info
|
323
|
+
@at_stat = block
|
324
|
+
nil
|
325
|
+
end
|
326
|
+
|
327
|
+
def at_stat_get(&block) # :yields: reset
|
328
|
+
@at_stat_get = block
|
329
|
+
nil
|
330
|
+
end
|
331
|
+
|
332
|
+
def at_stat_stop(&block) # :yields:
|
333
|
+
@at_stat_stop = block
|
334
|
+
nil
|
335
|
+
end
|
336
|
+
|
337
|
+
def preprocess(&block) # :yields:
|
338
|
+
@preprocess = block
|
339
|
+
nil
|
340
|
+
end
|
341
|
+
|
342
|
+
def postprocess(&block) # :yields:
|
343
|
+
@postprocess = block
|
344
|
+
nil
|
345
|
+
end
|
346
|
+
|
347
|
+
def accept(&block) # :yields:
|
348
|
+
@accept = block
|
349
|
+
nil
|
350
|
+
end
|
351
|
+
|
352
|
+
def accept_return(&block) # :yields:
|
353
|
+
@accept_return = block
|
354
|
+
nil
|
355
|
+
end
|
356
|
+
|
357
|
+
def dispatch(&block) # :yields: socket
|
358
|
+
@dispatch = block
|
359
|
+
nil
|
360
|
+
end
|
361
|
+
|
362
|
+
# should be called from signal(2) handler
|
363
|
+
def signal_stop_graceful
|
364
|
+
@stop_state ||= :graceful
|
365
|
+
nil
|
366
|
+
end
|
367
|
+
|
368
|
+
# should be called from signal(2) handler
|
369
|
+
def signal_stop_forced
|
370
|
+
@stop_state ||= :forced
|
371
|
+
nil
|
372
|
+
end
|
373
|
+
|
374
|
+
# should be called from signal(2) handler
|
375
|
+
def signal_stat_get(reset: true)
|
376
|
+
if (reset) then
|
377
|
+
@stat_operation_queue << :get_and_reset
|
378
|
+
else
|
379
|
+
@stat_operation_queue << :get
|
380
|
+
end
|
381
|
+
|
382
|
+
nil
|
383
|
+
end
|
384
|
+
|
385
|
+
# should be called from signal(2) handler
|
386
|
+
def signal_stat_stop
|
387
|
+
@stat_operation_queue << :stop
|
388
|
+
nil
|
389
|
+
end
|
390
|
+
|
391
|
+
def apply_signal_stat(queue)
|
392
|
+
unless (@stat_operation_queue.empty?) then
|
393
|
+
while (stat_ope = @stat_operation_queue.shift)
|
394
|
+
case (stat_ope)
|
395
|
+
when :get_and_reset
|
396
|
+
queue.stat_start
|
397
|
+
@at_stat.call(queue.stat_get(reset: true))
|
398
|
+
@at_stat_get.call(true)
|
399
|
+
when :get
|
400
|
+
queue.stat_start
|
401
|
+
@at_stat.call(queue.stat_get(reset: false))
|
402
|
+
@at_stat_get.call(false)
|
403
|
+
when :stop
|
404
|
+
queue.stat_stop
|
405
|
+
@at_stat_stop.call
|
406
|
+
else
|
407
|
+
raise "internal error: unknown stat operation <#{stat_ope.inspect}>"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
private :apply_signal_stat
|
413
|
+
|
414
|
+
# should be executed on the main thread sharing the stack with
|
415
|
+
# signal(2) handlers
|
416
|
+
#
|
417
|
+
# _server_socket is a dummy argument to call like
|
418
|
+
# SocketProcessDispatcher#start.
|
419
|
+
def start(_server_socket=nil)
|
420
|
+
@preprocess.call
|
421
|
+
begin
|
422
|
+
queue = TimeoutSizedQueue.new(@thread_queue_size, name: @thread_queue_name)
|
423
|
+
begin
|
424
|
+
thread_list = []
|
425
|
+
@thread_num.times{|i|
|
426
|
+
thread_list << Thread.new{
|
427
|
+
Thread.current[:number] = i
|
428
|
+
while (socket = queue.pop)
|
429
|
+
begin
|
430
|
+
@dispatch.call(socket)
|
431
|
+
ensure
|
432
|
+
socket.close unless socket.closed?
|
433
|
+
end
|
434
|
+
end
|
435
|
+
}
|
436
|
+
}
|
437
|
+
|
438
|
+
catch (:end_of_server) {
|
439
|
+
while (true)
|
440
|
+
begin
|
441
|
+
@stop_state and throw(:end_of_server)
|
442
|
+
apply_signal_stat(queue)
|
443
|
+
socket = @accept.call
|
444
|
+
end until (socket)
|
445
|
+
|
446
|
+
until (queue.push(socket, @thread_queue_polling_timeout_seconds))
|
447
|
+
if (@stop_state == :forced) then
|
448
|
+
socket.close
|
449
|
+
@accept_return.call
|
450
|
+
throw(:end_of_server)
|
451
|
+
end
|
452
|
+
apply_signal_stat(queue)
|
453
|
+
end
|
454
|
+
@accept_return.call
|
455
|
+
end
|
456
|
+
}
|
457
|
+
ensure
|
458
|
+
queue.close
|
459
|
+
end
|
460
|
+
|
461
|
+
@at_stop.call(@stop_state)
|
462
|
+
case (@stop_state)
|
463
|
+
when :graceful
|
464
|
+
for thread in thread_list
|
465
|
+
thread.join
|
466
|
+
end
|
467
|
+
when :forced
|
468
|
+
for thread in thread_list
|
469
|
+
thread.kill
|
470
|
+
end
|
471
|
+
else
|
472
|
+
raise "internal error: unknown stop state <#{@stop_state.inspect}>"
|
473
|
+
end
|
474
|
+
ensure
|
475
|
+
@postprocess.call
|
476
|
+
end
|
477
|
+
|
478
|
+
nil
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
SocketProcess = Struct.new(:pid, :io) # :nodoc:
|
483
|
+
|
484
|
+
class SocketProcessDispatcher
|
485
|
+
include ServerSignal
|
486
|
+
|
487
|
+
NO_CALL = proc{} # :nodoc:
|
488
|
+
|
489
|
+
def initialize(process_queue_name, thread_queue_name)
|
490
|
+
@accept_polling_timeout_seconds = nil
|
491
|
+
@process_num = nil
|
492
|
+
@process_queue_name = process_queue_name
|
493
|
+
@process_queue_size = nil
|
494
|
+
@process_queue_polling_timeout_seconds = nil
|
495
|
+
@process_send_io_polling_timeout_seconds = nil
|
496
|
+
@thread_num = nil
|
497
|
+
@thread_queue_name = thread_queue_name
|
498
|
+
@thread_queue_size = nil
|
499
|
+
@thread_queue_polling_timeout_seconds = nil
|
500
|
+
@at_fork= nil
|
501
|
+
@at_stop = nil
|
502
|
+
@at_stat = nil
|
503
|
+
@preprocess = nil
|
504
|
+
@postprocess = nil
|
505
|
+
@dispatch = nil
|
506
|
+
@process_dispatcher = nil
|
507
|
+
end
|
508
|
+
|
509
|
+
attr_accessor :accept_polling_timeout_seconds
|
510
|
+
attr_accessor :process_num
|
511
|
+
attr_accessor :process_queue_size
|
512
|
+
attr_accessor :process_queue_polling_timeout_seconds
|
513
|
+
attr_accessor :process_send_io_polling_timeout_seconds
|
514
|
+
attr_accessor :thread_num
|
515
|
+
attr_accessor :thread_queue_size
|
516
|
+
attr_accessor :thread_queue_polling_timeout_seconds
|
517
|
+
|
518
|
+
def at_fork(&block) # :yields:
|
519
|
+
@at_fork = block
|
520
|
+
nil
|
521
|
+
end
|
522
|
+
|
523
|
+
def at_stop(&block) # :yields: stop_state
|
524
|
+
@at_stop = block
|
525
|
+
nil
|
526
|
+
end
|
527
|
+
|
528
|
+
def at_stat(&block) # :yields: stat_info
|
529
|
+
@at_stat = block
|
530
|
+
nil
|
531
|
+
end
|
532
|
+
|
533
|
+
def preprocess(&block) # :yields:
|
534
|
+
@preprocess = block
|
535
|
+
nil
|
536
|
+
end
|
537
|
+
|
538
|
+
def postprocess(&block) # :yields:
|
539
|
+
@postprocess = block
|
540
|
+
nil
|
541
|
+
end
|
542
|
+
|
543
|
+
def dispatch(&block) # :yields: accept_object
|
544
|
+
@dispatch = block
|
545
|
+
nil
|
546
|
+
end
|
547
|
+
|
548
|
+
# should be called from signal(2) handler
|
549
|
+
def signal_stop_graceful
|
550
|
+
@process_dispatcher.signal_stop_graceful if @process_dispatcher
|
551
|
+
nil
|
552
|
+
end
|
553
|
+
|
554
|
+
# should be called from signal(2) handler
|
555
|
+
def signal_stop_forced
|
556
|
+
@process_dispatcher.signal_stop_forced if @process_dispatcher
|
557
|
+
nil
|
558
|
+
end
|
559
|
+
|
560
|
+
# should be called from signal(2) handler
|
561
|
+
def signal_stat_get(reset: true)
|
562
|
+
@process_dispatcher.signal_stat_get(reset: reset) if @process_dispatcher
|
563
|
+
nil
|
564
|
+
end
|
565
|
+
|
566
|
+
# should be called from signal(2) handler
|
567
|
+
def signal_stat_stop
|
568
|
+
@process_dispatcher.signal_stat_stop if @process_dispatcher
|
569
|
+
nil
|
570
|
+
end
|
571
|
+
|
572
|
+
# after this method call is completed, the object will be ready to
|
573
|
+
# accept `signal_...' methods.
|
574
|
+
def setup
|
575
|
+
@process_dispatcher = SocketThreadDispatcher.new(@process_queue_name)
|
576
|
+
nil
|
577
|
+
end
|
578
|
+
|
579
|
+
def start(server_socket)
|
580
|
+
case (server_socket)
|
581
|
+
when TCPServer, UNIXServer
|
582
|
+
socket_class = server_socket.class.superclass
|
583
|
+
else
|
584
|
+
socket_class = IO
|
585
|
+
end
|
586
|
+
|
587
|
+
process_list = []
|
588
|
+
@process_num.times do |pos|
|
589
|
+
child_io, parent_io = UNIXSocket.socketpair
|
590
|
+
pid = Process.fork{
|
591
|
+
parent_io.close
|
592
|
+
pos.times do |i|
|
593
|
+
process_list[i].io.close
|
594
|
+
end
|
595
|
+
|
596
|
+
thread_dispatcher = SocketThreadDispatcher.new("#{@thread_queue_name}-#{pos}")
|
597
|
+
thread_dispatcher.thread_num = @thread_num
|
598
|
+
thread_dispatcher.thread_queue_size = @thread_queue_size
|
599
|
+
thread_dispatcher.thread_queue_polling_timeout_seconds = @thread_queue_polling_timeout_seconds
|
600
|
+
|
601
|
+
thread_dispatcher.at_stop(&@at_stop)
|
602
|
+
thread_dispatcher.at_stat(&@at_stat)
|
603
|
+
thread_dispatcher.at_stat_get(&NO_CALL)
|
604
|
+
thread_dispatcher.at_stat_stop(&NO_CALL)
|
605
|
+
thread_dispatcher.preprocess(&@preprocess)
|
606
|
+
thread_dispatcher.postprocess(&@postprocess)
|
607
|
+
|
608
|
+
thread_dispatcher.accept{
|
609
|
+
if (child_io.wait_readable(@process_send_io_polling_timeout_seconds) != nil) then
|
610
|
+
command = child_io.read(5)
|
611
|
+
command == "SEND\n" or raise "internal error: unknown command <#{command.inspect}>"
|
612
|
+
child_io.recv_io(socket_class)
|
613
|
+
end
|
614
|
+
}
|
615
|
+
thread_dispatcher.accept_return{ child_io.write("RADY\n") }
|
616
|
+
thread_dispatcher.dispatch(&@dispatch)
|
617
|
+
|
618
|
+
Signal.trap(SIGNAL_STOP_GRACEFUL) { thread_dispatcher.signal_stop_graceful }
|
619
|
+
Signal.trap(SIGNAL_STOP_FORCED) { thread_dispatcher.signal_stop_forced }
|
620
|
+
Signal.trap(SIGNAL_STAT_GET_AND_RESET) { thread_dispatcher.signal_stat_get(reset: true) }
|
621
|
+
Signal.trap(SIGNAL_STAT_GET_NO_RESET) { thread_dispatcher.signal_stat_get(reset: false) }
|
622
|
+
Signal.trap(SIGNAL_STAT_STOP) { thread_dispatcher.signal_stat_stop }
|
623
|
+
|
624
|
+
begin
|
625
|
+
child_io.write("RADY\n")
|
626
|
+
@at_fork.call
|
627
|
+
thread_dispatcher.start
|
628
|
+
ensure
|
629
|
+
child_io.close
|
630
|
+
end
|
631
|
+
}
|
632
|
+
child_io.close
|
633
|
+
|
634
|
+
process_list << SocketProcess.new(pid, parent_io)
|
635
|
+
end
|
636
|
+
|
637
|
+
for process in process_list
|
638
|
+
response = process.io.read(5)
|
639
|
+
response == "RADY\n" or raise "internal error: unknown response <#{response.inspect}>"
|
640
|
+
end
|
641
|
+
|
642
|
+
setup unless @process_dispatcher
|
643
|
+
@process_dispatcher.thread_num = @process_num
|
644
|
+
@process_dispatcher.thread_queue_size = @process_queue_size
|
645
|
+
@process_dispatcher.thread_queue_polling_timeout_seconds = @process_queue_polling_timeout_seconds
|
646
|
+
|
647
|
+
@process_dispatcher.at_stop{|state|
|
648
|
+
case (state)
|
649
|
+
when :graceful
|
650
|
+
for process in process_list
|
651
|
+
Process.kill(SIGNAL_STOP_GRACEFUL, process.pid)
|
652
|
+
end
|
653
|
+
when :forced
|
654
|
+
for process in process_list
|
655
|
+
Process.kill(SIGNAL_STOP_FORCED, process.pid)
|
656
|
+
end
|
657
|
+
end
|
658
|
+
}
|
659
|
+
@process_dispatcher.at_stat(&@at_stat)
|
660
|
+
@process_dispatcher.at_stat_get{|reset|
|
661
|
+
if (reset) then
|
662
|
+
for process in process_list
|
663
|
+
Process.kill(SIGNAL_STAT_GET_AND_RESET, process.pid)
|
664
|
+
end
|
665
|
+
else
|
666
|
+
for process in process_list
|
667
|
+
Process.kill(SIGNAL_STAT_GET_NO_RESET, process.pid)
|
668
|
+
end
|
669
|
+
end
|
670
|
+
}
|
671
|
+
@process_dispatcher.at_stat_stop{
|
672
|
+
for process in process_list
|
673
|
+
Process.kill(SIGNAL_STAT_STOP, process.pid)
|
674
|
+
end
|
675
|
+
}
|
676
|
+
@process_dispatcher.preprocess(&NO_CALL)
|
677
|
+
@process_dispatcher.postprocess(&NO_CALL)
|
678
|
+
|
679
|
+
@process_dispatcher.accept{
|
680
|
+
if (server_socket.wait_readable(@accept_polling_timeout_seconds) != nil) then
|
681
|
+
server_socket.accept
|
682
|
+
end
|
683
|
+
}
|
684
|
+
@process_dispatcher.accept_return(&NO_CALL)
|
685
|
+
@process_dispatcher.dispatch{|socket|
|
686
|
+
process = process_list[Thread.current[:number]]
|
687
|
+
process.io.write("SEND\n")
|
688
|
+
process.io.send_io(socket)
|
689
|
+
response = process.io.read(5)
|
690
|
+
response == "RADY\n" or raise "internal error: unknown response <#{response.inspect}>"
|
691
|
+
}
|
692
|
+
@process_dispatcher.start
|
693
|
+
|
694
|
+
for process in process_list
|
695
|
+
Process.wait(process.pid)
|
696
|
+
process.io.close
|
697
|
+
end
|
698
|
+
|
699
|
+
nil
|
700
|
+
end
|
701
|
+
end
|
702
|
+
|
703
|
+
class SocketServer
|
704
|
+
NO_CALL = proc{} # :nodoc:
|
705
|
+
|
706
|
+
def initialize
|
707
|
+
@accept_polling_timeout_seconds = 0.1
|
708
|
+
@process_num = 0
|
709
|
+
@process_queue_size = 20
|
710
|
+
@process_queue_polling_timeout_seconds = 0.1
|
711
|
+
@process_send_io_polling_timeout_seconds = 0.1
|
712
|
+
@thread_num = 4
|
713
|
+
@thread_queue_size = 20
|
714
|
+
@thread_queue_polling_timeout_seconds = 0.1
|
715
|
+
@before_start = NO_CALL
|
716
|
+
@at_fork = NO_CALL
|
717
|
+
@at_stop = NO_CALL
|
718
|
+
@at_stat = NO_CALL
|
719
|
+
@preprocess = NO_CALL
|
720
|
+
@postprocess = NO_CALL
|
721
|
+
@after_stop = NO_CALL
|
722
|
+
@dispatch = nil
|
723
|
+
@dispatcher = nil
|
724
|
+
end
|
725
|
+
|
726
|
+
attr_accessor :accept_polling_timeout_seconds
|
727
|
+
attr_accessor :process_num
|
728
|
+
attr_accessor :process_queue_size
|
729
|
+
attr_accessor :process_queue_polling_timeout_seconds
|
730
|
+
attr_accessor :process_send_io_polling_timeout_seconds
|
731
|
+
attr_accessor :thread_num
|
732
|
+
attr_accessor :thread_queue_size
|
733
|
+
attr_accessor :thread_queue_polling_timeout_seconds
|
734
|
+
|
735
|
+
def before_start(&block) # :yields: server_socket
|
736
|
+
@before_start = block
|
737
|
+
nil
|
738
|
+
end
|
739
|
+
|
740
|
+
def at_fork(&block) # :yields:
|
741
|
+
@at_fork = block
|
742
|
+
nil
|
743
|
+
end
|
744
|
+
|
745
|
+
def at_stop(&block) # :yields: stop_state
|
746
|
+
@at_stop = block
|
747
|
+
nil
|
748
|
+
end
|
749
|
+
|
750
|
+
def at_stat(&block) # :yields: stat_info
|
751
|
+
@at_stat = block
|
752
|
+
nil
|
753
|
+
end
|
754
|
+
|
755
|
+
def preprocess(&block) # :yields:
|
756
|
+
@preprocess = block
|
757
|
+
nil
|
758
|
+
end
|
759
|
+
|
760
|
+
def postprocess(&block) # :yields:
|
761
|
+
@postprocess = block
|
762
|
+
nil
|
763
|
+
end
|
764
|
+
|
765
|
+
def after_stop(&block) # :yields:
|
766
|
+
@after_stop = block
|
767
|
+
nil
|
768
|
+
end
|
769
|
+
|
770
|
+
def dispatch(&block) # :yields: socket
|
771
|
+
@dispatch = block
|
772
|
+
nil
|
773
|
+
end
|
774
|
+
|
775
|
+
# should be called from signal(2) handler
|
776
|
+
def signal_stop_graceful
|
777
|
+
@dispatcher.signal_stop_graceful if @dispatcher
|
778
|
+
nil
|
779
|
+
end
|
780
|
+
|
781
|
+
# should be called from signal(2) handler
|
782
|
+
def signal_stop_forced
|
783
|
+
@dispatcher.signal_stop_forced if @dispatcher
|
784
|
+
nil
|
785
|
+
end
|
786
|
+
|
787
|
+
# should be called from signal(2) handler
|
788
|
+
def signal_stat_get(reset: true)
|
789
|
+
@dispatcher.signal_stat_get(reset: reset) if @dispatcher
|
790
|
+
nil
|
791
|
+
end
|
792
|
+
|
793
|
+
# should be called from signal(2) handler
|
794
|
+
def signal_stat_stop
|
795
|
+
@dispatcher.signal_stat_stop if @dispatcher
|
796
|
+
nil
|
797
|
+
end
|
798
|
+
|
799
|
+
# after this method call is completed, the object will be ready to
|
800
|
+
# accept `signal_...' methods.
|
801
|
+
def setup(server_socket)
|
802
|
+
if (@process_num > 0) then
|
803
|
+
@dispatcher = SocketProcessDispatcher.new('process_queue', 'thread_queue')
|
804
|
+
@dispatcher.accept_polling_timeout_seconds = @accept_polling_timeout_seconds
|
805
|
+
@dispatcher.process_num = @process_num
|
806
|
+
@dispatcher.process_queue_size = @process_queue_size
|
807
|
+
@dispatcher.process_queue_polling_timeout_seconds = @process_queue_polling_timeout_seconds
|
808
|
+
@dispatcher.process_send_io_polling_timeout_seconds = @process_send_io_polling_timeout_seconds
|
809
|
+
@dispatcher.thread_num = @thread_num
|
810
|
+
@dispatcher.thread_queue_size = @thread_queue_size
|
811
|
+
@dispatcher.thread_queue_polling_timeout_seconds = @thread_queue_polling_timeout_seconds
|
812
|
+
|
813
|
+
@dispatcher.at_fork{
|
814
|
+
server_socket.close
|
815
|
+
@at_fork.call
|
816
|
+
}
|
817
|
+
@dispatcher.at_stop(&@at_stop)
|
818
|
+
@dispatcher.at_stat(&@at_stat)
|
819
|
+
@dispatcher.preprocess(&@preprocess)
|
820
|
+
@dispatcher.postprocess(&@postprocess)
|
821
|
+
@dispatcher.dispatch(&@dispatch)
|
822
|
+
@dispatcher.setup
|
823
|
+
else
|
824
|
+
@dispatcher = SocketThreadDispatcher.new('thread_queue')
|
825
|
+
@dispatcher.thread_num = @thread_num
|
826
|
+
@dispatcher.thread_queue_size = @thread_queue_size
|
827
|
+
@dispatcher.thread_queue_polling_timeout_seconds = @thread_queue_polling_timeout_seconds
|
828
|
+
|
829
|
+
@dispatcher.at_stop(&@at_stop)
|
830
|
+
@dispatcher.at_stat(&@at_stat)
|
831
|
+
@dispatcher.at_stat_get(&NO_CALL)
|
832
|
+
@dispatcher.at_stat_stop(&NO_CALL)
|
833
|
+
@dispatcher.preprocess(&@preprocess)
|
834
|
+
@dispatcher.postprocess(&@postprocess)
|
835
|
+
@dispatcher.accept{
|
836
|
+
if (server_socket.wait_readable(@accept_polling_timeout_seconds) != nil) then
|
837
|
+
server_socket.accept
|
838
|
+
end
|
839
|
+
}
|
840
|
+
@dispatcher.accept_return(&NO_CALL)
|
841
|
+
@dispatcher.dispatch(&@dispatch)
|
842
|
+
end
|
843
|
+
|
844
|
+
nil
|
845
|
+
end
|
846
|
+
|
847
|
+
# should be executed on the main thread sharing the stack with
|
848
|
+
# signal(2) handlers
|
849
|
+
def start(server_socket)
|
850
|
+
unless (@dispatcher) then
|
851
|
+
setup(server_socket)
|
852
|
+
end
|
853
|
+
|
854
|
+
@before_start.call(server_socket)
|
855
|
+
begin
|
856
|
+
@dispatcher.start(server_socket)
|
857
|
+
ensure
|
858
|
+
@after_stop.call
|
859
|
+
end
|
860
|
+
|
861
|
+
nil
|
862
|
+
end
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
# Local Variables:
|
867
|
+
# mode: Ruby
|
868
|
+
# indent-tabs-mode: nil
|
869
|
+
# End:
|