serverengine 2.0.0pre1-x64-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,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