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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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.



323
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
# File 'lib/merb-core/server.rb', line 323

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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



357
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
# File 'lib/merb-core/server.rb', line 357

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters

port<~to_s>

The port to check for Merb instances on.

Returns

Boolean

True if Merb is running on the specified port.

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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



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

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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



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

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parameters

port<~to_s>

The port of the Merb process to daemonize.



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

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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.



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

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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



107
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
# File 'lib/merb-core/server.rb', line 107

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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.



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

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

.pid_filesObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get a list of the pid files.

Returns

Array

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



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

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



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

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

.remove_pid(port) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Delete the pidfile for the specified port/socket.



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

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

.remove_pid_file(port) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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.



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

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Shut down Merb, reap any workers if necessary.



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

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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.



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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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.



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

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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.



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

def store_pid(port)
  store_details(port)
end