rsence 2.0.0.0.pre → 2.0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
data/lib/daemon/daemon.rb CHANGED
@@ -8,9 +8,6 @@
8
8
  ##
9
9
  #++
10
10
 
11
- # Use the default configuration:
12
- require 'conf/default'
13
-
14
11
  # Use rubygems to load rack
15
12
  require 'rubygems'
16
13
  require 'rack'
@@ -47,247 +44,336 @@ require 'transporter/transporter'
47
44
  require 'http/broker'
48
45
 
49
46
 
50
- module RSence
51
-
52
- # adapted from:
53
- # http://snippets.dzone.com/posts/show/2265
47
+ module ::RSence
54
48
 
55
- require 'fileutils'
49
+ @@launch_pid = Process.pid
50
+ def self.launch_pid
51
+ return @@launch_pid
52
+ end
56
53
 
54
+ # The Controller module handles the damonizing
55
+ # operations of the process referred to as +daemon+
57
56
  module Daemon
58
57
 
59
- class Base
60
- def self.pid_fn
61
- RSence.config[:daemon][:pid_fn]
62
- end
63
- def self.log_fn
64
- RSence.config[:daemon][:log_fn]
65
- end
66
- def self.daemonize
67
- Controller.daemonize(self)
58
+ # Writes the process id to disk, the pid_fn method of the daemon contains
59
+ # the name of the pid file.
60
+ def self.write_pid( daemon, pid )
61
+ File.open( daemon.pid_fn, 'w' ) do |pidfile|
62
+ pidfile.write( pid )
68
63
  end
69
64
  end
70
65
 
71
- module PidFile
72
- def self.store(daemon, pid)
73
- dir_path_pid = File.split( daemon.pid_fn )[0]
74
- FileUtils.mkdir_p( dir_path_pid ) unless File.exists? dir_path_pid
75
- File.open(daemon.pid_fn, 'w') {|f| f << pid}
76
- end
77
- def self.recall(daemon)
78
- IO.read(daemon.pid_fn).to_i rescue nil
79
- end
66
+ # Reads the process id from disk, the pid_fn method of the daemon contains
67
+ # the name of the pid file. Returns nil on errors.
68
+ def self.read_pid( daemon )
69
+ File.read( daemon.pid_fn ).to_i rescue nil
80
70
  end
81
71
 
82
- module Controller
83
-
84
- def self.print_status(daemon)
85
- is_running = self.status(daemon)
86
- puts "Riassence Framework is #{'not ' unless is_running}running"
72
+ def self.responds?( daemon )
73
+ wait_signal_response( daemon, 'USR2' )
74
+ end
75
+
76
+ # Reads the pid file and calls the process.
77
+ # Returns true if the process responds, false otherwise (no process)
78
+ def self.status( daemon )
79
+ pid = read_pid( daemon )
80
+ return nil if not pid
81
+ return responds?( daemon )
82
+ end
83
+
84
+ # Redirects standard input and errors to the log files
85
+ def self.start_logging( daemon )
86
+ outpath = "#{daemon.log_fn}.stdout"
87
+ errpath = "#{daemon.log_fn}.stderr"
88
+ STDOUT.reopen( outpath, (File.exist?( outpath ) ? 'a' : 'w') )
89
+ STDOUT.sync = true
90
+ STDERR.reopen( errpath, (File.exist?( errpath ) ? 'a' : 'w') )
91
+ STDERR.sync = true
92
+ end
93
+
94
+ def self.write_signal_response( daemon, signal )
95
+ sig_fn = daemon.pid_fn+'.response.'+signal
96
+ File.open(sig_fn,'w') do |file|
97
+ file.write(Process.pid.to_s)
87
98
  end
88
-
89
- ## Status is not entirely reliable
90
- def self.status(daemon)
91
- if File.file?(daemon.pid_fn)
92
- begin
93
- pid = File.read(daemon.pid_fn).to_i
94
- pid && Process.kill('USR2',pid)
95
- return true
96
- rescue Errno::ESRCH => e
97
- return false
98
- end
99
- end
100
- return false
99
+ end
100
+
101
+ def self.delete_signal_response( daemon, signal )
102
+ sig_fn = daemon.pid_fn+'.response.'+signal
103
+ if File.file?( sig_fn )
104
+ File.delete( sig_fn )
101
105
  end
102
-
103
- def self.open_log( outpath, errpath )
104
- dir_path_out = File.split( outpath )[0]
105
- FileUtils.mkdir_p( dir_path_out ) unless File.exists? dir_path_out
106
- dir_path_err = File.split( outpath )[0]
107
- FileUtils.mkdir_p( dir_path_err ) unless File.exists? dir_path_err
108
- if File.exist?( outpath )
109
- STDOUT.reopen( outpath, "a" )
110
- else
111
- STDOUT.reopen( outpath, "w" )
106
+ end
107
+
108
+ def self.wait_signal_response( daemon, signal, timeout = 10,
109
+ debug_pre = '<', debug_suf = '>', sleep_secs = 0.2 )
110
+ begin
111
+ if RSence.args[:verbose]
112
+ print debug_pre
113
+ STDOUT.flush
112
114
  end
113
- if File.exist?( errpath )
114
- STDERR.reopen( errpath, "a" )
115
- else
116
- STDERR.reopen( errpath, "w" )
115
+ pid = read_pid( daemon )
116
+ sig_fn = daemon.pid_fn+'.response.'+signal
117
+ File.delete( sig_fn ) if File.file?( sig_fn )
118
+ status = Process.kill( signal, pid )
119
+ time_out = Time.now + timeout
120
+ until time_out > Time.now or File.file?( sig_fn )
121
+ if RSence.args[:verbose]
122
+ print "."
123
+ STDOUT.flush
124
+ end
125
+ sleep sleep_secs
117
126
  end
118
- STDOUT.sync = true
119
- STDERR.sync = true
120
- end
121
-
122
- def self.log_io(daemon)
123
- outpath = "#{daemon.log_fn}.stdout"
124
- errpath = "#{daemon.log_fn}.stderr"
125
- if $DEBUG_MODE
126
- Thread.new do
127
- puts "Waiting 2 seconds before switching output to log file #{outpath} and .../#{File.split(errpath)[1]}"
128
- sleep 2
129
- puts "Switching to stdin and stdout to log mode. Follow the log file to see further server output."
130
- self.open_log( outpath, errpath )
127
+ sleep sleep_secs
128
+ if File.file?( sig_fn )
129
+ sig_pid = File.read( sig_fn ).to_i
130
+ if sig_pid != pid
131
+ puts "Warning, signal PID mismatch. Expected #{pid}, got #{sig_pid}"
131
132
  end
133
+ File.delete( sig_fn )
132
134
  else
133
- self.open_log( outpath, errpath )
135
+ puts "Warning, signal response file disappeared! Expected #{sig_fn}"
134
136
  end
137
+ puts debug_suf if RSence.args[:verbose]
138
+ return true
139
+ rescue Errno::ESRCH
140
+ return false
135
141
  end
142
+ end
143
+
144
+ # Traps common kill signals
145
+ def self.trap_signals( daemon )
136
146
 
137
- def self.daemonize(daemon)
138
- cmd = ARGV[0]
139
- if ['i386-mingw32','x86-mingw32'].include? RUBY_PLATFORM
140
- if cmd == 'start'
141
- warn "Running on Microsoft Windows: unable to background. Using the run command instead of start"
142
- cmd = 'run'
143
- elsif cmd != 'run'
144
- puts "Invalid command: #{cmd.inspect}. Windows supports only run and help."
145
- exit
146
- end
147
- elsif not cmd or not %w[status start stop restart save run].include?( cmd )
148
- puts "Invalid command. Please specify one of the following: run, start, stop, restart, status, save or help."
147
+ # Triggered with 'kill -USR1 `cat rsence.pid`'
148
+ Signal.trap( 'USR1' ) do
149
+ daemon.usr1
150
+ write_signal_response( daemon, 'USR1' )
151
+ end
152
+ Signal.trap('USR2') do
153
+ daemon.usr2
154
+ write_signal_response( daemon, 'USR2' )
155
+ end
156
+ ['INT', 'TERM', 'KILL'].each do | signal |
157
+ Signal.trap( signal ) do
158
+ puts "RSence killed with signal #{signal.inspect}" if RSence.args[:verbose]
159
+ daemon.usr1
160
+ daemon.stop
161
+ # delete_signal_response( daemon, 'USR1' )
162
+ # delete_signal_response( daemon, 'USR2' )
163
+ File.delete( daemon.pid_fn ) if File.file?( daemon.pid_fn )
164
+ write_signal_response( daemon, signal )
165
+ puts "Shutdown complete."
149
166
  exit
150
167
  end
151
- case cmd
152
- when 'run'
153
- puts "Starting as a foreground process."
154
- puts "Enter CTRL-C to stop."
155
- self.start_fg(daemon)
156
- when 'status'
157
- self.print_status(daemon)
158
- when 'start'
159
- self.start(daemon)
160
- when 'stop'
161
- self.stop(daemon)
162
- when 'restart'
163
- self.stop(daemon,true)
164
- self.start(daemon)
165
- when 'save'
166
- self.save(daemon)
167
- end
168
168
  end
169
- def self.start_fg(daemon)
170
- ['INT', 'TERM', 'KILL'].each do |signal|
171
- Signal.trap(signal) do
172
- puts "Got signal #{signal.inspect}"
173
- daemon.stop
174
- exit
169
+ # Signal.trap('HUP') do
170
+ # daemon.restart
171
+ # end
172
+ end
173
+
174
+ def self.handle_pid( daemon )
175
+ if RSence.pid_support?
176
+ is_running = status( daemon )
177
+ if is_running
178
+ puts "RSence is already running."
179
+ puts "Stop the existing process first: see 'rsence help stop'"
180
+ if RSence.launch_pid != Process.pid
181
+ Process.kill( 'INT', RSence.launch_pid )
175
182
  end
183
+ exit
184
+ elsif not is_running and File.file?( daemon.pid_fn )
185
+ puts "Stale pid file, removing.."
186
+ File.delete( daemon.pid_fn )
176
187
  end
188
+ trap_signals( daemon )
189
+ pid = Process.pid
190
+ write_pid( daemon, pid )
191
+ return pid
192
+ else
193
+ return false
194
+ end
195
+ end
196
+
197
+ def self.run( daemon )
198
+ handle_pid( daemon )
199
+ daemon.run
200
+ exit
201
+ end
202
+
203
+ def self.start( daemon )
204
+ fork do
205
+ exit if fork
206
+ handle_pid( daemon )
177
207
  daemon.start
208
+ end
209
+ Signal.trap('INT') do
210
+ puts "RSence startup failed. Please inspect the log and/or run in debug mode."
178
211
  exit
179
212
  end
180
- def self.start(daemon)
181
- is_running = self.status(daemon)
182
- if is_running
183
- puts "Riassence Framework is already running. Try restart."
184
- exit
185
- elsif not is_running and File.file?(daemon.pid_fn)
186
- puts "Stale pid file, removing.."
187
- FileUtils.rm(daemon.pid_fn)
188
- end
189
- fork do
190
- Process.setsid
191
- exit if fork
192
- PidFile.store(daemon, Process.pid)
193
- Signal.trap('USR1') do
194
- if @transporter != nil
195
- @transporter.plugins.shutdown if @transporter.plugins
196
- @transporter.sessions.shutdown if @transporter.session
197
- end
198
- end
199
- Signal.trap('USR2') do
200
- puts "Alive."
201
- end
202
- ['INT', 'TERM', 'KILL'].each do |signal|
203
- Signal.trap(signal) do
204
- puts "Got signal #{signal.inspect}"
205
- daemon.stop
206
- exit
207
- end
208
- end
209
- Signal.trap('HUP') {
210
- daemon.restart
211
- }
212
- STDIN.reopen( "/dev/null" )
213
- daemon.start
214
- end
215
-
216
- timeout = Time.now + 10
217
- sleep 0.1 until self.status(daemon) or timeout < Time.now
218
-
219
- if timeout < Time.now
220
- puts "Riassence Framework did not start, please check the logfile."
221
- else
222
- puts "Riassence Framework is running now."
223
- end
224
-
225
- sleep 2.5 if $DEBUG_MODE
226
-
227
- #Process.kill("USR2", File.read(daemon.pid_fn).to_i)
213
+ Signal.trap('TERM') do
214
+ puts "RSence is online at http://#{daemon.addr}:#{daemon.port}/"
215
+ exit
228
216
  end
229
- def self.save(daemon,is_restart=false)
230
- if !File.file?(daemon.pid_fn)
231
- puts "Pid file not found. Is Riassence Framework started?"
232
- return if is_restart
233
- exit
234
- end
235
- pid = File.read(daemon.pid_fn).to_i
236
- begin
237
- pid && Process.kill("USR1", pid)
217
+ sleep 1 while true
218
+ end
219
+
220
+ # Sends the USR1 signal to the process, which in turn
221
+ # calls the save method of the daemon.
222
+ def self.save( daemon )
223
+ status_ = status( daemon )
224
+ if status_
225
+ if wait_signal_response( daemon, 'USR1', 10, 'saving.', 'saved', 0.3 )
238
226
  puts "Session data saved."
239
- rescue
240
- puts "Error, no such pid (#{pid}) running"
227
+ else
228
+ puts "Warning: saving timed out! Session data not saved."
241
229
  end
230
+ elsif status_ == false
231
+ puts "Warning, no such process (#{pid}) running: unable to save."
232
+ elsif status_ == nil
233
+ puts "No pid file: unable to save."
234
+ else
235
+ throw "Unexpected process status: #{status_.inspect}"
242
236
  end
243
- def self.stop(daemon,is_restart=false)
244
- self.save(daemon,is_restart)
245
- if !File.file?(daemon.pid_fn)
246
- puts "Pid file not found. Is Riassence Framework started?"
247
- return if is_restart
248
- exit
249
- end
250
- pid = PidFile.recall(daemon)
251
- begin
252
- pid && Process.kill("TERM", pid)
253
- puts "Riassence Framework is stopped now."
254
- rescue
255
- puts "Error, no such pid (#{pid}) running"
237
+ end
238
+
239
+ # Sends the TERM signal to the process, which in turn
240
+ # calls the stop method of the daemon
241
+ def self.stop( daemon )#,is_restart=false)
242
+ status_ = status( daemon )
243
+ if status_
244
+ if wait_signal_response( daemon, 'TERM', 10, 'killing.', 'killed', 0.3 )
245
+ puts "RSence is terminated now."
246
+ else
247
+ puts "Warning: termination timed out!"
248
+ puts "RSence might still be running, please ensure manually."
256
249
  end
257
- FileUtils.rm(daemon.pid_fn)
250
+ elsif status_ == false
251
+ puts "Warning, no such process (#{pid}) running."
252
+ elsif status_ == nil
253
+ puts "Warning, no pid file (process not running)."
254
+ else
255
+ throw "Unexpected process status: #{status_.inspect}"
258
256
  end
259
257
  end
258
+
259
+ # Main entry point called from the daemon process passing +self+ as +daemon+.
260
+ def self.daemonize( daemon )
261
+
262
+ # Uses the command-line tool command to decide what to do.
263
+ case RSence.cmd
264
+ when :run
265
+ run( daemon )
266
+ when :start
267
+ start( daemon )
268
+ when :stop
269
+ stop( daemon )
270
+ when :restart
271
+ stop( daemon )
272
+ start( daemon )
273
+ when :save
274
+ save( daemon )
275
+ end
276
+ end
277
+
260
278
  end
261
279
 
262
- class HTTPDaemon < Daemon::Base
263
- def self.start
280
+ # Simple process control, constructed here and called from Daemon::Controller
281
+ class HTTPDaemon
282
+
283
+ def run
284
+ puts "Starting as a foreground process." if RSence.args[:verbose]
285
+ puts "Press CTRL-C to terminate."
286
+
264
287
  @transporter = Transporter.new
265
288
 
266
- unless ARGV.include?('--log-fg')
267
- Daemon::Controller.log_io(self)
289
+ conf = RSence.config[:http_server]
290
+
291
+ unless RSence.args[:log_fg]
292
+ Daemon.start_logging( self )
268
293
  end
269
294
 
270
295
  # This is the main http handler instance:
271
296
  @broker = Broker.start(
272
297
  @transporter,
273
- RSence.config[:http_server][:rack_handler],
274
- RSence.config[:http_server][:bind_address],
275
- RSence.config[:http_server][:port]
298
+ conf[:rack_handler],
299
+ conf[:bind_address],
300
+ conf[:port]
276
301
  )
277
302
 
278
- yield @broker if block_given?
279
-
280
303
  end
281
- def self.restart
282
- self.stop
283
- @broker = nil
284
- self.start
304
+
305
+ # Returns the pid file path.
306
+ def pid_fn
307
+ RSence.config[:daemon][:pid_fn]
308
+ end
309
+
310
+ # Returns the log path.
311
+ def log_fn
312
+ RSence.config[:daemon][:log_fn]
313
+ end
314
+
315
+ def addr
316
+ RSence.config[:http_server][:bind_address]
317
+ end
318
+
319
+ def port
320
+ RSence.config[:http_server][:port]
285
321
  end
286
- def self.stop
322
+
323
+ # Called by Controller#start, contains RSence-specific operations
324
+ def start
325
+
326
+ @transporter = Transporter.new
327
+
328
+ conf = RSence.config[:http_server]
329
+
330
+ unless RSence.args[:log_fg]
331
+ Daemon.start_logging( self )
332
+ STDIN.reopen( "/dev/null" )
333
+ end
334
+
335
+ Process.setsid
336
+
337
+ # This is the main http handler instance:
338
+ @broker = Broker.start(
339
+ @transporter,
340
+ conf[:rack_handler],
341
+ conf[:bind_address],
342
+ conf[:port]
343
+ )
344
+ yield @broker
345
+
346
+ end
347
+
348
+ # Called by Controller#stop, contains RSence-specific operations
349
+ def stop
287
350
  @transporter.plugins.shutdown
288
351
  @transporter.sessions.shutdown
289
352
  end
353
+
354
+ # Called on USR1 signals (save data)
355
+ def usr1
356
+ save
357
+ end
358
+
359
+ # Save state
360
+ def save
361
+ puts "Saving."
362
+ @transporter.plugins.delegate(:flush)
363
+ @transporter.sessions.store_sessions
364
+ puts "Saved."
365
+ end
366
+
367
+ # Called on USR2 signals ("Alive?")
368
+ def usr2
369
+ puts "Alive."
370
+ end
371
+
372
+ # Main entry point, daemonizes itself using Controller.
373
+ def daemonize!
374
+ Daemon.daemonize( self )
375
+ end
376
+
290
377
  end
291
378
 
292
379
  end
293
-
@@ -81,10 +81,9 @@ class PluginManager
81
81
  @sessions = transporter.sessions
82
82
  end
83
83
  @plugin_paths = plugin_paths
84
- puts "Loading plugins..."
84
+ puts "Loading plugins..." if RSence.args[:verbose]
85
85
  scan_plugins
86
- puts "Plugins loaded."
87
- puts "Riassence Framework is online."
86
+ puts "Plugins loaded." if RSence.args[:verbose]
88
87
  if autoreload
89
88
  @thr = Thread.new do
90
89
  Thread.pass
@@ -123,7 +122,7 @@ class PluginManager
123
122
  if @registry.has_key?( bundle_name.to_sym )
124
123
  puts "Disabling bundle #{bundle_name}..."
125
124
  unload_bundle( bundle_name.to_sym )
126
- if ARGV.include?('-say')
125
+ if RSence.args[:say]
127
126
  Thread.new do
128
127
  Thread.pass
129
128
  system(%{say "Unloaded #{bundle_name.to_s}."})
@@ -135,7 +134,7 @@ class PluginManager
135
134
  puts "Loading bundle #{bundle_name}..."
136
135
  load_bundle( bundle_path, bundle_name.to_sym, bundle_name+'.rb' )
137
136
  call( bundle_name.to_sym, :open )
138
- if ARGV.include?('-say')
137
+ if RSence.args[:say]
139
138
  Thread.new do
140
139
  Thread.pass
141
140
  system(%{say "Loaded #{bundle_name.to_s}."})
@@ -149,7 +148,7 @@ class PluginManager
149
148
  unload_bundle( bundle_name.to_sym )
150
149
  load_bundle( bundle_path, bundle_name.to_sym, bundle_name+'.rb' )
151
150
  call( bundle_name.to_sym, :open )
152
- if ARGV.include?('-say')
151
+ if RSence.args[:say]
153
152
  Thread.new do
154
153
  Thread.pass
155
154
  system(%{say "Reloaded #{bundle_name.to_s}."})
@@ -34,11 +34,9 @@ class Transporter
34
34
  @valuemanager = ValueManager.new
35
35
  @sessions = SessionManager.new( self )
36
36
  @plugins = PluginManager.new( ::RSence.config[:plugin_paths], self, $DEBUG_MODE )
37
-
38
- # Used by:
39
- # plugins/main/main.rb
40
- $SESSION = @sessions
41
-
37
+ if RSence.launch_pid != Process.pid
38
+ Process.kill( 'TERM', RSence.launch_pid )
39
+ end
42
40
  end
43
41
 
44
42
  def servlet( request_type, request, response )
@@ -47,7 +45,7 @@ class Transporter
47
45
  # if $DEBUG_MODE and uri == $config[:index_html][:respond_address] and request_type == :get
48
46
  # unless ARGV.include?('-no-rescan') or ARGV.include?('--no-rescan')
49
47
  # puts "Reloading plugins."
50
- # if ARGV.include?('-say')
48
+ # if RSence.args[:say]
51
49
  # Thread.new do
52
50
  # Thread.pass
53
51
  # system('say "Reloading plugins."')
@@ -55,7 +53,7 @@ class Transporter
55
53
  # end
56
54
  # @plugins.rescan
57
55
  # puts "Plugins reloaded."
58
- # if ARGV.include?('-say')
56
+ # if RSence.args[:say]
59
57
  # Thread.new do
60
58
  # Thread.pass
61
59
  # system('say "Plugins reloaded."')
@@ -63,14 +63,17 @@ class ClientPkg < Servlet
63
63
  @log_file = nil
64
64
  end
65
65
  def log( str )
66
- puts str
67
- return
68
- if @last_time < Time.now - 30
69
- @last_time = Time.now
70
- @log_file.write( %{--- #{@last_time.strftime("%Y-%m-%d %H:%M:%S")} ---\n} )
66
+ if ::RSence.args[:verbose]
67
+ puts str
68
+ return
69
+ else
70
+ if @last_time < Time.now - 30
71
+ @last_time = Time.now
72
+ @log_file.write( %{--- #{@last_time.strftime("%Y-%m-%d %H:%M:%S")} ---\n} )
73
+ end
74
+ @log_file.write( "#{str}\n" )
75
+ @log_file.flush
71
76
  end
72
- @log_file.write( "#{str}\n" )
73
- @log_file.flush
74
77
  end
75
78
  def open
76
79
  return if @log_file
@@ -104,7 +107,7 @@ class ClientPkg < Servlet
104
107
  if @client_build.bundle_changes( @last_change )
105
108
  rebuild_client
106
109
  puts "Autobuilt."
107
- if ARGV.include?('-say')
110
+ if RSence.args[:say]
108
111
  Thread.new do
109
112
  Thread.pass
110
113
  system('say "Autobuilt."')
@@ -169,7 +172,7 @@ class ClientPkg < Servlet
169
172
 
170
173
  @thr = false
171
174
 
172
- @build_logger = BuildLogger.new( File.join(@path,'log','build_log') )
175
+ @build_logger = BuildLogger.new( File.join(::RSence.args[:env_path],'log','build_log') )
173
176
  @build_logger.open
174
177
 
175
178
  @client_build = ClientPkgBuild.new( ::RSence.config[:client_pkg], @build_logger )
@@ -538,7 +538,7 @@ class ClientPkgBuild
538
538
  ## end
539
539
  ## flush
540
540
  ## run
541
- ## `say "Autobuild complete!"` if ARGV.include?('-say')
541
+ ## `say "Autobuild complete!"` if RSence.args[:say]
542
542
  ## end
543
543
  ##end
544
544