serverengine 2.0.0pre1-x86-mingw32

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +20 -0
  5. data/Changelog +122 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE +202 -0
  8. data/NOTICE +3 -0
  9. data/README.md +514 -0
  10. data/Rakefile +26 -0
  11. data/appveyor.yml +24 -0
  12. data/examples/server.rb +138 -0
  13. data/examples/spawn_worker_script.rb +38 -0
  14. data/lib/serverengine.rb +46 -0
  15. data/lib/serverengine/blocking_flag.rb +77 -0
  16. data/lib/serverengine/command_sender.rb +89 -0
  17. data/lib/serverengine/config_loader.rb +82 -0
  18. data/lib/serverengine/daemon.rb +233 -0
  19. data/lib/serverengine/daemon_logger.rb +135 -0
  20. data/lib/serverengine/embedded_server.rb +67 -0
  21. data/lib/serverengine/multi_process_server.rb +155 -0
  22. data/lib/serverengine/multi_spawn_server.rb +95 -0
  23. data/lib/serverengine/multi_thread_server.rb +80 -0
  24. data/lib/serverengine/multi_worker_server.rb +150 -0
  25. data/lib/serverengine/privilege.rb +57 -0
  26. data/lib/serverengine/process_manager.rb +508 -0
  27. data/lib/serverengine/server.rb +178 -0
  28. data/lib/serverengine/signal_thread.rb +116 -0
  29. data/lib/serverengine/signals.rb +31 -0
  30. data/lib/serverengine/socket_manager.rb +171 -0
  31. data/lib/serverengine/socket_manager_unix.rb +98 -0
  32. data/lib/serverengine/socket_manager_win.rb +154 -0
  33. data/lib/serverengine/supervisor.rb +313 -0
  34. data/lib/serverengine/utils.rb +62 -0
  35. data/lib/serverengine/version.rb +3 -0
  36. data/lib/serverengine/winsock.rb +128 -0
  37. data/lib/serverengine/worker.rb +81 -0
  38. data/serverengine.gemspec +37 -0
  39. data/spec/blocking_flag_spec.rb +59 -0
  40. data/spec/daemon_logger_spec.rb +175 -0
  41. data/spec/daemon_spec.rb +169 -0
  42. data/spec/multi_process_server_spec.rb +113 -0
  43. data/spec/server_worker_context.rb +232 -0
  44. data/spec/signal_thread_spec.rb +94 -0
  45. data/spec/socket_manager_spec.rb +119 -0
  46. data/spec/spec_helper.rb +19 -0
  47. data/spec/supervisor_spec.rb +215 -0
  48. metadata +184 -0
@@ -0,0 +1,178 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012-2013 Sadayuki Furuhashi
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 'serverengine/signals'
19
+ require 'serverengine/signal_thread'
20
+ require 'serverengine/worker'
21
+
22
+ module ServerEngine
23
+
24
+ class Server
25
+ include ConfigLoader
26
+
27
+ def initialize(worker_module, load_config_proc={}, &block)
28
+ @worker_module = worker_module
29
+
30
+ @stop = false
31
+ @stop_status = nil
32
+
33
+ super(load_config_proc, &block)
34
+
35
+ @log_stdout = !!@config.fetch(:log_stdout, true)
36
+ @log_stderr = !!@config.fetch(:log_stderr, true)
37
+ @log_stdout = false if logdev_from_config(@config) == STDOUT
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)
42
+ end
43
+
44
+ def before_run
45
+ end
46
+
47
+ def after_run
48
+ end
49
+
50
+ def stop(stop_graceful)
51
+ @logger.info "Received #{stop_graceful ? 'graceful' : 'immediate'} stop" if @logger
52
+ @stop = true
53
+ nil
54
+ end
55
+
56
+ def after_start
57
+ end
58
+
59
+ def restart(stop_graceful)
60
+ @logger.info "Received #{stop_graceful ? 'graceful' : 'immediate'} restart" if @logger
61
+ reload_config
62
+ @logger.reopen! if @logger
63
+ nil
64
+ end
65
+
66
+ def reload
67
+ @logger.info "Received reload" if @logger
68
+ reload_config
69
+ @logger.reopen! if @logger
70
+ nil
71
+ end
72
+
73
+ def install_signal_handlers
74
+ s = self
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
109
+ end
110
+ end
111
+ end
112
+
113
+ def main
114
+ create_logger unless @logger
115
+
116
+ # start threads to transfer logs from STDOUT/ERR to the logger
117
+ start_io_logging_thread(STDOUT) if @log_stdout && try_get_io_from_logger(@logger) != STDOUT
118
+ start_io_logging_thread(STDERR) if @log_stderr && try_get_io_from_logger(@logger) != STDERR
119
+
120
+ before_run
121
+
122
+ begin
123
+ run
124
+ ensure
125
+ after_run
126
+ end
127
+ if @stop_status
128
+ raise SystemExit.new(@stop_status)
129
+ end
130
+ end
131
+
132
+ module WorkerInitializer
133
+ def initialize
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ # If :logger option is set unexpectedly, reading from STDOUT/ERR
140
+ # and writing to :logger could cause infinite loop because
141
+ # :logger may write data to STDOUT/ERR.
142
+ def try_get_io_from_logger(logger)
143
+ logdev = logger.instance_eval { @logdev }
144
+ if logdev.respond_to?(:dev)
145
+ # ::Logger
146
+ logdev.dev
147
+ else
148
+ # logdev is IO if DaemonLogger. otherwise unknown object including nil
149
+ logdev
150
+ end
151
+ end
152
+
153
+ def create_worker(wid)
154
+ w = Worker.new(self, wid)
155
+ w.extend(WorkerInitializer)
156
+ w.extend(@worker_module)
157
+ w.instance_eval { initialize }
158
+ w
159
+ end
160
+
161
+ def start_io_logging_thread(io)
162
+ r, w = IO.pipe
163
+ io.reopen(w)
164
+ w.close
165
+
166
+ Thread.new do
167
+ begin
168
+ while line = r.gets
169
+ @logger << line
170
+ end
171
+ rescue => e
172
+ ServerEngine.dump_uncaught_error(e)
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ end
@@ -0,0 +1,116 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012-2013 Sadayuki Furuhashi
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
+ module ServerEngine
19
+
20
+ class SignalThread < Thread
21
+ def initialize(&block)
22
+ @handlers = {}
23
+
24
+ @mutex = Mutex.new
25
+ @cond = ConditionVariable.new
26
+ @queue = []
27
+ @finished = false
28
+
29
+ block.call(self) if block
30
+
31
+ super(&method(:main))
32
+ end
33
+
34
+ def trap(sig, command=nil, &block)
35
+ # normalize signal names
36
+ sig = sig.to_s.upcase
37
+ if sig[0,3] == "SIG"
38
+ sig = sig[3..-1]
39
+ end
40
+ sig = sig.to_sym
41
+
42
+ old = @handlers[sig]
43
+ if block
44
+ Kernel.trap(sig) { signal_handler_main(sig) }
45
+ @handlers[sig] = block
46
+ else
47
+ Kernel.trap(sig, command)
48
+ @handlers.delete(sig)
49
+ end
50
+
51
+ old
52
+ end
53
+
54
+ def handlers
55
+ @handlers.dup
56
+ end
57
+
58
+ def stop
59
+ @mutex.synchronize do
60
+ @finished = true
61
+ @cond.broadcast
62
+ end
63
+ self
64
+ end
65
+
66
+ private
67
+
68
+ def signal_handler_main(sig)
69
+ # here always creates new thread to avoid
70
+ # complicated race conditin in signal handlers
71
+ Thread.new do
72
+ begin
73
+ enqueue(sig)
74
+ rescue => e
75
+ ServerEngine.dump_uncaught_error(e)
76
+ end
77
+ end
78
+ end
79
+
80
+ def main
81
+ until @finished
82
+ sig = nil
83
+
84
+ @mutex.synchronize do
85
+ while true
86
+ return if @finished
87
+
88
+ sig = @queue.shift
89
+ break if sig
90
+
91
+ @cond.wait(@mutex, 1)
92
+ end
93
+ end
94
+
95
+ begin
96
+ @handlers[sig].call(sig)
97
+ rescue => e
98
+ ServerEngine.dump_uncaught_error(e)
99
+ end
100
+ end
101
+
102
+ nil
103
+
104
+ ensure
105
+ @finished = false
106
+ end
107
+
108
+ def enqueue(sig)
109
+ @mutex.synchronize do
110
+ @queue << sig
111
+ @cond.broadcast
112
+ end
113
+ end
114
+
115
+ end
116
+ end
@@ -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
@@ -0,0 +1,171 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012-2013 Sadayuki Furuhashi
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 'socket'
19
+ require 'ipaddr'
20
+
21
+ module ServerEngine
22
+ module SocketManager
23
+
24
+ class Client
25
+ def initialize(path)
26
+ @path = path
27
+ end
28
+
29
+ def listen_tcp(bind, port)
30
+ peer = connect_peer(@path)
31
+ begin
32
+ SocketManager.send_peer(peer, [Process.pid, :listen_tcp, bind, port])
33
+ res = SocketManager.recv_peer(peer)
34
+ if res.is_a?(Exception)
35
+ raise res
36
+ else
37
+ return recv_tcp(peer, res)
38
+ end
39
+ ensure
40
+ peer.close
41
+ end
42
+ end
43
+
44
+ def listen_udp(bind, port)
45
+ peer = connect_peer(@path)
46
+ begin
47
+ SocketManager.send_peer(peer, [Process.pid, :listen_udp, bind, port])
48
+ res = SocketManager.recv_peer(peer)
49
+ if res.is_a?(Exception)
50
+ raise res
51
+ else
52
+ return recv_udp(peer, res)
53
+ end
54
+ ensure
55
+ peer.close
56
+ end
57
+ end
58
+ end
59
+
60
+ class Server
61
+ def self.generate_path
62
+ if ServerEngine.windows?
63
+ for port in 10000..65535
64
+ if `netstat -na | find "#{port}"`.length == 0
65
+ return port
66
+ end
67
+ end
68
+ else
69
+ '/tmp/SERVERENGINE_SOCKETMANAGER_' + Time.now.to_s.gsub(' ', '') + '_' + Process.pid.to_s
70
+ end
71
+ end
72
+
73
+ def self.open(path)
74
+ new(path)
75
+ end
76
+
77
+ def initialize(path)
78
+ @tcp_sockets = {}
79
+ @udp_sockets = {}
80
+ @mutex = Mutex.new
81
+ @path = start_server(path)
82
+ end
83
+
84
+ attr_reader :path
85
+
86
+ def new_client
87
+ Client.new(@path)
88
+ end
89
+
90
+ def close
91
+ stop_server
92
+ nil
93
+ end
94
+
95
+ private
96
+
97
+ def listen_tcp(bind, port)
98
+ key, bind_ip = resolve_bind_key(bind, port)
99
+
100
+ @mutex.synchronize do
101
+ if @tcp_sockets.has_key?(key)
102
+ return @tcp_sockets[key]
103
+ else
104
+ return @tcp_sockets[key] = listen_tcp_new(bind_ip, port)
105
+ end
106
+ end
107
+ end
108
+
109
+ def listen_udp(bind, port)
110
+ key, bind_ip = resolve_bind_key(bind, port)
111
+
112
+ @mutex.synchronize do
113
+ if @udp_sockets.has_key?(key)
114
+ return @udp_sockets[key]
115
+ else
116
+ return @udp_sockets[key] = listen_udp_new(bind_ip, port)
117
+ end
118
+ end
119
+ end
120
+
121
+ def resolve_bind_key(bind, port)
122
+ bind_ip = IPAddr.new(IPSocket.getaddress(bind))
123
+ if bind_ip.ipv6?
124
+ return "[#{bind_ip}]:#{port}", bind_ip
125
+ else
126
+ # assuming ipv4
127
+ if bind_ip == "127.0.0.1" or bind_ip == "0.0.0.0"
128
+ return "localhost:#{port}", bind_ip
129
+ end
130
+ return "#{bind_ip}:#{port}", bind_ip
131
+ end
132
+ end
133
+
134
+ def process_peer(peer)
135
+ while true
136
+ pid, method, bind, port = *SocketManager.recv_peer(peer)
137
+ begin
138
+ send_socket(peer, pid, method, bind, port)
139
+ rescue => e
140
+ SocketManager.send_peer(peer, e)
141
+ end
142
+ end
143
+ ensure
144
+ peer.close
145
+ end
146
+ end
147
+
148
+ def self.send_peer(peer, obj)
149
+ data = Marshal.dump(obj)
150
+ peer.write [data.bytesize].pack('N')
151
+ peer.write data
152
+ end
153
+
154
+ def self.recv_peer(peer)
155
+ len = peer.read(4).unpack('N').first
156
+ data = peer.read(len)
157
+ Marshal.load(data)
158
+ end
159
+
160
+ if ServerEngine.windows?
161
+ require_relative 'socket_manager_win'
162
+ Client.include(SocketManagerWin::ClientModule)
163
+ Server.include(SocketManagerWin::ServerModule)
164
+ else
165
+ require_relative 'socket_manager_unix'
166
+ Client.include(SocketManagerUnix::ClientModule)
167
+ Server.include(SocketManagerUnix::ServerModule)
168
+ end
169
+
170
+ end
171
+ end