Class: Merb::Server

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

Overview

Server encapsulates the management of Merb daemons.

Class Method Summary (collapse)

Class Method Details

+ (Object) _change_privilege(user, group = user)

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.

Alternatives

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



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'merb-core/lib/merb-core/server.rb', line 305

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

+ (Object) add_irb_trap

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.



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'merb-core/lib/merb-core/server.rb', line 339

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

+ (Boolean) alive?(port)

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.

Returns True if Merb is running on the specified port.



51
52
53
54
55
56
57
58
59
60
# File 'merb-core/lib/merb-core/server.rb', line 51

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

+ (Object) bootup

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.



164
165
166
167
168
169
170
171
# File 'merb-core/lib/merb-core/server.rb', line 164

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

+ (Object) change_privilege

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.



186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'merb-core/lib/merb-core/server.rb', line 186

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

+ (Object) daemonize(port)

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.



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

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

+ (Object) kill(port, sig = "INT")

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.

Send a signal to a Merb process.

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


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

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

+ (Object) kill_pid(sig, file)

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.



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

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

+ (String) pid_file(port)

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.



273
274
275
276
# File 'merb-core/lib/merb-core/server.rb', line 273

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

+ (Array) pid_files

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.



284
285
286
287
288
289
290
291
292
293
294
# File 'merb-core/lib/merb-core/server.rb', line 284

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

+ (Object) pid_in_file(pidfile)

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.



63
64
65
# File 'merb-core/lib/merb-core/server.rb', line 63

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

+ (Object) remove_pid(port)

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.



231
232
233
# File 'merb-core/lib/merb-core/server.rb', line 231

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

+ (Object) remove_pid_file(port)

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..pid in log directory by default.

Alternatives

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



212
213
214
215
216
217
218
# File 'merb-core/lib/merb-core/server.rb', line 212

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

+ (Object) shutdown(status = 0)

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.



176
177
178
179
180
181
# File 'merb-core/lib/merb-core/server.rb', line 176

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

+ (Object) start(port, cluster = nil)

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.

Alternatives

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



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

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

+ (Object) store_details(port = nil)

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..pid in log directory by default.

Alternatives

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



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'merb-core/lib/merb-core/server.rb', line 247

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

+ (Object) store_pid(port)

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.



224
225
226
# File 'merb-core/lib/merb-core/server.rb', line 224

def store_pid(port)
  store_details(port)
end