serverengine 1.6.4 → 2.0.0pre1

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/Changelog +11 -0
  4. data/README.md +31 -3
  5. data/Rakefile +16 -3
  6. data/appveyor.yml +11 -5
  7. data/examples/server.rb +138 -0
  8. data/examples/spawn_worker_script.rb +38 -0
  9. data/lib/serverengine/blocking_flag.rb +2 -3
  10. data/lib/serverengine/command_sender.rb +89 -0
  11. data/lib/serverengine/config_loader.rb +2 -0
  12. data/lib/serverengine/daemon.rb +114 -86
  13. data/lib/serverengine/daemon_logger.rb +3 -139
  14. data/lib/serverengine/embedded_server.rb +2 -0
  15. data/lib/serverengine/multi_process_server.rb +28 -7
  16. data/lib/serverengine/multi_spawn_server.rb +17 -18
  17. data/lib/serverengine/multi_thread_server.rb +6 -0
  18. data/lib/serverengine/multi_worker_server.rb +14 -0
  19. data/lib/serverengine/privilege.rb +57 -0
  20. data/lib/serverengine/process_manager.rb +66 -48
  21. data/lib/serverengine/server.rb +45 -11
  22. data/lib/serverengine/signal_thread.rb +0 -2
  23. data/lib/serverengine/signals.rb +31 -0
  24. data/lib/serverengine/socket_manager.rb +3 -5
  25. data/lib/serverengine/socket_manager_unix.rb +1 -0
  26. data/lib/serverengine/socket_manager_win.rb +4 -2
  27. data/lib/serverengine/supervisor.rb +105 -25
  28. data/lib/serverengine/utils.rb +23 -0
  29. data/lib/serverengine/version.rb +1 -1
  30. data/lib/serverengine/worker.rb +10 -7
  31. data/lib/serverengine.rb +9 -27
  32. data/serverengine.gemspec +12 -1
  33. data/spec/daemon_logger_spec.rb +17 -12
  34. data/spec/daemon_spec.rb +147 -24
  35. data/spec/multi_process_server_spec.rb +59 -7
  36. data/spec/server_worker_context.rb +104 -0
  37. data/spec/signal_thread_spec.rb +61 -56
  38. data/spec/supervisor_spec.rb +113 -99
  39. metadata +40 -6
@@ -0,0 +1,57 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012-2013 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ require 'etc'
19
+
20
+ module ServerEngine
21
+ module Privilege
22
+ def self.get_etc_passwd(user)
23
+ if user.to_i.to_s == user
24
+ Etc.getpwuid(user.to_i)
25
+ else
26
+ Etc.getpwnam(user)
27
+ end
28
+ end
29
+
30
+ def self.get_etc_group(group)
31
+ if group.to_i.to_s == group
32
+ Etc.getgrgid(group.to_i)
33
+ else
34
+ Etc.getgrnam(group)
35
+ end
36
+ end
37
+
38
+ def self.change(user, group)
39
+ if user
40
+ etc_pw = get_etc_passwd(user)
41
+ user_groups = [etc_pw.gid]
42
+ Etc.setgrent
43
+ Etc.group { |gr| user_groups << gr.gid if gr.mem.include?(etc_pw.name) } # emulate 'id -G'
44
+
45
+ Process.groups = Process.groups | user_groups
46
+ Process::UID.change_privilege(etc_pw.uid)
47
+ end
48
+
49
+ if group
50
+ etc_group = get_etc_group(group)
51
+ Process::GID.change_privilege(etc_group.gid)
52
+ end
53
+
54
+ nil
55
+ end
56
+ end
57
+ end
@@ -15,9 +15,9 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
- module ServerEngine
18
+ require 'fcntl'
19
19
 
20
- require 'fcntl'
20
+ module ServerEngine
21
21
 
22
22
  class ProcessManager
23
23
  def initialize(config={})
@@ -57,7 +57,7 @@ module ServerEngine
57
57
  @read_buffer = ''
58
58
 
59
59
  if @auto_tick
60
- TickThread.new(self)
60
+ TickThread.new(@auto_tick_interval, &method(:tick))
61
61
  end
62
62
  end
63
63
 
@@ -69,6 +69,9 @@ module ServerEngine
69
69
  attr_reader :auto_tick, :auto_tick_interval
70
70
  attr_reader :enable_heartbeat, :auto_heartbeat
71
71
 
72
+ attr_accessor :command_sender
73
+ attr_reader :command_sender_pipe
74
+
72
75
  CONFIG_PARAMS = {
73
76
  heartbeat_interval: 1,
74
77
  heartbeat_timeout: 180,
@@ -96,6 +99,21 @@ module ServerEngine
96
99
  }
97
100
  end
98
101
 
102
+ def monitor_options
103
+ {
104
+ enable_heartbeat: @enable_heartbeat,
105
+ heartbeat_timeout: @heartbeat_timeout,
106
+ graceful_kill_signal: @graceful_kill_signal,
107
+ graceful_kill_timeout: @graceful_kill_timeout,
108
+ graceful_kill_interval: @graceful_kill_interval,
109
+ graceful_kill_interval_increment: @graceful_kill_interval_increment,
110
+ immediate_kill_signal: @immediate_kill_signal,
111
+ immediate_kill_timeout: @immediate_kill_timeout,
112
+ immediate_kill_interval: @immediate_kill_interval,
113
+ immediate_kill_interval_increment: @immediate_kill_interval_increment,
114
+ }
115
+ end
116
+
99
117
  def fork(&block)
100
118
  if ServerEngine.windows?
101
119
  raise NotImplementedError, "fork is not available on this platform. Please use spawn (worker_type: 'spawn')."
@@ -109,7 +127,7 @@ module ServerEngine
109
127
  begin
110
128
  t = Target.new(wpipe)
111
129
  if @enable_heartbeat && @auto_heartbeat
112
- HeartbeatThread.new(self, t, @heartbeat_error_proc)
130
+ HeartbeatThread.new(@heartbeat_interval, t, @heartbeat_error_proc)
113
131
  end
114
132
 
115
133
  block.call(t)
@@ -122,7 +140,7 @@ module ServerEngine
122
140
  end
123
141
  end
124
142
 
125
- m = Monitor.new(self, pid)
143
+ m = Monitor.new(pid, monitor_options)
126
144
 
127
145
  @monitors << m
128
146
  @rpipes[rpipe] = m
@@ -161,9 +179,18 @@ module ServerEngine
161
179
  end
162
180
  end
163
181
 
182
+ if @command_sender == "pipe"
183
+ inpipe, @command_sender_pipe = IO.pipe
184
+ @command_sender_pipe.sync = true
185
+ @command_sender_pipe.binmode
186
+ options[:in] = inpipe
187
+ end
164
188
  pid = Process.spawn(env, *args, options)
189
+ if @command_sender == "pipe"
190
+ inpipe.close
191
+ end
165
192
 
166
- m = Monitor.new(self, pid)
193
+ m = Monitor.new(pid, monitor_options)
167
194
 
168
195
  @monitors << m
169
196
 
@@ -251,38 +278,28 @@ module ServerEngine
251
278
  nil
252
279
  end
253
280
 
254
- def self.format_signal_name(n)
255
- Signal.list.each_pair {|k,v|
256
- return "SIG#{k}" if n == v
257
- }
258
- return n
259
- end
260
-
261
- def self.format_join_status(code)
262
- case code
263
- when Process::Status
264
- if code.signaled?
265
- "signal #{format_signal_name(code.termsig)}"
266
- else
267
- "status #{code.exitstatus}"
268
- end
269
- when Exception
270
- "exception #{code}"
271
- when nil
272
- "unknown reason"
273
- end
274
- end
275
-
276
281
  class AlreadyClosedError < EOFError
277
282
  end
278
283
 
279
284
  HEARTBEAT_MESSAGE = [0].pack('C')
280
285
 
281
286
  class Monitor
282
- def initialize(pm, pid)
283
- @pm = pm
287
+ def initialize(pid, opts={})
284
288
  @pid = pid
285
289
 
290
+ @enable_heartbeat = opts[:enable_heartbeat]
291
+ @heartbeat_timeout = opts[:heartbeat_timeout]
292
+
293
+ @graceful_kill_signal = opts[:graceful_kill_signal]
294
+ @graceful_kill_timeout = opts[:graceful_kill_timeout]
295
+ @graceful_kill_interval = opts[:graceful_kill_interval]
296
+ @graceful_kill_interval_increment = opts[:graceful_kill_interval_increment]
297
+
298
+ @immediate_kill_signal = opts[:immediate_kill_signal]
299
+ @immediate_kill_timeout = opts[:immediate_kill_timeout]
300
+ @immediate_kill_interval = opts[:immediate_kill_interval]
301
+ @immediate_kill_interval_increment = opts[:immediate_kill_interval_increment]
302
+
286
303
  @error = false
287
304
  @last_heartbeat_time = Time.now
288
305
  @next_kill_time = nil
@@ -367,13 +384,13 @@ module ServerEngine
367
384
  # check heartbeat timeout or escalation
368
385
  if (
369
386
  # heartbeat timeout
370
- @pm.enable_heartbeat &&
371
- heartbeat_delay >= @pm.heartbeat_timeout
387
+ @enable_heartbeat &&
388
+ heartbeat_delay >= @heartbeat_timeout
372
389
  ) || (
373
390
  # escalation
374
391
  @graceful_kill_start_time &&
375
- @pm.graceful_kill_timeout >= 0 &&
376
- @graceful_kill_start_time < now - @pm.graceful_kill_timeout
392
+ @graceful_kill_timeout >= 0 &&
393
+ @graceful_kill_start_time < now - @graceful_kill_timeout
377
394
  )
378
395
  # escalate to immediate kill
379
396
  @kill_count = 0
@@ -390,20 +407,20 @@ module ServerEngine
390
407
  # send signal now
391
408
 
392
409
  if @immediate_kill_start_time
393
- interval = @pm.immediate_kill_interval
394
- interval_incr = @pm.immediate_kill_interval_increment
395
- if @pm.immediate_kill_timeout >= 0 &&
396
- @immediate_kill_start_time <= now - @pm.immediate_kill_timeout
410
+ interval = @immediate_kill_interval
411
+ interval_incr = @immediate_kill_interval_increment
412
+ if @immediate_kill_timeout >= 0 &&
413
+ @immediate_kill_start_time <= now - @immediate_kill_timeout
397
414
  # escalate to SIGKILL
398
415
  signal = :KILL
399
416
  else
400
- signal = @pm.immediate_kill_signal
417
+ signal = @immediate_kill_signal
401
418
  end
402
419
 
403
420
  else
404
- signal = @pm.graceful_kill_signal
405
- interval = @pm.graceful_kill_interval
406
- interval_incr = @pm.graceful_kill_interval_increment
421
+ signal = @graceful_kill_signal
422
+ interval = @graceful_kill_interval
423
+ interval_incr = @graceful_kill_interval_increment
407
424
  end
408
425
 
409
426
  begin
@@ -427,8 +444,9 @@ module ServerEngine
427
444
  end
428
445
 
429
446
  class TickThread < Thread
430
- def initialize(pm)
431
- @pm = pm
447
+ def initialize(auto_tick_interval, &tick)
448
+ @auto_tick_interval = auto_tick_interval
449
+ @tick = tick
432
450
  super(&method(:main))
433
451
  end
434
452
 
@@ -436,7 +454,7 @@ module ServerEngine
436
454
 
437
455
  def main
438
456
  while true
439
- @pm.tick(@pm.auto_tick_interval)
457
+ @tick.call(@auto_tick_interval)
440
458
  end
441
459
  nil
442
460
  rescue AlreadyClosedError
@@ -464,8 +482,8 @@ module ServerEngine
464
482
  end
465
483
 
466
484
  class HeartbeatThread < Thread
467
- def initialize(pm, target, error_proc)
468
- @pm = pm
485
+ def initialize(heartbeat_interval, target, error_proc)
486
+ @heartbeat_interval = heartbeat_interval
469
487
  @target = target
470
488
  @error_proc = error_proc
471
489
  super(&method(:main))
@@ -475,7 +493,7 @@ module ServerEngine
475
493
 
476
494
  def main
477
495
  while true
478
- sleep @pm.heartbeat_interval
496
+ sleep @heartbeat_interval
479
497
  @target.heartbeat!
480
498
  end
481
499
  nil
@@ -15,6 +15,10 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
+ require 'serverengine/signals'
19
+ require 'serverengine/signal_thread'
20
+ require 'serverengine/worker'
21
+
18
22
  module ServerEngine
19
23
 
20
24
  class Server
@@ -24,6 +28,7 @@ module ServerEngine
24
28
  @worker_module = worker_module
25
29
 
26
30
  @stop = false
31
+ @stop_status = nil
27
32
 
28
33
  super(load_config_proc, &block)
29
34
 
@@ -31,6 +36,9 @@ module ServerEngine
31
36
  @log_stderr = !!@config.fetch(:log_stderr, true)
32
37
  @log_stdout = false if logdev_from_config(@config) == STDOUT
33
38
  @log_stderr = false if logdev_from_config(@config) == STDERR
39
+
40
+ @command_sender = @config.fetch(:command_sender, ServerEngine.windows? ? "pipe" : "signal")
41
+ @command_pipe = @config.fetch(:command_pipe, nil)
34
42
  end
35
43
 
36
44
  def before_run
@@ -64,17 +72,40 @@ module ServerEngine
64
72
 
65
73
  def install_signal_handlers
66
74
  s = self
67
- SignalThread.new do |st|
68
- st.trap(@config[:signal_graceful_stop] || Daemon::Signals::GRACEFUL_STOP) { s.stop(true) }
69
- st.trap(@config[:signal_detach] || Daemon::Signals::DETACH) { s.stop(true) }
70
- # Here disables signals excepting GRACEFUL_STOP == :SIGTERM because
71
- # only SIGTERM is available on all version of Windows.
72
- unless ServerEngine.windows?
73
- st.trap(@config[:signal_immediate_stop] || Daemon::Signals::IMMEDIATE_STOP) { s.stop(false) }
74
- st.trap(@config[:signal_graceful_restart] || Daemon::Signals::GRACEFUL_RESTART) { s.restart(true) }
75
- st.trap(@config[:signal_immediate_restart] || Daemon::Signals::IMMEDIATE_RESTART) { s.restart(false) }
76
- st.trap(@config[:signal_reload] || Daemon::Signals::RELOAD) { s.reload }
77
- st.trap(@config[:signal_dump] || Daemon::Signals::DUMP) { Sigdump.dump }
75
+ if @command_pipe
76
+ Thread.new do
77
+ until @command_pipe.closed?
78
+ case @command_pipe.gets.chomp
79
+ when "GRACEFUL_STOP"
80
+ s.stop(true)
81
+ when "IMMEDIATE_STOP"
82
+ s.stop(false)
83
+ when "GRACEFUL_RESTART"
84
+ s.restart(true)
85
+ when "IMMEDIATE_RESTART"
86
+ s.restart(false)
87
+ when "RELOAD"
88
+ s.reload
89
+ when "DETACH"
90
+ s.detach(true)
91
+ when "DUMP"
92
+ Sigdump.dump
93
+ end
94
+ end
95
+ end
96
+ else
97
+ SignalThread.new do |st|
98
+ st.trap(@config[:signal_graceful_stop] || Signals::GRACEFUL_STOP) { s.stop(true) }
99
+ st.trap(@config[:signal_detach] || Signals::DETACH) { s.stop(true) }
100
+ # Here disables signals excepting GRACEFUL_STOP == :SIGTERM because
101
+ # only SIGTERM is available on all version of Windows.
102
+ unless ServerEngine.windows?
103
+ st.trap(@config[:signal_immediate_stop] || Signals::IMMEDIATE_STOP) { s.stop(false) }
104
+ st.trap(@config[:signal_graceful_restart] || Signals::GRACEFUL_RESTART) { s.restart(true) }
105
+ st.trap(@config[:signal_immediate_restart] || Signals::IMMEDIATE_RESTART) { s.restart(false) }
106
+ st.trap(@config[:signal_reload] || Signals::RELOAD) { s.reload }
107
+ st.trap(@config[:signal_dump] || Signals::DUMP) { Sigdump.dump }
108
+ end
78
109
  end
79
110
  end
80
111
  end
@@ -93,6 +124,9 @@ module ServerEngine
93
124
  ensure
94
125
  after_run
95
126
  end
127
+ if @stop_status
128
+ raise SystemExit.new(@stop_status)
129
+ end
96
130
  end
97
131
 
98
132
  module WorkerInitializer
@@ -19,8 +19,6 @@ module ServerEngine
19
19
 
20
20
  class SignalThread < Thread
21
21
  def initialize(&block)
22
- require 'thread'
23
-
24
22
  @handlers = {}
25
23
 
26
24
  @mutex = Mutex.new
@@ -0,0 +1,31 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012-2013 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'serverengine/utils'
20
+
21
+ module ServerEngine
22
+ module Signals
23
+ GRACEFUL_STOP = :TERM
24
+ IMMEDIATE_STOP = ServerEngine::windows? ? :KILL : :QUIT
25
+ GRACEFUL_RESTART = :USR1
26
+ IMMEDIATE_RESTART = :HUP
27
+ RELOAD = :USR2
28
+ DETACH = :INT
29
+ DUMP = :CONT
30
+ end
31
+ end
@@ -15,12 +15,12 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
+ require 'socket'
19
+ require 'ipaddr'
20
+
18
21
  module ServerEngine
19
22
  module SocketManager
20
23
 
21
- require 'socket'
22
- require 'ipaddr'
23
-
24
24
  class Client
25
25
  def initialize(path)
26
26
  @path = path
@@ -157,8 +157,6 @@ module ServerEngine
157
157
  Marshal.load(data)
158
158
  end
159
159
 
160
- require_relative 'utils'
161
-
162
160
  if ServerEngine.windows?
163
161
  require_relative 'socket_manager_win'
164
162
  Client.include(SocketManagerWin::ClientModule)
@@ -15,6 +15,7 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
+ require 'socket'
18
19
 
19
20
  module ServerEngine
20
21
  module SocketManagerUnix
@@ -15,12 +15,14 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
+ require 'socket'
19
+ require 'ipaddr'
20
+
21
+ require_relative 'winsock'
18
22
 
19
23
  module ServerEngine
20
24
  module SocketManagerWin
21
25
 
22
- require_relative 'winsock'
23
-
24
26
  module ClientModule
25
27
  private
26
28
 
@@ -15,6 +15,17 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
+ require 'serverengine/config_loader'
19
+ require 'serverengine/blocking_flag'
20
+ require 'serverengine/process_manager'
21
+ require 'serverengine/command_sender'
22
+ require 'serverengine/signals'
23
+
24
+ require 'serverengine/embedded_server'
25
+ require 'serverengine/multi_process_server'
26
+ require 'serverengine/multi_thread_server'
27
+ require 'serverengine/multi_spawn_server'
28
+
18
29
  module ServerEngine
19
30
 
20
31
  class Supervisor
@@ -29,8 +40,8 @@ module ServerEngine
29
40
 
30
41
  @pm = ProcessManager.new(
31
42
  auto_tick: false,
32
- graceful_kill_signal: Daemon::Signals::GRACEFUL_STOP,
33
- immediate_kill_signal: Daemon::Signals::IMMEDIATE_STOP,
43
+ graceful_kill_signal: Signals::GRACEFUL_STOP,
44
+ immediate_kill_signal: Signals::IMMEDIATE_STOP,
34
45
  enable_heartbeat: true,
35
46
  auto_heartbeat: true,
36
47
  )
@@ -41,9 +52,19 @@ module ServerEngine
41
52
  @server_process_name = @config[:server_process_name]
42
53
 
43
54
  @restart_server_process = !!@config[:restart_server_process]
55
+ @unrecoverable_exit_codes = @config.fetch(:unrecoverable_exit_codes, [])
44
56
  @enable_detach = !!@config[:enable_detach]
45
57
  @exit_on_detach = !!@config[:exit_on_detach]
46
58
  @disable_reload = !!@config[:disable_reload]
59
+
60
+ @command_pipe = @config.fetch(:command_pipe, nil)
61
+
62
+ @command_sender = @config.fetch(:command_sender, ServerEngine.windows? ? "pipe" : "signal")
63
+ if @command_sender == "pipe"
64
+ extend CommandSender::Pipe
65
+ else
66
+ extend CommandSender::Signal
67
+ end
47
68
  end
48
69
 
49
70
  # server is available after start_server() call.
@@ -97,16 +118,16 @@ module ServerEngine
97
118
 
98
119
  def stop(stop_graceful)
99
120
  @stop = true
100
- send_signal(stop_graceful ? Daemon::Signals::GRACEFUL_STOP : Daemon::Signals::IMMEDIATE_STOP)
121
+ _stop(stop_graceful)
101
122
  end
102
123
 
103
124
  def restart(stop_graceful)
104
125
  reload_config
105
126
  @logger.reopen! if @logger
106
127
  if @restart_server_process
107
- send_signal(stop_graceful ? Daemon::Signals::GRACEFUL_STOP : Daemon::Signals::IMMEDIATE_STOP)
128
+ _stop(stop_graceful)
108
129
  else
109
- send_signal(stop_graceful ? Daemon::Signals::GRACEFUL_RESTART : Daemon::Signals::IMMEDIATE_RESTART)
130
+ _restart(stop_graceful)
110
131
  end
111
132
  end
112
133
 
@@ -115,13 +136,13 @@ module ServerEngine
115
136
  reload_config
116
137
  end
117
138
  @logger.reopen! if @logger
118
- send_signal(Daemon::Signals::RELOAD)
139
+ _reload
119
140
  end
120
141
 
121
142
  def detach(stop_graceful)
122
143
  if @enable_detach
123
144
  @detach_flag.set!
124
- send_signal(stop_graceful ? Daemon::Signals::GRACEFUL_STOP : Daemon::Signals::IMMEDIATE_STOP)
145
+ _stop(stop_graceful)
125
146
  else
126
147
  stop(stop_graceful)
127
148
  end
@@ -129,14 +150,37 @@ module ServerEngine
129
150
 
130
151
  def install_signal_handlers
131
152
  s = self
132
- SignalThread.new do |st|
133
- st.trap(Daemon::Signals::GRACEFUL_STOP) { s.stop(true) }
134
- st.trap(Daemon::Signals::IMMEDIATE_STOP) { s.stop(false) }
135
- st.trap(Daemon::Signals::GRACEFUL_RESTART) { s.restart(true) }
136
- st.trap(Daemon::Signals::IMMEDIATE_RESTART) { s.restart(false) }
137
- st.trap(Daemon::Signals::RELOAD) { s.reload }
138
- st.trap(Daemon::Signals::DETACH) { s.detach(true) }
139
- st.trap(Daemon::Signals::DUMP) { Sigdump.dump }
153
+ if @command_pipe
154
+ Thread.new do
155
+ until @command_pipe.closed?
156
+ case @command_pipe.gets.chomp
157
+ when "GRACEFUL_STOP"
158
+ s.stop(true)
159
+ when "IMMEDIATE_STOP"
160
+ s.stop(false)
161
+ when "GRACEFUL_RESTART"
162
+ s.restart(true)
163
+ when "IMMEDIATE_RESTART"
164
+ s.restart(false)
165
+ when "RELOAD"
166
+ s.reload
167
+ when "DETACH"
168
+ s.detach(true)
169
+ when "DUMP"
170
+ Sigdump.dump
171
+ end
172
+ end
173
+ end
174
+ else
175
+ SignalThread.new do |st|
176
+ st.trap(Signals::GRACEFUL_STOP) { s.stop(true) }
177
+ st.trap(Signals::IMMEDIATE_STOP) { s.stop(false) }
178
+ st.trap(Signals::GRACEFUL_RESTART) { s.restart(true) }
179
+ st.trap(Signals::IMMEDIATE_RESTART) { s.restart(false) }
180
+ st.trap(Signals::RELOAD) { s.reload }
181
+ st.trap(Signals::DETACH) { s.detach(true) }
182
+ st.trap(Signals::DUMP) { Sigdump.dump }
183
+ end
140
184
  end
141
185
  end
142
186
 
@@ -151,6 +195,9 @@ module ServerEngine
151
195
  until @detach_flag.wait(0.5)
152
196
  if try_join
153
197
  return if @stop # supervisor stoppped explicitly
198
+ if @stop_status # set exit code told by server
199
+ raise SystemExit.new(@stop_status)
200
+ end
154
201
 
155
202
  # child process died unexpectedly.
156
203
  # sleep @server_detach_wait sec and reboot process
@@ -184,7 +231,10 @@ module ServerEngine
184
231
 
185
232
  def try_join
186
233
  if stat = @pmon.try_join
187
- @logger.info "Server finished#{@stop ? '' : ' unexpectedly'} with #{ProcessManager.format_join_status(stat)}"
234
+ @logger.info "Server finished#{@stop ? '' : ' unexpectedly'} with #{ServerEngine.format_join_status(stat)}"
235
+ if !@stop && stat.is_a?(Process::Status) && stat.exited? && @unrecoverable_exit_codes.include?(stat.exitstatus)
236
+ @stop_status = stat.exitstatus
237
+ end
188
238
  @pmon = nil
189
239
  return stat
190
240
  else
@@ -194,20 +244,50 @@ module ServerEngine
194
244
  end
195
245
 
196
246
  def start_server
197
- s = create_server(logger)
198
- @last_start_time = Time.now
247
+ if @command_sender == "pipe"
248
+ inpipe, @command_sender_pipe = IO.pipe
249
+ end
199
250
 
200
- begin
201
- m = @pm.fork do
202
- $0 = @server_process_name if @server_process_name
203
- s.install_signal_handlers
251
+ unless ServerEngine.windows?
252
+ s = create_server(logger)
253
+ @last_start_time = Time.now
204
254
 
205
- s.main
255
+ begin
256
+ m = @pm.fork do
257
+ $0 = @server_process_name if @server_process_name
258
+ if @command_sender == "pipe"
259
+ @command_sender_pipe.close
260
+ s.instance_variable_set(:@command_pipe, inpipe)
261
+ end
262
+ s.install_signal_handlers
263
+
264
+ begin
265
+ s.main
266
+ rescue SystemExit => e
267
+ @logger.info "Server is exitting with code #{e.status}"
268
+ exit! e.status
269
+ end
270
+ end
271
+ if @command_sender == "pipe"
272
+ inpipe.close
273
+ end
274
+
275
+ return m
276
+ ensure
277
+ s.after_start
278
+ end
279
+ else # if ServerEngine.windows?
280
+ exconfig = {}
281
+ if @command_sender == "pipe"
282
+ exconfig[:in] = inpipe
283
+ end
284
+ @last_start_time = Time.now
285
+ m = @pm.spawn(*Array(config[:windows_daemon_cmdline]), exconfig)
286
+ if @command_sender == "pipe"
287
+ inpipe.close
206
288
  end
207
289
 
208
290
  return m
209
- ensure
210
- s.after_start
211
291
  end
212
292
  end
213
293