serverengine 2.0.0pre1-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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