Class: Merb::Server

Inherits:
Object show all
Defined in:
lib/merb-core/server.rb

Overview

Server encapsulates the management of Merb daemons.

Class Method Summary collapse

Class Method Details

._change_privilege(user, group = user) ⇒ Object

Change privileges of the process to the specified user and group.

Parameters

user<String>

The user to change the process to.

group<String>

The group to change the process to.

Alternatives

If group is left out, the user will be used as the group.

:api: private


324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/merb-core/server.rb', line 324

def _change_privilege(user, group=user)
  Merb.logger.warn! "Changing privileges to #{user}:#{group}"

  uid, gid = Process.euid, Process.egid

  begin
    target_uid = Etc.getpwnam(user).uid
  rescue ArgumentError => e
    Merb.fatal!("Failed to change to user #{user}, does the user exist?", e)
    return false
  end

  begin
    target_gid = Etc.getgrnam(group).gid
  rescue ArgumentError => e
    Merb.fatal!("Failed to change to group #{group}, does the group exist?", e)
    return false
  end

  if (uid != target_uid) || (gid != target_gid)
    # Change process ownership
    Process.initgroups(user, target_gid)
    Process::GID.change_privilege(target_gid)
    Process::UID.change_privilege(target_uid)
  end
  true
rescue Errno::EPERM => e
  Merb.fatal! "Permission denied for changing user:group to #{user}:#{group}.", e
  false
end

.add_irb_trapObject

Add trap to enter IRB on SIGINT. Process exit if second SIGINT is received.

:api: private


358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/merb-core/server.rb', line 358

def add_irb_trap
  Merb.trap("INT") do
    if @interrupted
      Merb.logger.warn! "Interrupt received a second time, exiting!\n"
      exit
    end

    @interrupted = true
    Merb.logger.warn! "Interrupt a second time to quit."
    Kernel.sleep 1.5
    ARGV.clear # Avoid passing args to IRB

    if @irb.nil?
      require "irb"
      IRB.setup(nil)
      @irb = IRB::Irb.new(nil)
      IRB.conf[:MAIN_CONTEXT] = @irb.context
    end

    Merb.trap(:INT) { @irb.signal_handle }
    catch(:IRB_EXIT) { @irb.eval_input }

    Merb.logger.warn! "Exiting from IRB mode back into server mode."
    @interrupted = false
    add_irb_trap
  end
end

.alive?(port) ⇒ Boolean

Parameters

port<~to_s>

The port to check for Merb instances on.

Returns

Boolean

True if Merb is running on the specified port.

:api: private

Returns:

  • (Boolean)

54
55
56
57
58
59
60
61
62
63
# File 'lib/merb-core/server.rb', line 54

def alive?(port)
  pidfile = pid_file(port)
  pid     = pid_in_file(pidfile)
  Process.kill(0, pid)
  true
rescue Errno::ESRCH, Errno::ENOENT
  false
rescue Errno::EACCES => e
  Merb.fatal!("You don't have access to the PID file at #{pidfile}: #{e.message}")
end

.bootupObject

Starts up Merb by running the bootloader and starting the adapter.

:api: private


168
169
170
171
172
173
174
175
# File 'lib/merb-core/server.rb', line 168

def bootup
  Merb.trap("TERM") { shutdown }

  Merb.logger.warn! "Running bootloaders..." if Merb::Config[:verbose]
  BootLoader.run
  Merb.logger.warn! "Starting Rack adapter..." if Merb::Config[:verbose]
  Merb.adapter.start(Merb::Config.to_hash)
end

.change_privilegeObject

Change process user/group to those specified in Merb::Config.

:api: private


190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/merb-core/server.rb', line 190

def change_privilege
  if Merb::Config[:user] && Merb::Config[:group]
    Merb.logger.verbose! "About to change privilege to group " \
      "#{Merb::Config[:group]} and user #{Merb::Config[:user]}"
    _change_privilege(Merb::Config[:user], Merb::Config[:group])
  elsif Merb::Config[:user]
    Merb.logger.verbose! "About to change privilege to user " \
      "#{Merb::Config[:user]}"
    _change_privilege(Merb::Config[:user])
  else
    return true
  end
end

.daemonize(port) ⇒ Object

Parameters

port<~to_s>

The port of the Merb process to daemonize.

:api: private


141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/merb-core/server.rb', line 141

def daemonize(port)
  Merb.logger.warn! "About to fork..." if Merb::Config[:verbose]
  fork do
    Process.setsid
    exit if fork
    Merb.logger.warn! "In #{Process.pid}" if Merb.logger
    File.umask 0000
    STDIN.reopen "/dev/null"
    STDOUT.reopen "/dev/null", "a"
    STDERR.reopen STDOUT
    begin
      Dir.chdir Merb::Config[:merb_root]
    rescue Errno::EACCES => e
      Merb.fatal! "You specified Merb root as #{Merb::Config[:merb_root]}, " \
        "yet the current user does not have access to it. ", e
    end
    at_exit { remove_pid_file(port) }
    Merb::Config[:port] = port
    bootup
  end
rescue NotImplementedError => e
  Merb.fatal! "Daemonized mode is not supported on your platform. ", e
end

.kill(port, sig = "INT") ⇒ Object

Parameters

port<~to_s>

The port of the Merb process to kill.

sig<~to_s>

The signal to send to the process, the default is 9 - SIGKILL.

No Name Default Action Description 1 SIGHUP terminate process terminal line hangup 2 SIGINT terminate process interrupt program 3 SIGQUIT create core image quit program 4 SIGILL create core image illegal instruction 9 SIGKILL terminate process kill program 15 SIGTERM terminate process software termination signal 30 SIGUSR1 terminate process User defined signal 1 31 SIGUSR2 terminate process User defined signal 2

Alternatives

If you pass “all” as the port, the signal will be sent to all Merb processes.

:api: private


88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/merb-core/server.rb', line 88

def kill(port, sig = "INT")
  if sig.is_a?(Integer)
    sig = Signal.list.invert[sig]
  end
  
  Merb::BootLoader::BuildFramework.run

  # If we kill the master, then the workers should be reaped also.
  if %w(main master all).include?(port)
    # If a graceful exit is requested then send INT to the master process.
    #
    # Otherwise read pids from pid files and try to kill each process in turn.
    kill_pid(sig, pid_file("main")) if sig == "INT"
  else
    kill_pid(sig, pid_file(port))
  end
end

.kill_pid(sig, file) ⇒ Object

Sends the provided signal to the process pointed at by the provided pid file. :api: private


108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/merb-core/server.rb', line 108

def kill_pid(sig, file)
  begin
    pid = pid_in_file(file)
    Merb.logger.fatal! "Killing pid #{pid} with #{sig}"
    Process.kill(sig, pid)
    FileUtils.rm(file) if File.exist?(file)
  rescue Errno::EINVAL
    Merb.logger.fatal! "Failed to kill PID #{pid} with #{sig}: '#{sig}' is an invalid " \
      "or unsupported signal number."
  rescue Errno::EPERM
    Merb.logger.fatal! "Failed to kill PID #{pid} with #{sig}: Insufficient permissions."
  rescue Errno::ESRCH
    FileUtils.rm file
    Merb.logger.fatal! "Failed to kill PID #{pid} with #{sig}: Process is " \
      "deceased or zombie."
  rescue Errno::EACCES => e
    Merb.logger.fatal! e.message
  rescue Errno::ENOENT => e
    # This should not cause abnormal exit, which is why 
    # we do not use Merb.fatal but instead just log with max level.
    Merb.logger.fatal! "Could not find a PID file at #{file}. " \
      "Most likely the process is no longer running and the pid file was not cleaned up."
  rescue Exception => e
    if !e.is_a?(SystemExit)
      Merb.logger.fatal! "Failed to kill PID #{pid.inspect} with #{sig.inspect}: #{e.message}"
    end
  end
end

.pid_file(port) ⇒ Object

Gets the pid file for the specified port/socket.

Parameters

port<~to_s>

The port/socket of the Merb process to whom the the PID file belongs to.

Returns

String

Location of pid file for specified port. If clustered and pid_file option is specified, it adds the port/socket value to the path.

:api: private


290
291
292
293
# File 'lib/merb-core/server.rb', line 290

def pid_file(port)
  pidfile = Merb::Config[:pid_file] || (Merb.log_path / "merb.%s.pid")
  pidfile % port
end

.pid_filesObject

Get a list of the pid files.

Returns

Array

List of pid file paths. If not running clustered, the array contains a single path.

:api: private


302
303
304
305
306
307
308
309
310
311
312
# File 'lib/merb-core/server.rb', line 302

def pid_files
 if Merb::Config[:pid_file]
   if Merb::Config[:cluster]
     Dir[Merb::Config[:pid_file] % "*"]
   else
     [ Merb::Config[:pid_file] ]
   end
 else
   Dir[Merb.log_path / "merb.*.pid"]
 end
end

.pid_in_file(pidfile) ⇒ Object

:api: private


66
67
68
# File 'lib/merb-core/server.rb', line 66

def pid_in_file(pidfile)
  File.read(pidfile).chomp.to_i
end

.remove_pid(port) ⇒ Object

Delete the pidfile for the specified port/socket.

:api: private


245
246
247
# File 'lib/merb-core/server.rb', line 245

def remove_pid(port)
  FileUtils.rm(pid_file(port)) if File.file?(pid_file(port))
end

.remove_pid_file(port) ⇒ Object

Removes a PID file used by the server from the filesystem. This uses :pid_file options from configuration when provided or merb.<port/socket>.pid in log directory by default.

Parameters

port<~to_s>

The port of the Merb process to whom the the PID file belongs to.

Alternatives

If Merb::Config has been specified, that will be used instead of the port/socket based PID file.

:api: private


217
218
219
220
221
222
223
# File 'lib/merb-core/server.rb', line 217

def remove_pid_file(port)
  pidfile = pid_file(port)
  if File.exist?(pidfile)
    Merb.logger.warn! "Removing pid file #{pidfile} (port/socket: #{port})..."
    FileUtils.rm(pidfile)
  end
end

.shutdown(status = 0) ⇒ Object

Shut down Merb, reap any workers if necessary.

:api: private


180
181
182
183
184
185
# File 'lib/merb-core/server.rb', line 180

def shutdown(status = 0)
  # reap_workers does exit but may not be called...
  Merb::BootLoader::LoadClasses.reap_workers(status) if Merb::Config[:fork_for_class_load]
  # which is why we exit explicitly here
  exit(status)
end

.start(port, cluster = nil) ⇒ Object

Start a Merb server, in either foreground, daemonized or cluster mode.

Parameters

port<~to_i>

The port to which the first server instance should bind to. Subsequent server instances bind to the immediately following ports.

cluster<~to_i>

Number of servers to run in a cluster.

Alternatives

If cluster is left out, then one process will be started. This process will be daemonized if Merb::Config is true.

:api: private


23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/merb-core/server.rb', line 23

def start(port, cluster=nil)

  @port = port
  @cluster = cluster

  if Merb::Config[:daemonize]
    pidfile = pid_file(port)
    pid = File.read(pidfile).chomp.to_i if File.exist?(pidfile)

    unless alive?(@port)
      remove_pid_file(@port)
      Merb.logger.warn! "Daemonizing..." if Merb::Config[:verbose]
      daemonize(@port)
    else
      Merb.fatal! "Merb is already running on port #{port}.\n" \
        "\e[0m   \e[1;31;47mpid file: \e[34;47m#{pidfile}" \
        "\e[1;31;47m, process id is \e[34;47m#{pid}."
    end
  else
    bootup
  end
end

.store_details(port = nil) ⇒ Object

Stores a PID file on the filesystem. This uses :pid_file options from configuration when provided or merb.<port/socket>.pid in log directory by default.

Parameters

port<~to_s>

The port of the Merb process to whom the the PID file belongs to.

Alternatives

If Merb::Config has been specified, that will be used instead of the port/socket based PID file.

:api: private


262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/merb-core/server.rb', line 262

def store_details(port = nil)
  file = pid_file(port)
  begin
    FileUtils.mkdir_p(File.dirname(file))
  rescue Errno::EACCES => e
    Merb.fatal! "Failed to store Merb logs in #{File.dirname(file)}, " \
      "permission denied. ", e
  end
  Merb.logger.warn! "Storing pid #{Process.pid} file to #{file}..." if Merb::Config[:verbose]
  begin
    File.open(file, 'w'){ |f| f.write(Process.pid.to_s) }
  rescue Errno::EACCES => e
    Merb.fatal! "Failed to access #{file}, permission denied.", e
  end
end

.store_pid(port) ⇒ Object

Stores a PID file on the filesystem. This uses :pid_file options from configuration when provided or merb.<port>.pid in log directory by default.

Parameters

port<~to_s>

The port of the Merb process to whom the the PID file belongs to.

Alternatives

If Merb::Config has been specified, that will be used instead of the port/socket based PID file.

:api: private


238
239
240
# File 'lib/merb-core/server.rb', line 238

def store_pid(port)
  store_details(port)
end