The server_runner binary provides a production entry point for running, managing, and inspecting the server.
# Build the server
make server
# Start with default config
\./server_runner start
# Start with custom config and port
\./server_runner start -c config/server.example.json -p 9090
# Check if the server is running
\./server_runner status
# Graceful shutdown
\./server_runner stopUsage: server_runner <command> [options]
Commands:
start Start the server (foreground, or -d for daemon)
stop Stop a running server
reload Reload configuration (daemon mode; shuts down foreground)
status Check server status
validate Validate configuration
config Show effective configuration
version Show version information
help Show this help
Start options:
-c, --config <file> Config file (default: config/server.json)
-p, --port <port> Override bind port (0-65535, 0=ephemeral)
-H, --host <address> Override bind address (numeric IPv4 only)
-l, --log-level <level> Override log level
(trace, debug, info, warn, error, critical)
-w, --workers <N> Override worker thread count (0 = auto)
-P, --pid-file <file> PID file path (default: /tmp/reactor_server.pid)
-d, --daemonize Run as a background daemon
--no-health-endpoint Disable /health and /stats endpoints
Stop/status/reload options:
-P, --pid-file <file> PID file path (default: /tmp/reactor_server.pid)
Validate/config options:
-c, --config <file> Config file
-p, --port <port> Override bind port
-H, --host <address> Override bind address
-l, --log-level <level> Override log level
-w, --workers <N> Override worker threads
-d, --daemonize Check daemon-mode constraints (validate only)
-P, --pid-file <file> PID file to validate (validate -d only)
Global options:
-v, --version Same as 'version'
-V, --version-verbose Verbose version with build details
-h, --help Same as 'help'
Running \./server_runner with no arguments prints the usage summary.
Values are resolved in this order (highest wins):
4. CLI flags (-p 9090, -H 0.0.0.0, -l debug, -w 4)
3. Environment (REACTOR_BIND_PORT=9090)
2. Config file (config/server.example.json: {"bind_port": 9090})
1. Defaults (ServerConfig{} defaults: bind_port=8080)
Validate a configuration file without starting the server:
\./server_runner validate -c config/server.example.json
# Output: "Configuration is valid." (exit 0) or error (exit 1)Show the fully resolved config (after applying file, env, and CLI overrides):
\./server_runner config -p 9090 -l debugThis outputs formatted JSON that can be redirected to a file and used as a config:
\./server_runner config -p 9090 > my_config.json
\./server_runner start -c my_config.json\./server_runner start
\./server_runner start -p 9090 -l debug
\./server_runner start -c config/server.example.json --no-health-endpoint\./server_runner status
# reactor_server is running
# PID: 12345
# PID file: /tmp/reactor_server.pidRun the server as a background daemon:
# Requires a log file (daemon has no terminal)
\./server_runner start -d -c config/production.json
# Or set log file via environment variable
REACTOR_LOG_FILE=/var/log/reactor.log \./server_runner start -d
# Verify it started
\./server_runner statusDaemon mode requirements:
- A log file must be configured (
log.filein config orREACTOR_LOG_FILEenv var) - Log file, PID file, and TLS cert/key paths must be absolute
- The launching shell sees exit code 0 once the daemon is ready (readiness pipe), or exit code 1 on startup failure
\./server_runner stop
# Sent SIGTERM to reactor_server (PID 12345)Reload the configuration of a running daemon without restarting:
\./server_runner reload
# Sent SIGHUP to reactor_server (PID 12345)Reload-safe fields (applied immediately or to new connections):
log.level,log.file,log.max_file_size,log.max_filesidle_timeout_sec,request_timeout_secmax_connections,max_body_size,max_header_size,max_ws_message_sizeshutdown_drain_timeout_sechttp2.max_concurrent_streams,http2.initial_window_size,http2.max_frame_size,http2.max_header_list_size
Restart-required fields (logged as skipped on reload):
bind_host,bind_port,tls.*,worker_threads,http2.enabled
You can also send SIGHUP directly:
kill -HUP $(cat /tmp/reactor_server.pid)Note: If a foreground server was started under nohup, SIGHUP is inherited as SIG_IGN and the reload command will be silently ignored. Use daemon mode (-d) for reliable SIGHUP-based reload.
| Signal | Behavior |
|---|---|
SIGTERM |
Graceful shutdown (sends WS Close 1001, drains connections, exits) |
SIGINT |
Same as SIGTERM (Ctrl+C in foreground) |
SIGHUP |
Daemon: reload configuration + reopen log files. Foreground: graceful shutdown (terminal hangup) |
SIGPIPE |
Ignored (handled by MSG_NOSIGNAL) |
Signal handling uses sigwait() (POSIX synchronous signal wait). Signals are blocked in all threads via pthread_sigmask; the main thread loops on sigwait(). In daemon mode, SIGHUP triggers a full configuration reload (re-reads config file, validates, applies reload-safe fields, reopens log files); in foreground mode, SIGHUP causes graceful shutdown (standard Unix terminal hangup behavior). SIGTERM/SIGINT always trigger shutdown.
The server supports logrotate-style log rotation via SIGHUP:
# Manual rotation
kill -HUP $(cat /tmp/reactor_server.pid)
# logrotate config example (/etc/logrotate.d/reactor_server):
# /var/log/reactor.log {
# daily
# rotate 7
# postrotate
# kill -HUP $(cat /tmp/reactor_server.pid) 2>/dev/null || true
# endscript
# }Note: By default (max_files > 1), the server uses date-based log file naming (reactor-YYYY-MM-DD[-N].log) with automatic size-based rotation. When using external logrotate, set log.max_files to 1 in the config to use the raw file path with no automatic rotation — fully compatible with logrotate's rename + SIGHUP workflow.
The server writes its PID to a file on startup and removes it on exit. The PID file uses flock() for race-free singleton enforcement.
- Default path:
/tmp/reactor_server.pid - Override:
-P /path/to/custom.pid - If the server crashes (SIGKILL), the stale PID file is automatically detected on the next start
Run multiple instances with different PID files and ports:
\./server_runner start -p 8080 -P /tmp/reactor_8080.pid &
\./server_runner start -p 8081 -P /tmp/reactor_8081.pid &By default, the server registers /health and /stats endpoints:
curl http://127.0.0.1:8080/health
# {"status":"ok","pid":12345,"uptime_seconds":3600}
curl http://127.0.0.1:8080/stats
# {
# "uptime_seconds": 3600,
# "connections": {
# "active": 42, "active_http1": 30, "active_http2": 12,
# "active_h2_streams": 58, "total_accepted": 10234
# },
# "requests": { "total": 50000, "active": 15 },
# "config": {
# "bind_host": "127.0.0.1", "bind_port": 8080,
# "worker_threads": 3, "max_connections": 10000,
# "idle_timeout_sec": 300, "request_timeout_sec": 30,
# "tls_enabled": false, "http2_enabled": true
# }
# }/health— lightweight liveness check with PID and uptime/stats— runtime metrics with connection/request counters and current config- The
configsection in/statsreflects live values for reload-safe fields (max_connections,idle_timeout_sec,request_timeout_sec) and startup values for restart-required fields - No PID, file paths, or TLS details are exposed in
/stats - If the server is internet-facing, restrict access to these endpoints via firewall or reverse proxy
Disable both with --no-health-endpoint.
\./server_runner version
# reactor_server version 1.0.0
\./server_runner version -V
# reactor_server version 1.0.0
# Compiler: 13.3.0 (C++17)
# OpenSSL: OpenSSL 3.0.13 30 Jan 2024
# Platform: Linux
# Features: HTTP/1.1, HTTP/2 (RFC 9113), WebSocket (RFC 6455), TLS/SSL| Code | Meaning |
|---|---|
| 0 | Success (normal exit, config valid, status confirms running, stop sent) |
| 1 | General error (config invalid, bind failure, server not running) |
| 2 | Usage error (unknown command, invalid option) |
make server # Build only the production binary
make all # Build both test runner (./run) and server (\./server_runner)
make clean # Remove both binaries