A framework to implement robust multiprocess servers like Unicorn
Ruby
763
403 commits
updated Jul 16, 2025
ServerEngine is a framework to implement robust multiprocess servers like Unicorn.
Main features:
Heartbeat via pipe
& auto-restart
/ \ ---+
+------------+ / +----------+ \ +--------+ |
| Supervisor |------| Server |------| Worker | |
+------------+ +----------+\ +--------+ | Multi-process
/ \ | or multi-thread
/ \ +--------+ |
Dynamic reconfiguration | Worker | |
and live restart support +--------+ |
---+
ServerEngine also provides useful options and utilities such as logging, signal handlers, changing process names shown by ps command, chuser, stacktrace and heap dump on signal.
What you need to implement at least is a worker module which has run and stop methods.
require 'serverengine'
module MyWorker
def run
until @stop
logger.info "Awesome work!"
sleep 1
end
end
def stop
@stop = true
end
end
se = ServerEngine.create(nil, MyWorker, {
daemonize: true,
log: 'myserver.log',
pid_path: 'myserver.pid',
})
se.run
Send TERM signal (or KILL on Windows) to kill the daemon. See also Signals section bellow for details.
Simply set worker_type: "process" or worker_type: "thread" parameter, and set number of workers to workers parameter.
se = ServerEngine.create(nil, MyWorker, {
daemonize: true,
log: 'myserver.log',
pid_path: 'myserver.pid',
worker_type: 'process',
workers: 4,
})
se.run
See also Worker types section bellow.
One of the typical implementation styles of TCP servers is that a parent process listens socket and child processes accept connections from clients.
ServerEngine allows you to optionally implement a server module to control the parent process:
# Server module controls the parent process
module MyServer
def before_run
@sock = TCPServer.new(config[:bind], config[:port])
end
attr_reader :sock
end
# Worker module controls child processes
module MyWorker
def run
until @stop
# you should use Cool.io or EventMachine actually
c = server.sock.accept
c.write "Awesome work!"
c.close
end
end
def stop
@stop = true
end
end
se = ServerEngine.create(MyServer, MyWorker, {
daemonize: true,
log: 'myserver.log',
pid_path: 'myserver.pid',
worker_type: 'process',
workers: 4,
bind: '0.0.0.0',
port: 9071,
})
se.run
Above worker_type: "process" depends on fork system call, which doesn't work on Windows or JRuby platform.
ServerEngine provides worker_type: "spawn" for those platforms (This is still EXPERIMENTAL). However, unfortunately, you need to implement different worker module because worker_type: "spawn" is not compatible with worker_type: "process" in terms of API.
What you need to implement at least to use worker_type: "spawn" is def spawn(process_manager) method. You will call process_manager.spawn at the method, where spawn is same with Process.spawn excepting return value.
In addition, Windows does not support signals. ServerEngine provides command_sender: "pipe" for Windows (and for other platforms, if you want to use it). When using command_sender: "pipe", the child process have to handle commands sent from parent process via STDIN. On Windows, command_sender: "pipe" is default.
You can call Server#stop(stop_graceful) and Server#restart(stop_graceful) instead of sending signals.
module MyWorker
def spawn(process_manager)
env = {
'SERVER_ENGINE_CONFIG' => config.to_json
}
script = %[
require 'serverengine'
require 'json'
conf = JSON.parse(ENV['SERVER_ENGINE_CONFIG'], symbolize_names: true)
logger = ServerEngine::DaemonLogger.new(conf[:log] || STDOUT, conf)
@stop = false
command_pipe = STDIN.dup
STDIN.reopen(File::NULL)
Thread.new do
until @stop
case command_pipe.gets.chomp
when "GRACEFUL_STOP"
@stop = true
when "IMMEDIATE_STOP"
@stop = true
when "GRACEFUL_RESTART", "IMMEDIATE_RESTART"
# do something...
end
end
end
until @stop
logger.info 'Awesome work!'
sleep 1
end
]
process_manager.spawn(env, "ruby", "-e", script)
end
end
se = ServerEngine.create(nil, MyWorker, {
worker_type: 'spawn',
command_sender: 'pipe',
log: 'myserver.log',
})
se.run
ServerEngine logger rotates logs by 1MB and keeps 5 generations by default.
se = ServerEngine.create(MyServer, MyWorker, {
log: 'myserver.log',
log_level: 'debug',
log_rotate_age: 5,
log_rotate_size: 1*1024*1024,
})
se.run
ServerEngine's default logger extends from Ruby's standard Logger library to:
See also Configuration section bellow.
Server programs running 24x7 hours need to survive even if a process stalled because of unexpected memory swapping or network errors.
Supervisor process runs as the parent process of the server process and monitor it to restart automatically. You can enable supervisor process by setting supervisor: true parameter:
se = ServerEngine.create(nil, MyWorker, {
daemonize: true,
pid_path: 'myserver.pid',
supervisor: true, # enables supervisor process
})
se.run
This auto restart feature will be suppressed for workers which exits with exit code specified by unrecoverable_exit_codes. At this case, whole process will exit without error (exit code 0).
You can restart a server process without waiting for completion of all workers using INT signal (supervisor: true and enable_detach: true parameters must be enabled).
This feature allows you to minimize downtime where workers take long time to complete a task.
# 1. starts server
+------------+ +----------+ +-----------+
| Supervisor |----| Server |----| Worker(s) |
+------------+ +----------+ +-----------+
# 2. receives SIGINT and waits for shutdown of the server for server_detach_wait
+------------+ +----------+ +-----------+
| Supervisor | | Server |----| Worker(s) |
+------------+ +----------+ +-----------+
# 3. starts new server if the server doesn't exit in server_detach_wait time
+------------+ +----------+ +-----------+
| Supervisor |\ | Server |----| Worker(s) |
+------------+ | +----------+ +-----------+
|
| +----------+ +-----------+
\--| Server |----| Worker(s) |
+----------+ +-----------+
# 4. old server exits eventually
+------------+
| Supervisor |\
+------------+ |
|
| +----------+ +-----------+
\--| Server |----| Worker(s) |
+----------+ +-----------+
Note that network servers (which listen sockets) shouldn't use live restart because it causes "Address already in use" error at the server process. Instead, simply use worker_type: "process" configuration and send USR1 to restart workers instead of the server. It restarts a worker without waiting for shutdown of the other workers. This way doesn't cause downtime because server process doesn't close listening sockets and keeps accepting new clients (See also restart_server_process parameter if necessary).
Robust servers should not restart only to update configuration parameters.
module MyWorker
def initialize
reload
end
def reload
@message = config[:message] || "Awesome work!"
@sleep = config[:sleep] || 1
end
def run
until @stop
logger.info @message
sleep @sleep
end
end
def stop
@stop = true
end
end
se = ServerEngine.create(nil, MyWorker) do
YAML.load_file("config.yml").merge({
daemonize: true,
worker_type: 'process',
})
end
se.run
Send USR2 signal to reload configuration file.
worker_type is "process")worker_type is "process")supervisor and enable_detach parameters are true. otherwise graceful shutdown)disable_sigdump: true in the configuration.Immediate shutdown and restart send SIGQUIT signal to worker processes which kills the processes.
Graceful shutdown and restart call Worker#stop method and wait for completion of Worker#run method.
Note that signals are not supported on Windows. You have to use piped command instead of signals on Windows. See also Multiprocess server on Windows and JRuby platforms section.
ServerEngine::BlockingFlag is recommended to stop workers because stop method is called by a different thread from the run thread.
module MyWorker
def initialize
@stop_flag = ServerEngine::BlockingFlag.new
end
def run
until @stop_flag.wait_for_set(1.0) # or @stop_flag.set?
logger.info @message
end
end
def stop
@stop_flag.set!
end
end
se = ServerEngine.create(nil, MyWorker) do
YAML.load_file(config).merge({
daemonize: true,
worker_type: 'process'
})
end
se.run
ServerEngine::SocketManager is a powerful library to listen on the same port across multiple worker processes dynamically.
module MyServer
def before_run
@socket_manager_server = ServerEngine::SocketManager::Server.open
@socket_manager_path = @socket_manager_server.path
end
def after_run
@socket_manager_server.close
end
attr_reader :socket_manager_path
end
module MyWorker
def initialize
@stop_flag = ServerEngine::BlockingFlag.new
@socket_manager = ServerEngine::SocketManager::Client.new(server.socket_manager_path)
end
def run
lsock = @socket_manager.listen_tcp('0.0.0.0', 12345)
until @stop
c = lsock.accept
c.write "Awesome work!"
c.close
end
end
def stop
@stop = true
end
end
se = ServerEngine.create(MyServer, MyWorker, {
daemonize: true,
log: 'myserver.log',
pid_path: 'myserver.pid',
worker_type: 'process',
workers: 4,
bind: '0.0.0.0',
port: 9071,
})
se.run
Other features:
socket_manager_server = SocketManager::Server.share_sockets_with_another_server(path)
See also examples.
Available methods are different depending on worker_type. ServerEngine supports 3 worker types:
initialize is called in the parent process (or thread) in contrast to the other methodsbefore_fork is called before fork for each worker process [worker_type = "thread", "process"]run is the required method for worker_type = "embedded", "thread", "process"spawn(process_manager) is the required method for worker_type = "spawn". Should call process_manager.spawn([env,] command... [,options]).stop is called when TERM signal is received [worker_type = "embedded", "thread", "process"]reload is called when USR2 signal is received [worker_type = "embedded", "thread", "process"]after_start is called after starting the worker process in the parent process (or thread) [worker_type = "thread", "process", "spawn"]server server instanceconfig configurationlogger loggerworker_id serial id of workers beginning from 0initialize is called in the parent process in contrast to the other methodsbefore_run is called before starting workersafter_run is called before shutting downafter_start is called after starting the server process in the parent process (available if supervisor parameter is true)super in these methods)
reload_configstop(stop_graceful)restart(stop_graceful)config configurationlogger loggerSIGCONT signal and dumping of the thread (default: false)supervisor parameters is true
worker_type is "thread" or "process" or "spawn"
worker_type is "process"
worker_type is "spawn"
log_rotate_age is 0, the log_rotate_size value will be ignored.Author: Sadayuki Furuhashi
Copyright: Copyright (c) 2012-2013 Sadayuki Furuhashi
License: Apache License, Version 2.0
278 followers · starred Sep 2014
46 followers · starred Aug 2016
314 followers · starred Sep 2014
28 followers · starred Apr 2016
A framework to implement robust multiprocess servers like Unicorn
Ruby
763
403 commits
updated Jul 16, 2025
ServerEngine is a framework to implement robust multiprocess servers like Unicorn.
Main features:
Heartbeat via pipe
& auto-restart
/ \ ---+
+------------+ / +----------+ \ +--------+ |
| Supervisor |------| Server |------| Worker | |
+------------+ +----------+\ +--------+ | Multi-process
/ \ | or multi-thread
/ \ +--------+ |
Dynamic reconfiguration | Worker | |
and live restart support +--------+ |
---+
ServerEngine also provides useful options and utilities such as logging, signal handlers, changing process names shown by ps command, chuser, stacktrace and heap dump on signal.
What you need to implement at least is a worker module which has run and stop methods.
require 'serverengine'
module MyWorker
def run
until @stop
logger.info "Awesome work!"
sleep 1
end
end
def stop
@stop = true
end
end
se = ServerEngine.create(nil, MyWorker, {
daemonize: true,
log: 'myserver.log',
pid_path: 'myserver.pid',
})
se.run
Send TERM signal (or KILL on Windows) to kill the daemon. See also Signals section bellow for details.
Simply set worker_type: "process" or worker_type: "thread" parameter, and set number of workers to workers parameter.
se = ServerEngine.create(nil, MyWorker, {
daemonize: true,
log: 'myserver.log',
pid_path: 'myserver.pid',
worker_type: 'process',
workers: 4,
})
se.run
See also Worker types section bellow.
One of the typical implementation styles of TCP servers is that a parent process listens socket and child processes accept connections from clients.
ServerEngine allows you to optionally implement a server module to control the parent process:
# Server module controls the parent process
module MyServer
def before_run
@sock = TCPServer.new(config[:bind], config[:port])
end
attr_reader :sock
end
# Worker module controls child processes
module MyWorker
def run
until @stop
# you should use Cool.io or EventMachine actually
c = server.sock.accept
c.write "Awesome work!"
c.close
end
end
def stop
@stop = true
end
end
se = ServerEngine.create(MyServer, MyWorker, {
daemonize: true,
log: 'myserver.log',
pid_path: 'myserver.pid',
worker_type: 'process',
workers: 4,
bind: '0.0.0.0',
port: 9071,
})
se.run
Above worker_type: "process" depends on fork system call, which doesn't work on Windows or JRuby platform.
ServerEngine provides worker_type: "spawn" for those platforms (This is still EXPERIMENTAL). However, unfortunately, you need to implement different worker module because worker_type: "spawn" is not compatible with worker_type: "process" in terms of API.
What you need to implement at least to use worker_type: "spawn" is def spawn(process_manager) method. You will call process_manager.spawn at the method, where spawn is same with Process.spawn excepting return value.
In addition, Windows does not support signals. ServerEngine provides command_sender: "pipe" for Windows (and for other platforms, if you want to use it). When using command_sender: "pipe", the child process have to handle commands sent from parent process via STDIN. On Windows, command_sender: "pipe" is default.
You can call Server#stop(stop_graceful) and Server#restart(stop_graceful) instead of sending signals.
module MyWorker
def spawn(process_manager)
env = {
'SERVER_ENGINE_CONFIG' => config.to_json
}
script = %[
require 'serverengine'
require 'json'
conf = JSON.parse(ENV['SERVER_ENGINE_CONFIG'], symbolize_names: true)
logger = ServerEngine::DaemonLogger.new(conf[:log] || STDOUT, conf)
@stop = false
command_pipe = STDIN.dup
STDIN.reopen(File::NULL)
Thread.new do
until @stop
case command_pipe.gets.chomp
when "GRACEFUL_STOP"
@stop = true
when "IMMEDIATE_STOP"
@stop = true
when "GRACEFUL_RESTART", "IMMEDIATE_RESTART"
# do something...
end
end
end
until @stop
logger.info 'Awesome work!'
sleep 1
end
]
process_manager.spawn(env, "ruby", "-e", script)
end
end
se = ServerEngine.create(nil, MyWorker, {
worker_type: 'spawn',
command_sender: 'pipe',
log: 'myserver.log',
})
se.run
ServerEngine logger rotates logs by 1MB and keeps 5 generations by default.
se = ServerEngine.create(MyServer, MyWorker, {
log: 'myserver.log',
log_level: 'debug',
log_rotate_age: 5,
log_rotate_size: 1*1024*1024,
})
se.run
ServerEngine's default logger extends from Ruby's standard Logger library to:
See also Configuration section bellow.
Server programs running 24x7 hours need to survive even if a process stalled because of unexpected memory swapping or network errors.
Supervisor process runs as the parent process of the server process and monitor it to restart automatically. You can enable supervisor process by setting supervisor: true parameter:
se = ServerEngine.create(nil, MyWorker, {
daemonize: true,
pid_path: 'myserver.pid',
supervisor: true, # enables supervisor process
})
se.run
This auto restart feature will be suppressed for workers which exits with exit code specified by unrecoverable_exit_codes. At this case, whole process will exit without error (exit code 0).
You can restart a server process without waiting for completion of all workers using INT signal (supervisor: true and enable_detach: true parameters must be enabled).
This feature allows you to minimize downtime where workers take long time to complete a task.
# 1. starts server
+------------+ +----------+ +-----------+
| Supervisor |----| Server |----| Worker(s) |
+------------+ +----------+ +-----------+
# 2. receives SIGINT and waits for shutdown of the server for server_detach_wait
+------------+ +----------+ +-----------+
| Supervisor | | Server |----| Worker(s) |
+------------+ +----------+ +-----------+
# 3. starts new server if the server doesn't exit in server_detach_wait time
+------------+ +----------+ +-----------+
| Supervisor |\ | Server |----| Worker(s) |
+------------+ | +----------+ +-----------+
|
| +----------+ +-----------+
\--| Server |----| Worker(s) |
+----------+ +-----------+
# 4. old server exits eventually
+------------+
| Supervisor |\
+------------+ |
|
| +----------+ +-----------+
\--| Server |----| Worker(s) |
+----------+ +-----------+
Note that network servers (which listen sockets) shouldn't use live restart because it causes "Address already in use" error at the server process. Instead, simply use worker_type: "process" configuration and send USR1 to restart workers instead of the server. It restarts a worker without waiting for shutdown of the other workers. This way doesn't cause downtime because server process doesn't close listening sockets and keeps accepting new clients (See also restart_server_process parameter if necessary).
Robust servers should not restart only to update configuration parameters.
module MyWorker
def initialize
reload
end
def reload
@message = config[:message] || "Awesome work!"
@sleep = config[:sleep] || 1
end
def run
until @stop
logger.info @message
sleep @sleep
end
end
def stop
@stop = true
end
end
se = ServerEngine.create(nil, MyWorker) do
YAML.load_file("config.yml").merge({
daemonize: true,
worker_type: 'process',
})
end
se.run
Send USR2 signal to reload configuration file.
worker_type is "process")worker_type is "process")supervisor and enable_detach parameters are true. otherwise graceful shutdown)disable_sigdump: true in the configuration.Immediate shutdown and restart send SIGQUIT signal to worker processes which kills the processes.
Graceful shutdown and restart call Worker#stop method and wait for completion of Worker#run method.
Note that signals are not supported on Windows. You have to use piped command instead of signals on Windows. See also Multiprocess server on Windows and JRuby platforms section.
ServerEngine::BlockingFlag is recommended to stop workers because stop method is called by a different thread from the run thread.
module MyWorker
def initialize
@stop_flag = ServerEngine::BlockingFlag.new
end
def run
until @stop_flag.wait_for_set(1.0) # or @stop_flag.set?
logger.info @message
end
end
def stop
@stop_flag.set!
end
end
se = ServerEngine.create(nil, MyWorker) do
YAML.load_file(config).merge({
daemonize: true,
worker_type: 'process'
})
end
se.run
ServerEngine::SocketManager is a powerful library to listen on the same port across multiple worker processes dynamically.
module MyServer
def before_run
@socket_manager_server = ServerEngine::SocketManager::Server.open
@socket_manager_path = @socket_manager_server.path
end
def after_run
@socket_manager_server.close
end
attr_reader :socket_manager_path
end
module MyWorker
def initialize
@stop_flag = ServerEngine::BlockingFlag.new
@socket_manager = ServerEngine::SocketManager::Client.new(server.socket_manager_path)
end
def run
lsock = @socket_manager.listen_tcp('0.0.0.0', 12345)
until @stop
c = lsock.accept
c.write "Awesome work!"
c.close
end
end
def stop
@stop = true
end
end
se = ServerEngine.create(MyServer, MyWorker, {
daemonize: true,
log: 'myserver.log',
pid_path: 'myserver.pid',
worker_type: 'process',
workers: 4,
bind: '0.0.0.0',
port: 9071,
})
se.run
Other features:
socket_manager_server = SocketManager::Server.share_sockets_with_another_server(path)
See also examples.
Available methods are different depending on worker_type. ServerEngine supports 3 worker types:
initialize is called in the parent process (or thread) in contrast to the other methodsbefore_fork is called before fork for each worker process [worker_type = "thread", "process"]run is the required method for worker_type = "embedded", "thread", "process"spawn(process_manager) is the required method for worker_type = "spawn". Should call process_manager.spawn([env,] command... [,options]).stop is called when TERM signal is received [worker_type = "embedded", "thread", "process"]reload is called when USR2 signal is received [worker_type = "embedded", "thread", "process"]after_start is called after starting the worker process in the parent process (or thread) [worker_type = "thread", "process", "spawn"]server server instanceconfig configurationlogger loggerworker_id serial id of workers beginning from 0initialize is called in the parent process in contrast to the other methodsbefore_run is called before starting workersafter_run is called before shutting downafter_start is called after starting the server process in the parent process (available if supervisor parameter is true)super in these methods)
reload_configstop(stop_graceful)restart(stop_graceful)config configurationlogger loggerSIGCONT signal and dumping of the thread (default: false)supervisor parameters is true
worker_type is "thread" or "process" or "spawn"
worker_type is "process"
worker_type is "spawn"
log_rotate_age is 0, the log_rotate_size value will be ignored.Author: Sadayuki Furuhashi
Copyright: Copyright (c) 2012-2013 Sadayuki Furuhashi
License: Apache License, Version 2.0
278 followers · starred Sep 2014
46 followers · starred Aug 2016
314 followers · starred Sep 2014
28 followers · starred Apr 2016