riser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,579 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'logger'
4
+ require 'syslog/logger'
5
+ require 'yaml'
6
+
7
+ module Riser
8
+ class StatusFile
9
+ def initialize(filename)
10
+ @filename = filename
11
+ end
12
+
13
+ def open
14
+ @file = File.open(@filename, File::WRONLY | File::CREAT, 0644)
15
+ self
16
+ end
17
+
18
+ def close
19
+ @file.close
20
+ nil
21
+ end
22
+
23
+ def lock
24
+ @file.flock(File::LOCK_EX | File::LOCK_NB)
25
+ end
26
+
27
+ def write(text)
28
+ @file.truncate(0)
29
+ @file.seek(0)
30
+ ret_val = @file.write(text)
31
+ @file.flush
32
+ ret_val
33
+ end
34
+ end
35
+
36
+ class RootProcess
37
+ class SystemOperation
38
+ def initialize(logger, module_Process: Process, class_IO: IO)
39
+ @logger = logger
40
+ @Process = module_Process
41
+ @IO = class_IO
42
+ end
43
+
44
+ def get_server_address(sockaddr_get)
45
+ begin
46
+ address_config = sockaddr_get.call
47
+ rescue
48
+ @logger.error("failed to get server address [#{$!}]")
49
+ @logger.debug($!) if @logger.debug?
50
+ return
51
+ end
52
+
53
+ server_address = SocketAddress.parse(address_config)
54
+ unless (server_address) then
55
+ @logger.error("failed to parse server address: #{address_config.inspect}")
56
+ end
57
+ server_address
58
+ end
59
+
60
+ def get_server_socket(server_address)
61
+ begin
62
+ server_address.open_server
63
+ rescue
64
+ @logger.error("failed to open server socket: #{server_address} [#{$!}]")
65
+ @logger.debug($!) if @logger.debug?
66
+ nil
67
+ end
68
+ end
69
+
70
+ def send_signal(pid, signal)
71
+ begin
72
+ @Process.kill(signal, pid)
73
+ rescue
74
+ @logger.error("failed to send signal (#{signal}) to process (pid: #{pid}) [#{$!}]")
75
+ @logger.debug($!) if @logger.debug?
76
+ nil
77
+ end
78
+ end
79
+
80
+ def wait(pid, flags=0)
81
+ begin
82
+ @Process.wait(pid, flags)
83
+ rescue
84
+ @logger.error("failed to wait(2) for process (pid: #{pid}) [#{$!}]")
85
+ @logger.debug($!) if @logger.debug?
86
+ nil
87
+ end
88
+ end
89
+
90
+ def pipe
91
+ begin
92
+ @IO.pipe
93
+ rescue
94
+ @logger.error("failed to pipe(2) [#{$!}]")
95
+ @logger.debug($!) if @logger.debug?
96
+ nil
97
+ end
98
+ end
99
+
100
+ def fork
101
+ begin
102
+ @Process.fork{ yield }
103
+ rescue
104
+ @logger.error("failed to fork(2) [#{$!}]")
105
+ @logger.debug($!) if @logger.debug?
106
+ nil
107
+ end
108
+ end
109
+
110
+ def gets(io)
111
+ begin
112
+ io.gets
113
+ rescue
114
+ @logger.error("failed to get line from #{io.inspect} [#{$!}]")
115
+ @logger.debug($!) if @logger.debug?
116
+ nil
117
+ end
118
+ end
119
+
120
+ def close(io)
121
+ begin
122
+ io.close
123
+ io
124
+ rescue
125
+ @logger.error("failed to close(2) #{io.inspect} [#{$!}]")
126
+ @logger.debug($!) if @logger.debug?
127
+ nil
128
+ end
129
+ end
130
+ end
131
+
132
+ include ServerSignal
133
+
134
+ def initialize(logger, sockaddr_get, server_polling_interval_seconds, server_restart_overlap_seconds=0, euid=nil, egid=nil, &block) # :yields: socket_server
135
+ @logger = logger
136
+ @sockaddr_get = sockaddr_get
137
+ @server_polling_interval_seconds = server_polling_interval_seconds
138
+ @server_restart_overlap_seconds = server_restart_overlap_seconds
139
+ @euid = euid
140
+ @egid = egid
141
+ @server_setup = block
142
+ @sysop = SystemOperation.new(@logger)
143
+ @stop_state = nil
144
+ @in_server_polling_sleep = false
145
+ @signal_operation_queue = []
146
+ @process_wait_count_table = {}
147
+ end
148
+
149
+ def server_polling_sleep
150
+ catch(:end_of_server_polling_sleep) {
151
+ begin
152
+ @in_server_polling_sleep = true
153
+ sleep(@server_polling_interval_seconds)
154
+ ensure
155
+ @in_server_polling_sleep = false
156
+ end
157
+ }
158
+ end
159
+ private :server_polling_sleep
160
+
161
+ def interrupt_server_polling_sleep
162
+ if (@in_server_polling_sleep) then
163
+ throw(:end_of_server_polling_sleep)
164
+ end
165
+ end
166
+ private :interrupt_server_polling_sleep
167
+
168
+ # should be called from signal(2) handler
169
+ def signal_stop_graceful
170
+ @stop_state ||= :graceful
171
+ interrupt_server_polling_sleep
172
+ nil
173
+ end
174
+
175
+ # should be called from signal(2) handler
176
+ def signal_stop_forced
177
+ @stop_state ||= :forced
178
+ interrupt_server_polling_sleep
179
+ nil
180
+ end
181
+
182
+ # should be called from signal(2) handler
183
+ def signal_restart_graceful
184
+ @signal_operation_queue << :restart_graceful
185
+ interrupt_server_polling_sleep
186
+ nil
187
+ end
188
+
189
+ # should be called from signal(2) handler
190
+ def signal_restart_forced
191
+ @signal_operation_queue << :restart_forced
192
+ interrupt_server_polling_sleep
193
+ nil
194
+ end
195
+
196
+ # should be called from signal(2) handler
197
+ def signal_stat_get(reset: true)
198
+ if (reset) then
199
+ @signal_operation_queue << :stat_get_and_reset
200
+ else
201
+ @signal_operation_queue << :stat_get_no_reset
202
+ end
203
+ interrupt_server_polling_sleep
204
+ nil
205
+ end
206
+
207
+ # should be called from signal(2) handler
208
+ def signal_stat_stop
209
+ @signal_operation_queue << :stat_stop
210
+ interrupt_server_polling_sleep
211
+ nil
212
+ end
213
+
214
+ # should be called from signal(2) handler
215
+ def signal_server_down
216
+ interrupt_server_polling_sleep
217
+ nil
218
+ end
219
+
220
+ def server_stop_graceful(pid)
221
+ @sysop.send_signal(pid, SIGNAL_STOP_GRACEFUL)
222
+ end
223
+ private :server_stop_graceful
224
+
225
+ def server_stop_forced(pid)
226
+ @sysop.send_signal(pid, SIGNAL_STOP_FORCED)
227
+ end
228
+ private :server_stop_forced
229
+
230
+ def run_server(server_socket)
231
+ read_write = @sysop.pipe
232
+ unless (read_write) then
233
+ @logger.error('failed to start server.')
234
+ return
235
+ end
236
+ latch_read_io, latch_write_io = read_write
237
+
238
+ pid = @sysop.fork{
239
+ begin
240
+ latch_read_io.close
241
+
242
+ if (@egid) then
243
+ @logger.info("change group privilege from #{Process::GID.eid} to #{@egid}")
244
+ Process::GID.change_privilege(@egid)
245
+ end
246
+
247
+ if (@euid) then
248
+ @logger.info("change user privilege from #{Process::UID.eid} to #{@euid}")
249
+ Process::UID.change_privilege(@euid)
250
+ end
251
+
252
+ server = SocketServer.new
253
+ @server_setup.call(server)
254
+ server.setup(server_socket)
255
+ Signal.trap(SIGNAL_STOP_GRACEFUL) { server.signal_stop_graceful }
256
+ Signal.trap(SIGNAL_STOP_FORCED) { server.signal_stop_forced }
257
+ Signal.trap(SIGNAL_STAT_GET_AND_RESET) { server.signal_stat_get(reset: true) }
258
+ Signal.trap(SIGNAL_STAT_GET_NO_RESET) { server.signal_stat_get(reset: false) }
259
+ Signal.trap(SIGNAL_STAT_STOP) { server.signal_stat_stop }
260
+ rescue
261
+ @logger.error("failed to setup server (pid: #{$$}) [#{$!}]")
262
+ @logger.debug($!) if @logger.debug?
263
+ raise
264
+ end
265
+ @logger.close
266
+ latch_write_io.puts("server process (pid: #{$$}) is ready to go.")
267
+
268
+ server.start(server_socket)
269
+ }
270
+
271
+ unless (pid) then
272
+ @sysop.close(latch_read_io)
273
+ @sysop.close(latch_write_io)
274
+ @logger.error('failed to start server.')
275
+ return
276
+ end
277
+
278
+ error_count = 0
279
+ @sysop.close(latch_write_io) or error_count += 1
280
+ server_messg = @sysop.gets(latch_read_io)
281
+ @sysop.close(latch_read_io) or error_count += 1
282
+
283
+ if (server_messg) then
284
+ @logger.debug("[server process message] #{server_messg.chomp}") if @logger.debug?
285
+ else
286
+ @logger.error("no response from server process (pid: #{pid})")
287
+ end
288
+
289
+ if (! server_messg || error_count > 0) then
290
+ @sysop.send_signal(pid, SIGNAL_STOP_FORCED) or @logger.error("failed to kill abnormal server process (pid: #{pid})")
291
+ @process_wait_count_table[pid] = 0
292
+ @logger.error('failed to start server.')
293
+ return
294
+ end
295
+
296
+ pid
297
+ end
298
+ private :run_server
299
+
300
+ # should be executed on the main thread sharing the stack with
301
+ # signal(2) handlers
302
+ def start
303
+ @logger.info('daemon start.')
304
+
305
+ unless (server_address = @sysop.get_server_address(@sockaddr_get)) then
306
+ @logger.fatal('failed to start daemon.')
307
+ return 1
308
+ end
309
+
310
+ unless (server_socket = @sysop.get_server_socket(server_address)) then
311
+ @logger.fatal('failed to start daemon.')
312
+ return 1
313
+ end
314
+ @logger.info("open server socket: #{server_socket.local_address.inspect_sockaddr}")
315
+
316
+ unless (server_pid = run_server(server_socket)) then
317
+ @logger.fatal('failed to start daemon.')
318
+ return 1
319
+ end
320
+ @logger.info("server process start (pid: #{server_pid})")
321
+
322
+ @logger.info("start server polling (interval seconds: #{@server_polling_interval_seconds})")
323
+ until (@stop_state)
324
+ server_polling_sleep
325
+ if (server_pid) then
326
+ @logger.debug("server polling... (pid: #{server_pid})") if @logger.debug?
327
+ else
328
+ @logger.debug('server polling...') if @logger.debug?
329
+ end
330
+
331
+ if (! server_pid || @sysop.wait(server_pid, Process::WNOHANG)) then
332
+ if (server_pid) then
333
+ @logger.warn("found server down (pid: #{server_pid}) and restart server.")
334
+ else
335
+ @logger.warn('found server down and restart server.')
336
+ end
337
+ if (server_pid = run_server(server_socket)) then
338
+ @logger.info("server process start (pid: #{server_pid})")
339
+ end
340
+ end
341
+
342
+ while (! @stop_state && server_pid && sig_ope = @signal_operation_queue.shift)
343
+ case (sig_ope)
344
+ when :restart_graceful, :restart_forced
345
+ if (next_server_address = @sysop.get_server_address(@sockaddr_get)) then
346
+ if (next_server_address != server_address) then
347
+ if (next_server_socket = @sysop.get_server_socket(next_server_address)) then
348
+ @logger.info("open server socket: #{next_server_socket.local_address.inspect_sockaddr}")
349
+ @logger.info("close server socket: #{server_socket.local_address.inspect_sockaddr}")
350
+ @sysop.close(server_socket) or @logger.warn("failed to close server socket (#{server_address})")
351
+ server_socket = next_server_socket
352
+ server_address = next_server_address
353
+ else
354
+ @logger.warn("server socket continue: #{server_socket.local_address.inspect_sockaddr}")
355
+ end
356
+ end
357
+ else
358
+ @logger.warn("server socket continue: #{server_socket.local_address.inspect_sockaddr}")
359
+ end
360
+
361
+ case (sig_ope)
362
+ when :restart_graceful
363
+ @logger.info("server graceful restart (pid: #{server_pid})")
364
+ when :restart_forced
365
+ @logger.info("server forced restart (pid: #{server_pid})")
366
+ else
367
+ @logger.warn("internal warning: unknown signal operation <#{sig_ope.inspect}>")
368
+ end
369
+
370
+ if (next_pid = run_server(server_socket)) then
371
+ @logger.info("server process start (pid: #{next_pid})")
372
+
373
+ if (@server_restart_overlap_seconds > 0) then
374
+ @logger.info("server restart overlap (interval seconds: #{@server_restart_overlap_seconds})")
375
+ sleep(@server_restart_overlap_seconds)
376
+ end
377
+
378
+ case (sig_ope)
379
+ when :restart_graceful
380
+ @logger.info("server graceful stop (pid: #{server_pid})")
381
+ server_stop_graceful(server_pid)
382
+ when :restart_forced
383
+ @logger.info("server forced stop (pid: #{server_pid})")
384
+ server_stop_forced(server_pid)
385
+ else
386
+ @logger.warn("internal warning: unknown signal operation <#{sig_ope.inspect}>")
387
+ end
388
+
389
+ @process_wait_count_table[server_pid] = 0
390
+ server_pid = next_pid
391
+ else
392
+ @logger.warn("server continue (pid: #{server_pid})")
393
+ end
394
+ when :stat_get_and_reset
395
+ @logger.info("stat get(reset: true) (pid: #{server_pid})")
396
+ @sysop.send_signal(server_pid, SIGNAL_STAT_GET_AND_RESET) or @logger.error("failed to stat get(reset: true) (pid: #{server_pid})")
397
+ when :stat_get_no_reset
398
+ @logger.info("stat get(reset: false) (pid: #{server_pid})")
399
+ @sysop.send_signal(server_pid, SIGNAL_STAT_GET_NO_RESET) or @logger.error("failed to stat get(reset: false) (pid: #{server_pid})")
400
+ when :stat_stop
401
+ @logger.info("stat stop (pid: #{server_pid})")
402
+ @sysop.send_signal(server_pid, SIGNAL_STAT_STOP) or @logger.error("failed to stat stop (pid: #{server_pid})")
403
+ else
404
+ @logger.warn("internal warning: unknown signal operation <#{sig_ope.inspect}>")
405
+ end
406
+ end
407
+
408
+ for pid in @process_wait_count_table.keys
409
+ if (@sysop.wait(pid, Process::WNOHANG)) then
410
+ @logger.info("server stop completed (pid: #{pid})")
411
+ @process_wait_count_table.delete(pid)
412
+ else
413
+ @process_wait_count_table[pid] += 1
414
+ if (@process_wait_count_table[pid] >= 2) then
415
+ @logger.warn("not stopped server process (pid: #{pid})")
416
+ end
417
+ end
418
+ end
419
+ end
420
+
421
+ if (server_pid) then
422
+ case (@stop_state)
423
+ when :graceful
424
+ @logger.info("server graceful stop (pid: #{server_pid})")
425
+ unless (server_stop_graceful(server_pid)) then
426
+ @logger.fatal('failed to stop daemon.')
427
+ return 1
428
+ end
429
+ unless (@sysop.wait(server_pid)) then
430
+ @logger.fatal('failed to stop daemon.')
431
+ return 1
432
+ end
433
+ when :forced
434
+ @logger.info("server forced stop (pid: #{server_pid})")
435
+ unless (server_stop_forced(server_pid)) then
436
+ @logger.fatal('failed to stop daemon.')
437
+ return 1
438
+ end
439
+ unless (@sysop.wait(server_pid)) then
440
+ @logger.fatal('failed to stop daemon.')
441
+ return 1
442
+ end
443
+ else
444
+ @logger.error("internal error: unknown stop state <#{@stop_state.inspect}>")
445
+ return 1
446
+ end
447
+ else
448
+ @logger.warn('no server to stop.')
449
+ end
450
+
451
+ @logger.info('daemon stop.')
452
+ return 0
453
+ end
454
+ end
455
+
456
+ module Daemon
457
+ include ServerSignal
458
+
459
+ def get_id(name, id_mod) # :nodoc:
460
+ if (name) then
461
+ case (name)
462
+ when Integer
463
+ name
464
+ when /\A \d+ \z/x
465
+ name.to_i
466
+ else
467
+ id_mod.from_name(name)
468
+ end
469
+ end
470
+ end
471
+ module_function :get_id
472
+
473
+ def get_uid(user)
474
+ get_id(user, Process::UID)
475
+ end
476
+ module_function :get_uid
477
+
478
+ def get_gid(group)
479
+ get_id(group, Process::GID)
480
+ end
481
+ module_function :get_gid
482
+
483
+ DEFAULT = {
484
+ daemonize: true,
485
+ daemon_name: 'ruby',
486
+ daemon_debug: $DEBUG,
487
+ daemon_nochdir: true,
488
+ status_file: nil,
489
+ listen_address: nil,
490
+ server_polling_interval_seconds: 3,
491
+ server_restart_overlap_seconds: 0,
492
+ server_privileged_user: nil,
493
+ server_privileged_group: nil,
494
+
495
+ signal_stop_graceful: SIGNAL_STOP_GRACEFUL,
496
+ signal_stop_forced: SIGNAL_STOP_FORCED,
497
+ signal_stat_get_and_reset: SIGNAL_STAT_GET_AND_RESET,
498
+ signal_stat_get_no_reset: SIGNAL_STAT_GET_NO_RESET,
499
+ signal_stat_stop: SIGNAL_STAT_STOP,
500
+ signal_restart_graceful: SIGNAL_RESTART_GRACEFUL,
501
+ signal_restart_forced: SIGNAL_RESTART_FORCED
502
+ }.freeze
503
+
504
+ # should be executed on the main thread sharing the stack with
505
+ # signal(2) handlers
506
+ def start_daemon(config, &block) # :yields: socket_server
507
+ c = DEFAULT.dup
508
+ c.update(config)
509
+
510
+ if (c[:status_file]) then
511
+ status_file = StatusFile.new(c[:status_file])
512
+ status_file.open
513
+ status_file.lock or abort("#{c[:daemon_name]} daemon is already running.")
514
+ end
515
+
516
+ if (c[:daemonize]) then
517
+ logger = Syslog::Logger.new(c[:daemon_name])
518
+ def logger.close
519
+ Syslog::Logger.syslog = nil
520
+ Syslog.close
521
+ nil
522
+ end
523
+ else
524
+ logger = Logger.new(STDOUT)
525
+ logger.progname = c[:daemon_name]
526
+ def logger.close # not close STDOUT
527
+ end
528
+ end
529
+
530
+ if (c[:daemon_debug]) then
531
+ logger.level = Logger::DEBUG
532
+ else
533
+ logger.level = Logger::INFO
534
+ end
535
+
536
+ if (c[:listen_address].respond_to? :call) then
537
+ sockaddr_get = c[:listen_address]
538
+ else
539
+ sockaddr_get = proc{ c[:listen_address] }
540
+ end
541
+
542
+ euid = get_uid(c[:server_privileged_user])
543
+ egid = get_gid(c[:server_privileged_group])
544
+
545
+ root_process = RootProcess.new(logger, sockaddr_get, c[:server_polling_interval_seconds], c[:server_restart_overlap_seconds], euid, egid, &block)
546
+ [ [ :signal_stop_graceful, proc{ root_process.signal_stop_graceful } ],
547
+ [ :signal_stop_forced, proc{ root_process.signal_stop_forced } ],
548
+ [ :signal_stat_get_and_reset, proc{ root_process.signal_stat_get(reset: true) } ],
549
+ [ :signal_stat_get_no_reset, proc{ root_process.signal_stat_get(reset: false) } ],
550
+ [ :signal_stat_stop, proc{ root_process.signal_stat_stop } ],
551
+ [ :signal_restart_graceful, proc{ root_process.signal_restart_graceful } ],
552
+ [ :signal_restart_forced, proc{ root_process.signal_restart_forced } ]
553
+ ].each{|sig_key, sig_hook|
554
+ if (signal = c[sig_key]) then
555
+ Signal.trap(signal, &sig_hook)
556
+ end
557
+ }
558
+ Signal.trap(:CHLD) { root_process.signal_server_down }
559
+
560
+ if (c[:daemonize]) then
561
+ Process.daemon(c[:daemon_nochdir], true)
562
+ end
563
+
564
+ # update after process ID changes in daemonization.
565
+ if (c[:status_file]) then
566
+ status_file.write({ 'pid' => $$ }.to_yaml)
567
+ end
568
+
569
+ status = root_process.start
570
+ exit(status)
571
+ end
572
+ module_function :start_daemon
573
+ end
574
+ end
575
+
576
+ # Local Variables:
577
+ # mode: Ruby
578
+ # indent-tabs-mode: nil
579
+ # End:
data/lib/riser/poll.rb ADDED
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'io/wait'
4
+
5
+ module Riser
6
+ class ReadPoll
7
+ def initialize(read_io)
8
+ @read_io = read_io
9
+ reset_timer
10
+ end
11
+
12
+ def reset_timer
13
+ @t0 = Time.now
14
+ self
15
+ end
16
+
17
+ def interval_seconds
18
+ Time.now - @t0
19
+ end
20
+
21
+ def read_poll(timeout_seconds)
22
+ readable = @read_io.wait_readable(timeout_seconds)
23
+ reset_timer unless readable.nil?
24
+ readable
25
+ end
26
+
27
+ alias poll read_poll
28
+ alias call read_poll
29
+ end
30
+ end
31
+
32
+ # Local Variables:
33
+ # mode: Ruby
34
+ # indent-tabs-mode: nil
35
+ # End: