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.
@@ -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: