serverengine 2.0.0pre1-x64-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,62 @@
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
+ IS_WINDOWS = /mswin|mingw/ === RUBY_PLATFORM
21
+ private_constant :IS_WINDOWS
22
+
23
+ def self.windows?
24
+ IS_WINDOWS
25
+ end
26
+
27
+ module ClassMethods
28
+ def dump_uncaught_error(e)
29
+ STDERR.write "Unexpected error #{e}\n"
30
+ e.backtrace.each {|bt|
31
+ STDERR.write " #{bt}\n"
32
+ }
33
+ nil
34
+ end
35
+
36
+ def format_signal_name(n)
37
+ Signal.list.each_pair {|k,v|
38
+ return "SIG#{k}" if n == v
39
+ }
40
+ return n
41
+ end
42
+
43
+ def format_join_status(code)
44
+ case code
45
+ when Process::Status
46
+ if code.signaled?
47
+ "signal #{format_signal_name(code.termsig)}"
48
+ else
49
+ "status #{code.exitstatus}"
50
+ end
51
+ when Exception
52
+ "exception #{code}"
53
+ when nil
54
+ "unknown reason"
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ extend ClassMethods
61
+
62
+ end
@@ -0,0 +1,3 @@
1
+ module ServerEngine
2
+ VERSION = "2.0.0pre1"
3
+ end
@@ -0,0 +1,128 @@
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
+ module WinSock
20
+
21
+ require 'fiddle/import'
22
+ require 'fiddle/types'
23
+ require 'socket'
24
+
25
+ extend Fiddle::Importer
26
+
27
+ dlload "ws2_32.dll"
28
+ include Fiddle::Win32Types
29
+
30
+ extern "int WSASocketA(int, int, int, void *, int, DWORD)"
31
+ extern "long inet_addr(char *)"
32
+ extern "int bind(int, void *, int)"
33
+ extern "int listen(int, int)"
34
+ extern "int WSADuplicateSocketA(int, DWORD, void *)"
35
+ extern "int WSAGetLastError()"
36
+
37
+ SockaddrIn = struct(["short sin_family",
38
+ "short sin_port",
39
+ "long sin_addr",
40
+ "char sin_zero[8]",
41
+ ])
42
+
43
+ WSAPROTOCOL_INFO = struct(["DWORD dwServiceFlags1",
44
+ "DWORD dwServiceFlags2",
45
+ "DWORD dwServiceFlags3",
46
+ "DWORD dwServiceFlags4",
47
+ "DWORD dwProviderFlags",
48
+ "DWORD Data1",
49
+ "WORD Data2",
50
+ "WORD Data3",
51
+ "BYTE Data4[8]",
52
+ "DWORD dwCatalogEntryId",
53
+ "int ChainLen",
54
+ "DWORD ChainEntries[7]",
55
+ "int iVersion",
56
+ "int iAddressFamily",
57
+ "int iMaxSockAddr",
58
+ "int iMinSockAddr",
59
+ "int iSocketType",
60
+ "int iProtocol",
61
+ "int iProtocolMaxOffset",
62
+ "int iNetworkByteOrder",
63
+ "int iSecurityScheme",
64
+ "DWORD dwMessageSize",
65
+ "DWORD dwProviderReserved",
66
+ "char szProtocol[256]",
67
+ ])
68
+
69
+ class WSAPROTOCOL_INFO
70
+ def self.from_bin(bin)
71
+ proto = malloc
72
+ proto.to_ptr.ref.ptr[0, size] = bin
73
+ proto
74
+ end
75
+
76
+ def to_bin
77
+ to_ptr.to_s
78
+ end
79
+ end
80
+
81
+ INVALID_SOCKET = -1
82
+ end
83
+
84
+ module RbWinSock
85
+ extend Fiddle::Importer
86
+
87
+ dlload "kernel32"
88
+ extern "int GetModuleFileNameA(int, char *, int)"
89
+ extern "int CloseHandle(int)"
90
+
91
+ ruby_bin_path_buf = Fiddle::Pointer.malloc(1000)
92
+ GetModuleFileNameA(0, ruby_bin_path_buf, ruby_bin_path_buf.size)
93
+
94
+ ruby_bin_path = ruby_bin_path_buf.to_s.gsub(/\\/, '/')
95
+ ruby_dll_paths = File.dirname(ruby_bin_path) + '/*msvcr*ruby*.dll'
96
+ ruby_dll_path = Dir.glob(ruby_dll_paths).first
97
+ dlload ruby_dll_path
98
+
99
+ extern "int rb_w32_map_errno(int)"
100
+ extern "void rb_syserr_fail(int, char *)"
101
+
102
+ def self.raise_last_error(name)
103
+ errno = rb_w32_map_errno(WinSock.WSAGetLastError)
104
+ rb_syserr_fail(errno, name)
105
+ end
106
+
107
+ extern "int rb_w32_wrap_io_handle(int, int)"
108
+
109
+ def self.wrap_io_handle(sock_class, handle, flags)
110
+ begin
111
+ fd = rb_w32_wrap_io_handle(handle, flags)
112
+ if fd < 0
113
+ raise_last_error("rb_w32_wrap_io_handle(3)")
114
+ end
115
+
116
+ sock = sock_class.for_fd(fd)
117
+ wrapped = true
118
+ sock.define_singleton_method(:handle) { handle }
119
+
120
+ return sock
121
+ ensure
122
+ unless wrapped
123
+ WinSock.CloseHandle(handle)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,81 @@
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
+
21
+ module ServerEngine
22
+
23
+ class Worker
24
+ def initialize(server, worker_id)
25
+ @server = server
26
+ @logger = @server.logger
27
+ @worker_id = worker_id
28
+ end
29
+
30
+ attr_reader :server, :worker_id
31
+ attr_accessor :logger
32
+
33
+ def config
34
+ @server.config
35
+ end
36
+
37
+ def before_fork
38
+ end
39
+
40
+ def run
41
+ raise NoMethodError, "Worker#run method is not implemented"
42
+ end
43
+
44
+ def spawn(process_manager)
45
+ raise NoMethodError, "Worker#spawn(process_manager) method is required for worker_type=spawn"
46
+ end
47
+
48
+ def stop
49
+ end
50
+
51
+ def reload
52
+ end
53
+
54
+ def after_start
55
+ end
56
+
57
+ def install_signal_handlers
58
+ w = self
59
+ SignalThread.new do |st|
60
+ st.trap(Signals::GRACEFUL_STOP) { w.stop }
61
+ st.trap(Signals::IMMEDIATE_STOP, 'SIG_DFL')
62
+
63
+ st.trap(Signals::GRACEFUL_RESTART) { w.stop }
64
+ st.trap(Signals::IMMEDIATE_RESTART, 'SIG_DFL')
65
+
66
+ st.trap(Signals::RELOAD) {
67
+ w.logger.reopen!
68
+ w.reload
69
+ }
70
+ st.trap(Signals::DETACH) { w.stop }
71
+
72
+ st.trap(Signals::DUMP) { Sigdump.dump }
73
+ end
74
+ end
75
+
76
+ def main
77
+ run
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path 'lib/serverengine/version', File.dirname(__FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "serverengine"
5
+ gem.version = ServerEngine::VERSION
6
+
7
+ gem.authors = ["Sadayuki Furuhashi"]
8
+ gem.email = ["frsyuki@gmail.com"]
9
+ gem.description = %q{A framework to implement robust multiprocess servers like Unicorn}
10
+ gem.summary = %q{ServerEngine - multiprocess server framework}
11
+ gem.homepage = "https://github.com/fluent/serverengine"
12
+ gem.license = "Apache 2.0"
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+ gem.has_rdoc = false
19
+
20
+ gem.required_ruby_version = ">= 2.1.0"
21
+
22
+ gem.add_dependency "sigdump", ["~> 0.2.2"]
23
+
24
+ gem.add_development_dependency "rake", [">= 0.9.2"]
25
+ gem.add_development_dependency "rspec", ["~> 2.13.0"]
26
+
27
+ gem.add_development_dependency 'rake-compiler-dock', ['~> 0.5.0']
28
+ gem.add_development_dependency 'rake-compiler', ['~> 0.9.4']
29
+
30
+ # build gem for a certain platform. see also Rakefile
31
+ fake_platform = ENV['GEM_BUILD_FAKE_PLATFORM'].to_s
32
+ gem.platform = fake_platform unless fake_platform.empty?
33
+ if /mswin|mingw/ =~ fake_platform || (/mswin|mingw/ =~ RUBY_PLATFORM && fake_platform.empty?)
34
+ # windows dependencies
35
+ gem.add_runtime_dependency("windows-pr", ["~> 1.2.5"])
36
+ end
37
+ end
@@ -0,0 +1,59 @@
1
+
2
+ describe ServerEngine::BlockingFlag do
3
+ subject { BlockingFlag.new }
4
+
5
+ it 'set and reset' do
6
+ should_not be_set
7
+ subject.set!
8
+ should be_set
9
+ subject.reset!
10
+ should_not be_set
11
+ end
12
+
13
+ it 'set! and reset! return whether it toggled the state' do
14
+ subject.reset!.should == false
15
+ subject.set!.should == true
16
+ subject.set!.should == false
17
+ subject.reset!.should == true
18
+ end
19
+
20
+ it 'wait_for_set timeout' do
21
+ start = Time.now
22
+
23
+ subject.wait_for_set(0.01)
24
+ elapsed = Time.now - start
25
+
26
+ elapsed.should >= 0.01
27
+ end
28
+
29
+ it 'wait_for_reset timeout' do
30
+ subject.set!
31
+
32
+ start = Time.now
33
+
34
+ subject.wait_for_reset(0.01)
35
+ elapsed = Time.now - start
36
+
37
+ elapsed.should >= 0.01
38
+ end
39
+
40
+ it 'wait' do
41
+ start = Time.now
42
+ elapsed = nil
43
+
44
+ started = BlockingFlag.new
45
+ t = Thread.new do
46
+ started.set!
47
+ Thread.pass
48
+ subject.wait_for_set(1)
49
+ elapsed = Time.now - start
50
+ end
51
+ started.wait_for_set
52
+
53
+ subject.set!
54
+ t.join
55
+
56
+ elapsed.should_not be_nil
57
+ elapsed.should < 0.5
58
+ end
59
+ end
@@ -0,0 +1,175 @@
1
+ require 'stringio'
2
+
3
+ describe ServerEngine::DaemonLogger do
4
+ before { FileUtils.rm_rf("tmp") }
5
+ before { FileUtils.mkdir_p("tmp") }
6
+ before { FileUtils.rm_f("tmp/se1.log") }
7
+ before { FileUtils.rm_f("tmp/se2.log") }
8
+ before { FileUtils.rm_f Dir["tmp/se3.log.**"] }
9
+ before { FileUtils.rm_f Dir["tmp/se4.log.**"] }
10
+
11
+ subject { DaemonLogger.new("tmp/se1.log", level: 'trace') }
12
+
13
+ it 'reopen' do
14
+ subject.warn "ABCDEF"
15
+ File.open('tmp/se1.log', "w") {|f| }
16
+
17
+ subject.warn "test2"
18
+ File.read('tmp/se1.log').should_not =~ /ABCDEF/
19
+
20
+ subject.reopen!
21
+ subject.warn "test3"
22
+ File.read('tmp/se1.log').should =~ /test3/
23
+ end
24
+
25
+ it 'reset path' do
26
+ subject.logdev = 'tmp/se2.log'
27
+ subject.warn "test"
28
+
29
+ File.read('tmp/se2.log').should =~ /test$/
30
+ end
31
+
32
+ it 'default level is debug' do
33
+ subject.debug 'debug'
34
+ File.read('tmp/se1.log').should =~ /debug$/
35
+ end
36
+
37
+ it 'level set by int' do
38
+ subject.level = Logger::FATAL
39
+ subject.level.should == Logger::FATAL
40
+ subject.trace?.should == false
41
+ subject.debug?.should == false
42
+ subject.info?.should == false
43
+ subject.warn?.should == false
44
+ subject.error?.should == false
45
+ subject.fatal?.should == true
46
+
47
+ subject.level = Logger::ERROR
48
+ subject.level.should == Logger::ERROR
49
+ subject.trace?.should == false
50
+ subject.debug?.should == false
51
+ subject.info?.should == false
52
+ subject.warn?.should == false
53
+ subject.error?.should == true
54
+ subject.fatal?.should == true
55
+
56
+ subject.level = Logger::WARN
57
+ subject.level.should == Logger::WARN
58
+ subject.trace?.should == false
59
+ subject.debug?.should == false
60
+ subject.info?.should == false
61
+ subject.warn?.should == true
62
+ subject.error?.should == true
63
+ subject.fatal?.should == true
64
+
65
+ subject.level = Logger::INFO
66
+ subject.level.should == Logger::INFO
67
+ subject.trace?.should == false
68
+ subject.debug?.should == false
69
+ subject.info?.should == true
70
+ subject.warn?.should == true
71
+ subject.error?.should == true
72
+ subject.fatal?.should == true
73
+
74
+ subject.level = Logger::DEBUG
75
+ subject.level.should == Logger::DEBUG
76
+ subject.trace?.should == false
77
+ subject.debug?.should == true
78
+ subject.info?.should == true
79
+ subject.warn?.should == true
80
+ subject.error?.should == true
81
+ subject.fatal?.should == true
82
+
83
+ subject.level = DaemonLogger::TRACE
84
+ subject.level.should == DaemonLogger::TRACE
85
+ subject.trace?.should == true
86
+ subject.debug?.should == true
87
+ subject.info?.should == true
88
+ subject.warn?.should == true
89
+ subject.error?.should == true
90
+ subject.fatal?.should == true
91
+ end
92
+
93
+ it 'level set by string' do
94
+ subject.level = 'fatal'
95
+ subject.level.should == Logger::FATAL
96
+
97
+ subject.level = 'error'
98
+ subject.level.should == Logger::ERROR
99
+
100
+ subject.level = 'warn'
101
+ subject.level.should == Logger::WARN
102
+
103
+ subject.level = 'info'
104
+ subject.level.should == Logger::INFO
105
+
106
+ subject.level = 'debug'
107
+ subject.level.should == Logger::DEBUG
108
+
109
+ subject.level = 'trace'
110
+ subject.level.should == DaemonLogger::TRACE
111
+ end
112
+
113
+ it 'unknown level' do
114
+ lambda { subject.level = 'unknown' }.should raise_error(ArgumentError)
115
+ end
116
+
117
+ it 'rotation' do
118
+ log = DaemonLogger.new("tmp/se3.log", level: 'trace', log_rotate_age: 3, log_rotate_size: 10000)
119
+ # 100 bytes
120
+ log.warn "test1"*20
121
+ File.exist?("tmp/se3.log").should == true
122
+ File.exist?("tmp/se3.log.0").should == false
123
+
124
+ # 10000 bytes
125
+ 100.times { log.warn "test2"*20 }
126
+ File.exist?("tmp/se3.log").should == true
127
+ File.exist?("tmp/se3.log.0").should == true
128
+ File.read("tmp/se3.log.0") =~ /test2$/
129
+
130
+ # 10000 bytes
131
+ 100.times { log.warn "test3"*20 }
132
+ File.exist?("tmp/se3.log").should == true
133
+ File.exist?("tmp/se3.log.1").should == true
134
+ File.exist?("tmp/se3.log.2").should == false
135
+
136
+ log.warn "test4"*20
137
+ File.read("tmp/se3.log").should =~ /test4$/
138
+ File.read("tmp/se3.log.0").should =~ /test3$/
139
+ end
140
+
141
+ it 'IO logger' do
142
+ io = StringIO.new
143
+ io.should_receive(:write)
144
+ io.should_not_receive(:reopen)
145
+
146
+ log = DaemonLogger.new(io)
147
+ log.debug "stdout logging test"
148
+ log.reopen!
149
+ end
150
+
151
+ it 'inter-process locking on rotation' do
152
+ pending "fork is not implemented in Windows" if ServerEngine.windows?
153
+
154
+ log = DaemonLogger.new("tmp/se4.log", level: 'trace', log_rotate_age: 3, log_rotate_size: 10)
155
+ r, w = IO.pipe
156
+ $stderr = w # To capture #warn output in DaemonLogger
157
+ pid1 = Process.fork do
158
+ 10.times do
159
+ log.info '0' * 15
160
+ end
161
+ end
162
+ pid2 = Process.fork do
163
+ 10.times do
164
+ log.info '0' * 15
165
+ end
166
+ end
167
+ Process.waitpid pid1
168
+ Process.waitpid pid2
169
+ w.close
170
+ stderr = r.read
171
+ r.close
172
+ $stderr = STDERR
173
+ stderr.should_not =~ /(log shifting failed|log writing failed|log rotation inter-process lock failed)/
174
+ end
175
+ end