thin-preforker 0.0.2 → 0.0.3

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.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- thin-preforker (0.0.2)
4
+ thin-preforker (0.0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -0,0 +1,31 @@
1
+ module Thin
2
+ module Preforker
3
+ class Callbacks
4
+ attr_accessor :before_fork_callbacks, :after_fork_callbacks
5
+
6
+ def initialize filename = nil
7
+ @before_fork_callbacks = []
8
+ @after_fork_callbacks = []
9
+
10
+ instance_eval(open(filename).read, filename) if filename
11
+ end
12
+
13
+ def run_before_fork_callbacks *args
14
+ @before_fork_callbacks.each { |callback| callback.call(*args) }
15
+ end
16
+
17
+ def run_after_fork_callbacks *args
18
+ @after_fork_callbacks.each { |callback| callback.call(*args) }
19
+ end
20
+
21
+ private
22
+ def before_fork &block
23
+ @before_fork_callbacks << block
24
+ end
25
+
26
+ def after_fork &block
27
+ @after_fork_callbacks << block
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,60 +1,59 @@
1
1
  module Thin
2
2
  module Preforker
3
3
  class Controller < Thin::Controllers::Cluster
4
- def initialize *args
5
- super
4
+ def initialize options
5
+ @options = options
6
6
 
7
- GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
7
+ if @options[:socket]
8
+ @options.delete(:address)
9
+ @options.delete(:port)
10
+ end
11
+
12
+ GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)
8
13
  end
9
14
 
10
15
  # Start the servers
11
16
  def start
17
+ daemonize_prefoker if @options[:daemonize]
18
+
12
19
  with_each_server do |n|
13
20
  start_server n, app
14
- sleep 0.1 # Let the OS breath
15
21
  end
22
+
23
+ Process.waitall
16
24
  end
17
25
 
18
26
  # Start a single server
19
27
  def start_server(number, app)
20
28
  log "Starting server on #{server_id(number)} ... "
21
29
 
22
- # Save the log files
23
- logs = logs_to_reopen
30
+ server = Thin::Server.new(server_options_for(number)[:socket] || server_options_for(number)[:address],
31
+ server_options_for(number)[:port],
32
+ server_options_for(number))
33
+
34
+ # Set options
35
+ server.pid_file = server_options_for(number)[:pid]
36
+ server.log_file = server_options_for(number)[:log]
37
+ server.timeout = server_options_for(number)[:timeout]
38
+ server.maximum_connections = server_options_for(number)[:max_conns]
39
+ server.maximum_persistent_connections = server_options_for(number)[:max_persistent_conns]
40
+ server.threaded = server_options_for(number)[:threaded]
41
+ server.no_epoll = server_options_for(number)[:no_epoll] if server.backend.respond_to?(:no_epoll=)
42
+
43
+ # ssl support
44
+ if server_options_for(number)[:ssl]
45
+ server.ssl = true
46
+ server.ssl_options = { :private_key_file => server_options_for(number)[:ssl_key_file], :cert_chain_file => server_options_for(number)[:ssl_cert_file], :verify_peer => server_options_for(number)[:ssl_verify] }
47
+ end
24
48
 
25
- # Force GC to collect before forking
26
- GC.start
27
-
28
- fork do
29
- $stdout.reopen($stdout)
30
- $stderr.reopen($stderr)
31
- $stdin.reopen("/dev/null")
49
+ before_fork_for(server, number)
50
+
51
+ Process.fork do
52
+ after_fork_for(server, number)
32
53
 
33
54
  # Constantize backend class
34
55
  server_options_for(number)[:backend] = eval(server_options_for(number)[:backend], TOPLEVEL_BINDING) if server_options_for(number)[:backend]
35
56
 
36
- server = Thin::Server.new(server_options_for(number)[:socket] || server_options_for(number)[:address], # Server detects kind of socket
37
- server_options_for(number)[:port], # Port ignored on UNIX socket
38
- server_options_for(number))
39
-
40
- # Set options
41
- server.pid_file = server_options_for(number)[:pid]
42
- server.log_file = server_options_for(number)[:log]
43
- server.timeout = server_options_for(number)[:timeout]
44
- server.maximum_connections = server_options_for(number)[:max_conns]
45
- server.maximum_persistent_connections = server_options_for(number)[:max_persistent_conns]
46
- server.threaded = server_options_for(number)[:threaded]
47
- server.no_epoll = server_options_for(number)[:no_epoll] if server.backend.respond_to?(:no_epoll=)
48
-
49
- # ssl support
50
- if server_options_for(number)[:ssl]
51
- server.ssl = true
52
- server.ssl_options = { :private_key_file => server_options_for(number)[:ssl_key_file], :cert_chain_file => server_options_for(number)[:ssl_cert_file], :verify_peer => server_options_for(number)[:ssl_verify] }
53
- end
54
-
55
- # Detach the process, after this line the current process returns
56
- server.daemonize if server_options_for(number)[:daemonize]
57
-
58
57
  # +config+ must be called before changing privileges since it might require superuser power.
59
58
  server.config
60
59
 
@@ -68,11 +67,11 @@ module Thin
68
67
 
69
68
  # If a stats URL is specified, wrap in Stats adapter
70
69
  server.app = Thin::Stats::Adapter.new(server.app, server_options_for(number)[:stats]) if server_options_for(number)[:stats]
71
-
72
- reopen_logs logs
73
70
 
74
71
  server.start
75
72
  end
73
+
74
+ wait_for_file :creation, server_options_for(number)[:pid]
76
75
  end
77
76
 
78
77
  # Stop the servers
@@ -97,13 +96,15 @@ module Thin
97
96
 
98
97
  # Stop and start the servers.
99
98
  def restart
100
- app
99
+ daemonize_prefoker if @options[:daemonize]
101
100
 
102
101
  with_each_server do |n|
103
102
  stop_server n
104
103
  start_server n, app
105
104
  sleep 0.1 # Let the OS breath
106
105
  end
106
+
107
+ Process.waitall
107
108
  end
108
109
 
109
110
  private
@@ -112,6 +113,10 @@ module Thin
112
113
  # a Rack adapter from it. Or else we guess which adapter to use and load it.
113
114
  @app ||= @options[:rackup] ? load_rackup_config : load_adapter
114
115
  end
116
+
117
+ def callbacks
118
+ @callbacks ||= Callbacks.new @options[:callbacks]
119
+ end
115
120
 
116
121
  def server_options_for(number)
117
122
  @server_options ||= {}
@@ -119,7 +124,7 @@ module Thin
119
124
 
120
125
  # Sets the server options for this server
121
126
  @server_options[number] = @options.reject { |option, value| CLUSTER_OPTIONS.include?(option) }
122
- @server_options[number].merge!(:pid => pid_file_for(number), :log => log_file_for(number))
127
+ @server_options[number].merge!(:pid => pid_file_for(number), :log => log_file_for(number), :daemonize => nil)
123
128
  if socket
124
129
  @server_options[number].merge!(:socket => socket_for(number))
125
130
  elsif swiftiply?
@@ -135,7 +140,7 @@ module Thin
135
140
  require 'fcntl'
136
141
 
137
142
  logs = []
138
- ObjectSpace.each_object(File) { |fp| logs << fp.path if fp.fcntl(Fcntl::F_GETFL) == File::APPEND | File::WRONLY rescue false }
143
+
139
144
 
140
145
  logs
141
146
  end
@@ -143,6 +148,67 @@ module Thin
143
148
  def reopen_logs logs
144
149
  ObjectSpace.each_object(File) { |fp| fp.reopen(fp.path, "a") if logs.include? fp.path }
145
150
  end
151
+
152
+ def before_fork_for(server, number)
153
+ raise ArgumentError, "You must specify a pid file to fork" unless server_options_for(number)[:pid]
154
+ raise ArgumentError, "You must specify a log file to fork" unless server_options_for(number)[:log]
155
+
156
+ server.send(:remove_stale_pid_file)
157
+
158
+ @pwd = Dir.pwd # Current directory is changed during fork, so store it
159
+
160
+ FileUtils.mkdir_p File.dirname(server_options_for(number)[:pid])
161
+ FileUtils.mkdir_p File.dirname(server_options_for(number)[:log])
162
+
163
+ @logs = []
164
+ ObjectSpace.each_object(File) { |fp| @logs << fp.path if fp.fcntl(Fcntl::F_GETFL) == File::APPEND | File::WRONLY rescue false }
165
+
166
+ callbacks.run_before_fork_callbacks server, number
167
+ end
168
+
169
+ def after_fork_for(server, number)
170
+ log_fp = open(server_options_for(number)[:log], "a")
171
+ log_fp.sync = true
172
+ $stdout.reopen(log_fp)
173
+ $stderr.reopen(log_fp)
174
+ $stdin.reopen("/dev/null")
175
+
176
+ Dir.chdir(@pwd)
177
+
178
+ server.send(:write_pid_file)
179
+ server.send(:at_exit) do
180
+ log ">> Exiting!"
181
+ server.send(:remove_pid_file)
182
+ end
183
+
184
+ Signal.trap("INT") { server.stop! }
185
+ Signal.trap("TERM") { server.stop }
186
+ Signal.trap("QUIT") { server.stop } unless Thin.win?
187
+
188
+ ObjectSpace.each_object(File) { |fp| fp.reopen(fp.path, "a") if @logs.include? fp.path }
189
+
190
+ callbacks.run_after_fork_callbacks server, number
191
+ end
192
+
193
+ def daemonize_prefoker
194
+ raise ArgumentError, "You must specify a preforker pid file to daemonize" unless @options[:preforker_pid]
195
+ raise ArgumentError, "You must specify a preforker log file to daemonize" unless @options[:preforker_log]
196
+
197
+ pwd = Dir.pwd # Current directory is changed during fork, so store it
198
+
199
+ FileUtils.mkdir_p File.dirname(@options[:preforker_pid])
200
+ FileUtils.mkdir_p File.dirname(@options[:preforker_log])
201
+
202
+ Daemonize.daemonize
203
+
204
+ Dir.chdir(pwd)
205
+
206
+ Daemonize.redirect_io @options[:preforker_log]
207
+
208
+ log ">> Writing PID to #{@options[:preforker_pid]}"
209
+ open(@options[:preforker_pid],"w") { |f| f.write(Process.pid) }
210
+ File.chmod(0644, @options[:preforker_pid])
211
+ end
146
212
  end
147
213
  end
148
214
  end
@@ -2,9 +2,51 @@ class Thin::Preforker::Runner < Thin::Runner
2
2
  # Error raised that will abort the process and print not backtrace.
3
3
  class RunnerError < RuntimeError; end
4
4
 
5
+ def initialize argv
6
+ @argv = argv
7
+
8
+ # Default options values
9
+ @options = {
10
+ :chdir => Dir.pwd,
11
+ :environment => ENV['RACK_ENV'] || 'development',
12
+ :address => '0.0.0.0',
13
+ :port => Thin::Server::DEFAULT_PORT,
14
+ :timeout => Thin::Server::DEFAULT_TIMEOUT,
15
+ :log => 'log/thin.log',
16
+ :pid => 'tmp/pids/thin.pid',
17
+ :max_conns => Thin::Server::DEFAULT_MAXIMUM_CONNECTIONS,
18
+ :max_persistent_conns => Thin::Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS,
19
+ :require => [],
20
+ :wait => Thin::Preforker::Controller::DEFAULT_WAIT_TIME,
21
+ :daemonize => true,
22
+ :preforker_callbacks => nil,
23
+ :preforker_log => "log/thin-preforker.log",
24
+ :preforker_pid => "tmp/pids/thin-preforker.pid"
25
+ }
26
+
27
+ parse!
28
+ end
29
+
30
+ def parser
31
+ super
32
+
33
+ @parser.banner = "Usage: #{@parser.program_name} [options] #{self.class.commands.join('|')}"
34
+
35
+ @parser.tap do |opts|
36
+ # TODO change to #on instead of #on_tail after thin is fixed.
37
+ opts.on_tail ""
38
+ opts.on_tail "Preforker options:"
39
+
40
+ opts.on_tail("--callbacks FILE", "Path to preforker callbacks file") { |file| @options[:callbacks] = file }
41
+ opts.on_tail("--preforker-log FILE", "File to redirect preforker output " + "(default: #{@options[:preforker_log]})") { |file| @options[:preforker_log] = file }
42
+ opts.on_tail("--preforker-pid FILE", "File to store preforker PID " + "(default: #{@options[:preforker_pid]})") { |file| @options[:preforker_pid] = file }
43
+ end
44
+ end
45
+
46
+
5
47
  def run_command
6
48
  load_options_from_config_file! unless CONFIGLESS_COMMANDS.include?(@command)
7
-
49
+
8
50
  # PROGRAM_NAME is relative to the current directory, so make sure
9
51
  # we store and expand it before changing directory.
10
52
  Thin::Command.script = File.expand_path($PROGRAM_NAME)
@@ -1,14 +1,16 @@
1
- module Thin
2
- module Preforker
3
- module VERSION #:nodoc:
4
- MAJOR = 0
5
- MINOR = 0
6
- TINY = 2
1
+ unless defined?(Thin::Preforker::VERSION)
2
+ module Thin
3
+ module Preforker
4
+ module VERSION #:nodoc:
5
+ MAJOR = 0
6
+ MINOR = 0
7
+ TINY = 3
7
8
 
8
- STRING = [MAJOR, MINOR, TINY].join('.')
9
- end
9
+ STRING = [MAJOR, MINOR, TINY].join('.')
10
+ end
10
11
 
11
- NAME = 'thin-preforker'.freeze
12
- SERVER = "#{NAME} #{VERSION::STRING}".freeze
12
+ NAME = 'thin-preforker'.freeze
13
+ SERVER = "#{NAME} #{VERSION::STRING}".freeze
14
+ end
13
15
  end
14
16
  end
@@ -2,9 +2,9 @@ require "thin"
2
2
 
3
3
  module Thin
4
4
  module Preforker
5
+ autoload :Callbacks, "thin/preforker/callbacks"
5
6
  autoload :Controller, "thin/preforker/controller"
6
7
  autoload :Runner, "thin/preforker/runner"
8
+ autoload :Version, "thin/preforker/version"
7
9
  end
8
- end
9
-
10
- require "thin/preforker/version"
10
+ end
@@ -0,0 +1,7 @@
1
+ before_fork do |server, number|
2
+ puts "foo"
3
+ end
4
+
5
+ after_fork do |server, number|
6
+ puts "boo"
7
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thin-preforker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-10 00:00:00.000000000 Z
12
+ date: 2012-07-11 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A thin controller and spawner with prefork
15
15
  email: rafael.gaspar@me.com
@@ -26,9 +26,11 @@ files:
26
26
  - Rakefile
27
27
  - bin/thin-preforker
28
28
  - lib/thin/preforker.rb
29
+ - lib/thin/preforker/callbacks.rb
29
30
  - lib/thin/preforker/controller.rb
30
31
  - lib/thin/preforker/runner.rb
31
32
  - lib/thin/preforker/version.rb
33
+ - sample/callbacks.rb
32
34
  - thin-preforker.gemspec
33
35
  homepage: http://github.com/rafaelgaspar/thin-preforker/
34
36
  licenses:
@@ -45,7 +47,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
47
  version: '0'
46
48
  segments:
47
49
  - 0
48
- hash: -2667861152408548097
50
+ hash: 1366569596521026037
49
51
  required_rubygems_version: !ruby/object:Gem::Requirement
50
52
  none: false
51
53
  requirements:
@@ -54,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
54
56
  version: '0'
55
57
  segments:
56
58
  - 0
57
- hash: -2667861152408548097
59
+ hash: 1366569596521026037
58
60
  requirements: []
59
61
  rubyforge_project:
60
62
  rubygems_version: 1.8.10