Keep FastCGI available during restarts
This commit is contained in:
@@ -145,6 +145,25 @@ module lives in the configured writable cache root rather than beside the
|
|||||||
possibly root-owned deployed `core.wasm`; freshness still uses the deployed
|
possibly root-owned deployed `core.wasm`; freshness still uses the deployed
|
||||||
artifact's metadata.
|
artifact's metadata.
|
||||||
|
|
||||||
|
Packaged deployments use systemd socket activation for FastCGI. The socket unit
|
||||||
|
owns `/run/uce/fastcgi.sock`; UCE validates and adopts the single named listener
|
||||||
|
after exec. The listener therefore remains connectable and queues requests while
|
||||||
|
the service and its post-fork workers restart. Direct launches without systemd
|
||||||
|
activation retain the existing configured Unix/TCP listener behavior.
|
||||||
|
On termination the parent asks render workers to close their listeners, finish
|
||||||
|
accepted connections within the bounded worker drain interval, and only then
|
||||||
|
exits. This prevents an accepted FastCGI request from being reset at handoff;
|
||||||
|
the socket unit queues later connections for the replacement workers.
|
||||||
|
The graceful signal handler belongs to the parent and render workers. Generic
|
||||||
|
`task()` children restore default termination signals after fork so
|
||||||
|
`task_kill()` and `server_stop()` retain their immediate stop contract.
|
||||||
|
|
||||||
|
Epoch interruption measures uninterrupted guest CPU segments. The common
|
||||||
|
hostcall membrane re-arms the store deadline after every native call, excluding
|
||||||
|
blocking I/O, process waits, hashing, and other host work without weakening a
|
||||||
|
guest loop that makes no hostcalls. Keeping this at the membrane also covers new
|
||||||
|
hostcalls without per-import timeout bookkeeping.
|
||||||
|
|
||||||
`request_perf()` reports worker module-cache hits and misses and divides a miss
|
`request_perf()` reports worker module-cache hits and misses and divides a miss
|
||||||
into artifact lookup, wasm read, custom-section parse, serialized-module
|
into artifact lookup, wasm read, custom-section parse, serialized-module
|
||||||
deserialization or wasm compilation, and immutable import classification. This
|
deserialization or wasm compilation, and immutable import classification. This
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ set -e
|
|||||||
if command -v systemctl >/dev/null 2>&1; then
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
if [ "$1" = "configure" ]; then
|
if [ "$1" = "configure" ]; then
|
||||||
systemctl enable uce.service >/dev/null 2>&1 || true
|
systemctl enable uce.socket uce.service >/dev/null 2>&1 || true
|
||||||
|
systemctl start uce.socket >/dev/null 2>&1 || true
|
||||||
systemctl restart uce.service >/dev/null 2>&1 || true
|
systemctl restart uce.service >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ set -e
|
|||||||
|
|
||||||
if [ "$1" = "remove" ] || [ "$1" = "deconfigure" ]; then
|
if [ "$1" = "remove" ] || [ "$1" = "deconfigure" ]; then
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
systemctl disable --now uce.service >/dev/null 2>&1 || true
|
systemctl disable --now uce.service uce.socket >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=UCE FastCGI Runtime
|
Description=UCE FastCGI Runtime
|
||||||
After=network-online.target mariadb.service memcached.service
|
After=network-online.target mariadb.service memcached.service uce.socket
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
Requires=uce.socket
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
@@ -10,12 +11,10 @@ RuntimeDirectory=uce
|
|||||||
StateDirectory=uce
|
StateDirectory=uce
|
||||||
CacheDirectory=uce
|
CacheDirectory=uce
|
||||||
ExecStartPre=/usr/bin/mkdir -p /var/cache/uce/work /var/lib/uce/uploads /var/lib/uce/sessions
|
ExecStartPre=/usr/bin/mkdir -p /var/cache/uce/work /var/lib/uce/uploads /var/lib/uce/sessions
|
||||||
ExecStartPre=/usr/bin/rm -f /run/uce/fastcgi.sock
|
|
||||||
ExecStart=/usr/lib/uce/bin/uce_fastcgi.linux.bin
|
ExecStart=/usr/lib/uce/bin/uce_fastcgi.linux.bin
|
||||||
ExecStopPost=/usr/bin/rm -f /run/uce/fastcgi.sock
|
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=2
|
RestartSec=2
|
||||||
TimeoutStopSec=15
|
TimeoutStopSec=30
|
||||||
KillMode=mixed
|
KillMode=mixed
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
StandardError=journal
|
StandardError=journal
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=UCE FastCGI socket
|
||||||
|
|
||||||
|
[Socket]
|
||||||
|
ListenStream=/run/uce/fastcgi.sock
|
||||||
|
FileDescriptorName=fastcgi
|
||||||
|
SocketUser=www-data
|
||||||
|
SocketGroup=www-data
|
||||||
|
SocketMode=0660
|
||||||
|
DirectoryMode=0755
|
||||||
|
Backlog=100
|
||||||
|
Service=uce.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sockets.target
|
||||||
@@ -205,6 +205,7 @@ bundle_wasmtime "$STAGE_DIR"
|
|||||||
|
|
||||||
write_packaged_settings "$STAGE_DIR/etc/uce/settings.cfg" "$WEBROOT"
|
write_packaged_settings "$STAGE_DIR/etc/uce/settings.cfg" "$WEBROOT"
|
||||||
install -m 0644 "$DEB_ASSET_DIR/uce.service" "$STAGE_DIR/lib/systemd/system/uce.service"
|
install -m 0644 "$DEB_ASSET_DIR/uce.service" "$STAGE_DIR/lib/systemd/system/uce.service"
|
||||||
|
install -m 0644 "$DEB_ASSET_DIR/uce.socket" "$STAGE_DIR/lib/systemd/system/uce.socket"
|
||||||
install -m 0644 "$DEB_ASSET_DIR/conffiles" "$DEBIAN_DIR/conffiles"
|
install -m 0644 "$DEB_ASSET_DIR/conffiles" "$DEBIAN_DIR/conffiles"
|
||||||
install -m 0755 "$DEB_ASSET_DIR/postinst" "$DEBIAN_DIR/postinst"
|
install -m 0755 "$DEB_ASSET_DIR/postinst" "$DEBIAN_DIR/postinst"
|
||||||
install -m 0755 "$DEB_ASSET_DIR/prerm" "$DEBIAN_DIR/prerm"
|
install -m 0755 "$DEB_ASSET_DIR/prerm" "$DEBIAN_DIR/prerm"
|
||||||
|
|||||||
+5
-2
@@ -179,6 +179,7 @@ bundle_wasi_sdk "$STAGE_DIR"
|
|||||||
bundle_wasmtime "$STAGE_DIR"
|
bundle_wasmtime "$STAGE_DIR"
|
||||||
write_packaged_settings "$STAGE_DIR/etc/uce/settings.cfg" "$WEBROOT"
|
write_packaged_settings "$STAGE_DIR/etc/uce/settings.cfg" "$WEBROOT"
|
||||||
install -m 0644 "$REPO_ROOT/scripts/deb/uce.service" "$STAGE_DIR/usr/lib/systemd/system/uce.service"
|
install -m 0644 "$REPO_ROOT/scripts/deb/uce.service" "$STAGE_DIR/usr/lib/systemd/system/uce.service"
|
||||||
|
install -m 0644 "$REPO_ROOT/scripts/deb/uce.socket" "$STAGE_DIR/usr/lib/systemd/system/uce.socket"
|
||||||
|
|
||||||
(
|
(
|
||||||
cd "$STAGE_DIR"
|
cd "$STAGE_DIR"
|
||||||
@@ -225,13 +226,14 @@ cp -a %{_builddir}/$PACKAGE_NAME-$VERSION/. %{buildroot}/
|
|||||||
%post
|
%post
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
systemctl enable uce.service >/dev/null 2>&1 || true
|
systemctl enable uce.socket uce.service >/dev/null 2>&1 || true
|
||||||
|
systemctl start uce.socket >/dev/null 2>&1 || true
|
||||||
systemctl restart uce.service >/dev/null 2>&1 || true
|
systemctl restart uce.service >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
%preun
|
%preun
|
||||||
if [ "\$1" = "0" ] && command -v systemctl >/dev/null 2>&1; then
|
if [ "\$1" = "0" ] && command -v systemctl >/dev/null 2>&1; then
|
||||||
systemctl disable --now uce.service >/dev/null 2>&1 || true
|
systemctl disable --now uce.service uce.socket >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
%postun
|
%postun
|
||||||
@@ -245,6 +247,7 @@ fi
|
|||||||
%config(noreplace) /etc/uce/settings.cfg
|
%config(noreplace) /etc/uce/settings.cfg
|
||||||
/usr/lib/uce
|
/usr/lib/uce
|
||||||
/usr/lib/systemd/system/uce.service
|
/usr/lib/systemd/system/uce.service
|
||||||
|
/usr/lib/systemd/system/uce.socket
|
||||||
$WEBROOT
|
$WEBROOT
|
||||||
%dir /var/cache/uce
|
%dir /var/cache/uce
|
||||||
%dir /var/lib/uce
|
%dir /var/lib/uce
|
||||||
|
|||||||
@@ -73,4 +73,5 @@ if [[ "$action" == "run" ]]; then
|
|||||||
scripts/test_mysql_epoch_refresh.sh
|
scripts/test_mysql_epoch_refresh.sh
|
||||||
scripts/test_mysql_persistent_pool.sh
|
scripts/test_mysql_persistent_pool.sh
|
||||||
scripts/test_log_timeliness.sh
|
scripts/test_log_timeliness.sh
|
||||||
|
scripts/test_socket_activation.sh
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=UCE FastCGI Runtime
|
Description=UCE FastCGI Runtime
|
||||||
After=network-online.target mariadb.service memcached.service
|
After=network-online.target mariadb.service memcached.service uce.socket
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
Requires=uce.socket
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
@@ -10,13 +11,11 @@ RuntimeDirectory=uce
|
|||||||
StateDirectory=uce
|
StateDirectory=uce
|
||||||
CacheDirectory=uce
|
CacheDirectory=uce
|
||||||
ExecStartPre=/usr/bin/mkdir -p /var/cache/uce/work /var/lib/uce/uploads /var/lib/uce/sessions
|
ExecStartPre=/usr/bin/mkdir -p /var/cache/uce/work /var/lib/uce/uploads /var/lib/uce/sessions
|
||||||
ExecStartPre=/usr/bin/rm -f /run/uce/fastcgi.sock
|
|
||||||
ExecStartPre=/usr/bin/bash /Code/uce.openfu.com/uce/scripts/build_linux.sh
|
ExecStartPre=/usr/bin/bash /Code/uce.openfu.com/uce/scripts/build_linux.sh
|
||||||
ExecStart=/Code/uce.openfu.com/uce/bin/uce_fastcgi.linux.bin
|
ExecStart=/Code/uce.openfu.com/uce/bin/uce_fastcgi.linux.bin
|
||||||
ExecStopPost=/usr/bin/rm -f /run/uce/fastcgi.sock
|
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=2
|
RestartSec=2
|
||||||
TimeoutStopSec=15
|
TimeoutStopSec=30
|
||||||
KillMode=mixed
|
KillMode=mixed
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
StandardError=journal
|
StandardError=journal
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=UCE FastCGI socket
|
||||||
|
|
||||||
|
[Socket]
|
||||||
|
ListenStream=/run/uce/fastcgi.sock
|
||||||
|
FileDescriptorName=fastcgi
|
||||||
|
SocketUser=www-data
|
||||||
|
SocketGroup=www-data
|
||||||
|
SocketMode=0660
|
||||||
|
DirectoryMode=0755
|
||||||
|
Backlog=100
|
||||||
|
Service=uce.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sockets.target
|
||||||
Executable
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
service_name="${UCE_TEST_SERVICE:-uce.service}"
|
||||||
|
socket_name="${UCE_TEST_SOCKET_SERVICE:-uce.socket}"
|
||||||
|
http_host="${UCE_TEST_HTTP_HOST:-uce.openfu.com}"
|
||||||
|
http_path="${UCE_TEST_HTTP_PATH:-/info/}"
|
||||||
|
requests="${UCE_TEST_RESTART_REQUESTS:-100}"
|
||||||
|
statuses=$(mktemp)
|
||||||
|
trap 'rm -f "$statuses"' EXIT
|
||||||
|
|
||||||
|
systemctl is-active --quiet "$socket_name"
|
||||||
|
systemctl is-active --quiet "$service_name"
|
||||||
|
socket_path=$(systemctl show -p Listen --value "$socket_name" | sed -n 's/ \+(Stream)$//p')
|
||||||
|
[[ -S "$socket_path" ]]
|
||||||
|
inode_before=$(stat -c %i "$socket_path")
|
||||||
|
|
||||||
|
(
|
||||||
|
for ((i = 0; i < requests; i++)); do
|
||||||
|
curl -sS --max-time 15 -o /dev/null -w '%{http_code}\n' \
|
||||||
|
-H "Host: $http_host" "http://127.0.0.1${http_path}"
|
||||||
|
done
|
||||||
|
) >"$statuses" &
|
||||||
|
load_pid=$!
|
||||||
|
sleep 0.05
|
||||||
|
systemctl restart "$service_name"
|
||||||
|
wait "$load_pid"
|
||||||
|
|
||||||
|
inode_after=$(stat -c %i "$socket_path")
|
||||||
|
[[ "$inode_after" = "$inode_before" ]]
|
||||||
|
[[ $(wc -l <"$statuses") -eq "$requests" ]]
|
||||||
|
if [[ $(grep -c '^200$' "$statuses") -ne "$requests" ]]; then
|
||||||
|
sort "$statuses" | uniq -c >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
systemctl is-active --quiet "$socket_name"
|
||||||
|
systemctl is-active --quiet "$service_name"
|
||||||
|
echo "socket activation restart passed across $requests HTTP requests"
|
||||||
@@ -206,7 +206,6 @@ FastCGIServer::shutdown()
|
|||||||
{
|
{
|
||||||
printf("Closing server socket %i\n", *it);
|
printf("Closing server socket %i\n", *it);
|
||||||
close(*it);
|
close(*it);
|
||||||
sleep(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::vector<std::string>::iterator it = listen_unlink.begin();
|
for (std::vector<std::string>::iterator it = listen_unlink.begin();
|
||||||
@@ -223,7 +222,6 @@ FastCGIServer::shutdown()
|
|||||||
delete it->second;
|
delete it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(1);
|
|
||||||
server_sockets.clear();
|
server_sockets.clear();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -264,6 +262,22 @@ FastCGIServer::listen(unsigned tcp_port)
|
|||||||
return(listen(tcp_port, "0.0.0.0"));
|
return(listen(tcp_port, "0.0.0.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
FastCGIServer::adopt_listener(int socket_handle, char type)
|
||||||
|
{
|
||||||
|
int accepting = 0;
|
||||||
|
socklen_t accepting_size = sizeof(accepting);
|
||||||
|
if(socket_handle < 0 || getsockopt(socket_handle, SOL_SOCKET, SO_ACCEPTCONN,
|
||||||
|
&accepting, &accepting_size) != 0 || !accepting)
|
||||||
|
throw std::runtime_error("inherited descriptor is not a listening socket");
|
||||||
|
set_socket_nonblocking(socket_handle);
|
||||||
|
server_sockets.push_back(socket_handle);
|
||||||
|
server_socket_types[socket_handle] = type;
|
||||||
|
printf("(P) adopted #%i inherited %s listener\n", socket_handle,
|
||||||
|
type == 'F' ? "FastCGI" : "server");
|
||||||
|
return(socket_handle);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
FastCGIServer::listen(unsigned tcp_port, const std::string& bind_address)
|
FastCGIServer::listen(unsigned tcp_port, const std::string& bind_address)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ public:
|
|||||||
int listen_http(unsigned tcp_port, const std::string& bind_address);
|
int listen_http(unsigned tcp_port, const std::string& bind_address);
|
||||||
int listen_cli(const std::string& local_path);
|
int listen_cli(const std::string& local_path);
|
||||||
int listen(const std::string& local_path);
|
int listen(const std::string& local_path);
|
||||||
|
int adopt_listener(int socket_handle, char type = 'F');
|
||||||
|
|
||||||
void process(int timeout_ms = -1); // timeout_ms<0 blocks forever
|
void process(int timeout_ms = -1); // timeout_ms<0 blocks forever
|
||||||
void process_forever();
|
void process_forever();
|
||||||
|
|||||||
@@ -1549,6 +1549,12 @@ pid_t task(String key, std::function<void()> exec_after_spawn, u64 timeout)
|
|||||||
{
|
{
|
||||||
close_locked_file(lock_fd);
|
close_locked_file(lock_fd);
|
||||||
my_pid = getpid();
|
my_pid = getpid();
|
||||||
|
// The FastCGI worker handles termination to drain accepted requests.
|
||||||
|
// Generic task children do not run that drain loop, so inheriting those
|
||||||
|
// handlers would turn task_kill(SIGTERM) into a no-op.
|
||||||
|
signal(SIGTERM, SIG_DFL);
|
||||||
|
signal(SIGINT, SIG_DFL);
|
||||||
|
signal(SIGHUP, SIG_DFL);
|
||||||
signal(SIGALRM, SIG_DFL);
|
signal(SIGALRM, SIG_DFL);
|
||||||
if(timeout > 0)
|
if(timeout > 0)
|
||||||
alarm(timeout);
|
alarm(timeout);
|
||||||
|
|||||||
+57
-13
@@ -699,15 +699,7 @@ volatile bool termination_signal_received = false;
|
|||||||
|
|
||||||
void on_terminate(int sig)
|
void on_terminate(int sig)
|
||||||
{
|
{
|
||||||
if(termination_signal_received)
|
|
||||||
return;
|
|
||||||
termination_signal_received = true;
|
termination_signal_received = true;
|
||||||
if(getpid() != parent_pid)
|
|
||||||
exit(1);
|
|
||||||
printf("Terminating... PID %i:%i\n", getpid(), parent_pid);
|
|
||||||
wasm_backend_shutdown();
|
|
||||||
server.shutdown();
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear_shared_unit_cache(ServerState& state)
|
void clear_shared_unit_cache(ServerState& state)
|
||||||
@@ -1069,7 +1061,7 @@ void run_ws_broker()
|
|||||||
ws_broker.listen(server_state.config["WS_BROKER_SOCKET_PATH"]);
|
ws_broker.listen(server_state.config["WS_BROKER_SOCKET_PATH"]);
|
||||||
chmod(server_state.config["WS_BROKER_SOCKET_PATH"].c_str(), S_IRWXU | S_IRGRP | S_IWGRP);
|
chmod(server_state.config["WS_BROKER_SOCKET_PATH"].c_str(), S_IRWXU | S_IRGRP | S_IWGRP);
|
||||||
}
|
}
|
||||||
for(;;)
|
while(!termination_signal_received)
|
||||||
{
|
{
|
||||||
ws_broker.process(50);
|
ws_broker.process(50);
|
||||||
ws_broker_drain_outbound(ws_broker_outbound_timeout_seconds);
|
ws_broker_drain_outbound(ws_broker_outbound_timeout_seconds);
|
||||||
@@ -1254,7 +1246,7 @@ void run_proactive_compiler()
|
|||||||
}
|
}
|
||||||
next_scan_at = time_precise();
|
next_scan_at = time_precise();
|
||||||
|
|
||||||
for(;;)
|
while(!termination_signal_received)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1397,10 +1389,21 @@ void listen_for_connections()
|
|||||||
printf("(P) wasm worker ready: PID %i in %.3f ms\n", getpid(), wasm_ms);
|
printf("(P) wasm worker ready: PID %i in %.3f ms\n", getpid(), wasm_ms);
|
||||||
else
|
else
|
||||||
fprintf(stderr, "(!) wasm worker initialization failed: PID %i in %.3f ms: %s\n", getpid(), wasm_ms, wasm_error.c_str());
|
fprintf(stderr, "(!) wasm worker initialization failed: PID %i in %.3f ms: %s\n", getpid(), wasm_ms, wasm_error.c_str());
|
||||||
for(;;)
|
while(!termination_signal_received)
|
||||||
{
|
{
|
||||||
server.process(-1);
|
server.process(-1);
|
||||||
}
|
}
|
||||||
|
close_inherited_server_sockets();
|
||||||
|
f64 drain_deadline = time_precise() + (f64)to_u64(server_state.config["WORKER_DRAIN_TIMEOUT_SECONDS"], 10);
|
||||||
|
while(!server.client_sockets.empty() && time_precise() < drain_deadline)
|
||||||
|
server.process(100);
|
||||||
|
if(!server.client_sockets.empty())
|
||||||
|
fprintf(stderr, "(!) worker PID %i drain deadline reached with %zu client connections\n",
|
||||||
|
getpid(), server.client_sockets.size());
|
||||||
|
else
|
||||||
|
printf("(P) worker PID %i drained cleanly\n", getpid());
|
||||||
|
wasm_backend_shutdown();
|
||||||
|
exit(server.client_sockets.empty() ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
mode_t configured_socket_mode(String value, mode_t fallback)
|
mode_t configured_socket_mode(String value, mode_t fallback)
|
||||||
@@ -1424,6 +1427,25 @@ void chmod_configured_socket(String path, String mode_value, mode_t fallback)
|
|||||||
fprintf(stderr, "(!) Could not chmod socket %s to %04o: %s\n", path.c_str(), (unsigned int)mode, strerror(errno));
|
fprintf(stderr, "(!) Could not chmod socket %s to %04o: %s\n", path.c_str(), (unsigned int)mode, strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int systemd_fastcgi_listener()
|
||||||
|
{
|
||||||
|
const char* listen_pid = getenv("LISTEN_PID");
|
||||||
|
const char* listen_fds = getenv("LISTEN_FDS");
|
||||||
|
if(!listen_pid || !listen_fds)
|
||||||
|
return(-1);
|
||||||
|
if(to_u64(listen_pid, 0) != (u64)getpid())
|
||||||
|
return(-1);
|
||||||
|
if(to_u64(listen_fds, 0) != 1)
|
||||||
|
throw std::runtime_error("UCE requires exactly one systemd FastCGI listener");
|
||||||
|
const char* names = getenv("LISTEN_FDNAMES");
|
||||||
|
if(names && names[0] != '\0' && String(names) != "fastcgi")
|
||||||
|
throw std::runtime_error("systemd listener must be named fastcgi");
|
||||||
|
unsetenv("LISTEN_PID");
|
||||||
|
unsetenv("LISTEN_FDS");
|
||||||
|
unsetenv("LISTEN_FDNAMES");
|
||||||
|
return(3);
|
||||||
|
}
|
||||||
|
|
||||||
StringMap redacted_server_config_for_log(const StringMap& config)
|
StringMap redacted_server_config_for_log(const StringMap& config)
|
||||||
{
|
{
|
||||||
StringMap redacted = config;
|
StringMap redacted = config;
|
||||||
@@ -1443,13 +1465,16 @@ void init_base_process()
|
|||||||
server_state.config = make_server_settings();
|
server_state.config = make_server_settings();
|
||||||
server_state.config["COMPILER_SYS_PATH"] = cwd_get();
|
server_state.config["COMPILER_SYS_PATH"] = cwd_get();
|
||||||
printf("Compiler base path: %s\n", server_state.config["COMPILER_SYS_PATH"].c_str());
|
printf("Compiler base path: %s\n", server_state.config["COMPILER_SYS_PATH"].c_str());
|
||||||
|
int inherited_fastcgi = systemd_fastcgi_listener();
|
||||||
|
if(inherited_fastcgi >= 0)
|
||||||
|
server.adopt_listener(inherited_fastcgi, 'F');
|
||||||
|
|
||||||
if(server_state.config["FCGI_PORT"] != "")
|
if(server_state.config["FCGI_PORT"] != "")
|
||||||
server.listen(int_val(server_state.config["FCGI_PORT"]));
|
server.listen(int_val(server_state.config["FCGI_PORT"]));
|
||||||
|
|
||||||
printf("%s\n", var_dump(redacted_server_config_for_log(server_state.config)).c_str());
|
printf("%s\n", var_dump(redacted_server_config_for_log(server_state.config)).c_str());
|
||||||
|
|
||||||
if(server_state.config["FCGI_SOCKET_PATH"] != "")
|
if(inherited_fastcgi < 0 && server_state.config["FCGI_SOCKET_PATH"] != "")
|
||||||
{
|
{
|
||||||
server.listen(server_state.config["FCGI_SOCKET_PATH"]);
|
server.listen(server_state.config["FCGI_SOCKET_PATH"]);
|
||||||
chmod_configured_socket(server_state.config["FCGI_SOCKET_PATH"], server_state.config["FCGI_SOCKET_MODE"], 0666);
|
chmod_configured_socket(server_state.config["FCGI_SOCKET_PATH"], server_state.config["FCGI_SOCKET_MODE"], 0666);
|
||||||
@@ -1469,7 +1494,9 @@ void init_base_process()
|
|||||||
mkdir(server_state.config["SESSION_PATH"]);
|
mkdir(server_state.config["SESSION_PATH"]);
|
||||||
|
|
||||||
signal(SIGCHLD, on_child_exit);
|
signal(SIGCHLD, on_child_exit);
|
||||||
|
signal(SIGTERM, on_terminate);
|
||||||
signal(SIGINT, on_terminate);
|
signal(SIGINT, on_terminate);
|
||||||
|
signal(SIGHUP, on_terminate);
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
srand(time());
|
srand(time());
|
||||||
}
|
}
|
||||||
@@ -1489,7 +1516,7 @@ int main(int argc, char** argv)
|
|||||||
ensure_proactive_compiler();
|
ensure_proactive_compiler();
|
||||||
ensure_ws_broker();
|
ensure_ws_broker();
|
||||||
|
|
||||||
for(;;)
|
while(!termination_signal_received)
|
||||||
{
|
{
|
||||||
if(!proactive_compiler_alive())
|
if(!proactive_compiler_alive())
|
||||||
proactive_compiler_pid = 0;
|
proactive_compiler_pid = 0;
|
||||||
@@ -1512,5 +1539,22 @@ int main(int argc, char** argv)
|
|||||||
sleep(1);
|
sleep(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf("(P) draining %zu workers before shutdown\n", workers.size());
|
||||||
|
for(auto& worker : workers)
|
||||||
|
kill(worker.first, SIGTERM);
|
||||||
|
if(proactive_compiler_pid > 0)
|
||||||
|
kill(proactive_compiler_pid, SIGTERM);
|
||||||
|
if(ws_broker_pid > 0)
|
||||||
|
kill(ws_broker_pid, SIGTERM);
|
||||||
|
f64 drain_deadline = time_precise() + (f64)to_u64(server_state.config["WORKER_DRAIN_TIMEOUT_SECONDS"], 10);
|
||||||
|
while(!workers.empty() && time_precise() < drain_deadline)
|
||||||
|
{
|
||||||
|
on_child_exit(0);
|
||||||
|
usleep(10000);
|
||||||
|
}
|
||||||
|
for(auto& worker : workers)
|
||||||
|
kill(worker.first, SIGKILL);
|
||||||
|
server.shutdown();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-14
@@ -2106,6 +2106,11 @@ private:
|
|||||||
auto profiled = [self, callback, profile_name](Caller caller, Span<const Val> args, Span<Val> results) mutable -> Result<std::monostate, Trap> {
|
auto profiled = [self, callback, profile_name](Caller caller, Span<const Val> args, Span<Val> results) mutable -> Result<std::monostate, Trap> {
|
||||||
auto started = std::chrono::steady_clock::now();
|
auto started = std::chrono::steady_clock::now();
|
||||||
auto result = callback(caller, args, results);
|
auto result = callback(caller, args, results);
|
||||||
|
// Epoch interruption limits guest CPU, not time spent in native I/O,
|
||||||
|
// process management, hashing, or other host work. Re-arm at the one
|
||||||
|
// membrane every hostcall crosses so newly added blocking imports
|
||||||
|
// cannot silently consume the next guest segment's budget.
|
||||||
|
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
||||||
if(profile_name != "uce_host_request_perf")
|
if(profile_name != "uce_host_request_perf")
|
||||||
{
|
{
|
||||||
u64 elapsed = (u64)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - started).count();
|
u64 elapsed = (u64)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - started).count();
|
||||||
@@ -2288,9 +2293,9 @@ private:
|
|||||||
if(mod == "env" && name == "uce_host_crypto_equal")
|
if(mod == "env" && name == "uce_host_crypto_equal")
|
||||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String a,b; self->hostcall_read(args[0].i32(), args[1].i32(), a); self->hostcall_read(args[2].i32(), args[3].i32(), b); results[0]=Val((int32_t)(crypto_equal_native(a,b)?1:0)); return(std::monostate()); }));
|
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String a,b; self->hostcall_read(args[0].i32(), args[1].i32(), a); self->hostcall_read(args[2].i32(), args[3].i32(), b); results[0]=Val((int32_t)(crypto_equal_native(a,b)?1:0)); return(std::monostate()); }));
|
||||||
if(mod == "env" && name == "uce_host_password_hash")
|
if(mod == "env" && name == "uce_host_password_hash")
|
||||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String password; self->hostcall_read(args[0].i32(), args[1].i32(), password); String out=password_hash_native(password); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
|
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String password; self->hostcall_read(args[0].i32(), args[1].i32(), password); String out=password_hash_native(password); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
|
||||||
if(mod == "env" && name == "uce_host_password_verify")
|
if(mod == "env" && name == "uce_host_password_verify")
|
||||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String password,encoded; self->hostcall_read(args[0].i32(), args[1].i32(), password); self->hostcall_read(args[2].i32(), args[3].i32(), encoded); bool valid=password_verify_native(password,encoded); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0]=Val((int32_t)(valid?1:0)); return(std::monostate()); }));
|
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String password,encoded; self->hostcall_read(args[0].i32(), args[1].i32(), password); self->hostcall_read(args[2].i32(), args[3].i32(), encoded); bool valid=password_verify_native(password,encoded); results[0]=Val((int32_t)(valid?1:0)); return(std::monostate()); }));
|
||||||
if(mod == "env" && name == "uce_host_password_needs_rehash")
|
if(mod == "env" && name == "uce_host_password_needs_rehash")
|
||||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); results[0]=Val((int32_t)(password_needs_rehash_native(encoded)?1:0)); return(std::monostate()); }));
|
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); results[0]=Val((int32_t)(password_needs_rehash_native(encoded)?1:0)); return(std::monostate()); }));
|
||||||
if(mod == "env" && name == "uce_host_log")
|
if(mod == "env" && name == "uce_host_log")
|
||||||
@@ -2316,7 +2321,6 @@ private:
|
|||||||
}
|
}
|
||||||
if(buf != 0 && cap >= out.size())
|
if(buf != 0 && cap >= out.size())
|
||||||
self->hostcall_write(buf, out);
|
self->hostcall_write(buf, out);
|
||||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
|
||||||
results[0] = Val((int32_t)out.size());
|
results[0] = Val((int32_t)out.size());
|
||||||
return(std::monostate());
|
return(std::monostate());
|
||||||
}));
|
}));
|
||||||
@@ -2324,7 +2328,7 @@ private:
|
|||||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||||
String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); String out; String stage_key="http:"+encoded;
|
String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); String out; String stage_key="http:"+encoded;
|
||||||
if(!self->hostcall_staged(stage_key,out)) { DValue req,response; String err; if(ucb_decode(encoded,req,&err)) response=uce_http_request_value(req); else response["error"]="http_request decode failed: "+err; out=ucb_encode(response); if(buf==0) self->hostcall_stage(stage_key,out); }
|
if(!self->hostcall_staged(stage_key,out)) { DValue req,response; String err; if(ucb_decode(encoded,req,&err)) response=uce_http_request_value(req); else response["error"]="http_request decode failed: "+err; out=ucb_encode(response); if(buf==0) self->hostcall_stage(stage_key,out); }
|
||||||
if(buf&&cap>=out.size()) self->hostcall_write(buf,out); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0]=Val((int32_t)out.size()); return(std::monostate());
|
if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate());
|
||||||
}));
|
}));
|
||||||
if(mod == "env" && name == "uce_host_http_request_async")
|
if(mod == "env" && name == "uce_host_http_request_async")
|
||||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); DValue req; String err; u64 id=0; if(ucb_decode(encoded,req,&err)) id=uce_http_spawn_spec(req); results[0]=Val((int64_t)id); return(std::monostate()); }));
|
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); DValue req; String err; u64 id=0; if(ucb_decode(encoded,req,&err)) id=uce_http_spawn_spec(req); results[0]=Val((int64_t)id); return(std::monostate()); }));
|
||||||
@@ -2335,7 +2339,6 @@ private:
|
|||||||
String out; String stage_key="shell_dv:"+encoded;
|
String out; String stage_key="shell_dv:"+encoded;
|
||||||
if(!self->hostcall_staged(stage_key,out)) { DValue spec, response; String err; if(ucb_decode(encoded,spec,&err)) response=uce_shell_exec_spec(spec); else response["error"]="shell_exec spec decode failed: "+err; out=ucb_encode(response); if(buf==0) self->hostcall_stage(stage_key,out); }
|
if(!self->hostcall_staged(stage_key,out)) { DValue spec, response; String err; if(ucb_decode(encoded,spec,&err)) response=uce_shell_exec_spec(spec); else response["error"]="shell_exec spec decode failed: "+err; out=ucb_encode(response); if(buf==0) self->hostcall_stage(stage_key,out); }
|
||||||
if(buf&&cap>=out.size()) self->hostcall_write(buf,out);
|
if(buf&&cap>=out.size()) self->hostcall_write(buf,out);
|
||||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
|
||||||
results[0]=Val((int32_t)out.size()); return(std::monostate());
|
results[0]=Val((int32_t)out.size()); return(std::monostate());
|
||||||
}));
|
}));
|
||||||
if(mod == "env" && name == "uce_host_shell_spawn")
|
if(mod == "env" && name == "uce_host_shell_spawn")
|
||||||
@@ -2347,7 +2350,7 @@ private:
|
|||||||
if(mod == "env" && name == "uce_host_job_result")
|
if(mod == "env" && name == "uce_host_job_result")
|
||||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String out=ucb_encode(uce_job_result_value((u64)args[0].i64(), 100)); u32 cap=(u32)args[2].i32(); int32_t buf=args[1].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
|
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String out=ucb_encode(uce_job_result_value((u64)args[0].i64(), 100)); u32 cap=(u32)args[2].i32(); int32_t buf=args[1].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
|
||||||
if(mod == "env" && name == "uce_host_job_await")
|
if(mod == "env" && name == "uce_host_job_await")
|
||||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { u64 timeout=std::min<u64>((u64)args[1].i64(), 30000); String out=ucb_encode(uce_job_result_value((u64)args[0].i64(), timeout)); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
|
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { u64 timeout=std::min<u64>((u64)args[1].i64(), 30000); String out=ucb_encode(uce_job_result_value((u64)args[0].i64(), timeout)); u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32(); if(buf&&cap>=out.size()) self->hostcall_write(buf,out); results[0]=Val((int32_t)out.size()); return(std::monostate()); }));
|
||||||
if(mod == "env" && name == "uce_host_job_cancel")
|
if(mod == "env" && name == "uce_host_job_cancel")
|
||||||
return(add([](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { results[0]=Val((int32_t)(uce_job_cancel_value((u64)args[0].i64())?1:0)); return(std::monostate()); }));
|
return(add([](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { results[0]=Val((int32_t)(uce_job_cancel_value((u64)args[0].i64())?1:0)); return(std::monostate()); }));
|
||||||
if(mod == "env" && name == "uce_host_path_real")
|
if(mod == "env" && name == "uce_host_path_real")
|
||||||
@@ -2823,7 +2826,6 @@ private:
|
|||||||
}
|
}
|
||||||
if(buf != 0 && cap >= out.size())
|
if(buf != 0 && cap >= out.size())
|
||||||
self->hostcall_write(buf, out);
|
self->hostcall_write(buf, out);
|
||||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
|
||||||
results[0] = Val((int32_t)out.size());
|
results[0] = Val((int32_t)out.size());
|
||||||
return(std::monostate());
|
return(std::monostate());
|
||||||
}));
|
}));
|
||||||
@@ -2922,7 +2924,6 @@ private:
|
|||||||
}
|
}
|
||||||
if(buf != 0 && cap >= out.size())
|
if(buf != 0 && cap >= out.size())
|
||||||
self->hostcall_write(buf, out);
|
self->hostcall_write(buf, out);
|
||||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
|
||||||
results[0] = Val((int32_t)out.size());
|
results[0] = Val((int32_t)out.size());
|
||||||
return(std::monostate());
|
return(std::monostate());
|
||||||
}));
|
}));
|
||||||
@@ -3033,7 +3034,6 @@ private:
|
|||||||
}
|
}
|
||||||
if(buf != 0 && cap >= out.size())
|
if(buf != 0 && cap >= out.size())
|
||||||
self->hostcall_write(buf, out);
|
self->hostcall_write(buf, out);
|
||||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
|
||||||
results[0] = Val((int32_t)out.size());
|
results[0] = Val((int32_t)out.size());
|
||||||
return(std::monostate());
|
return(std::monostate());
|
||||||
}));
|
}));
|
||||||
@@ -3074,7 +3074,6 @@ private:
|
|||||||
context->resources.sockets.push_back(fd);
|
context->resources.sockets.push_back(fd);
|
||||||
}
|
}
|
||||||
results[0] = Val((int64_t)(fd > 0 ? fd : 0));
|
results[0] = Val((int64_t)(fd > 0 ? fd : 0));
|
||||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
|
||||||
return(std::monostate());
|
return(std::monostate());
|
||||||
}));
|
}));
|
||||||
if(mod == "env" && name == "uce_host_socket_close")
|
if(mod == "env" && name == "uce_host_socket_close")
|
||||||
@@ -3115,7 +3114,6 @@ private:
|
|||||||
}
|
}
|
||||||
if(buf != 0 && cap >= out.size())
|
if(buf != 0 && cap >= out.size())
|
||||||
self->hostcall_write(buf, out);
|
self->hostcall_write(buf, out);
|
||||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
|
||||||
results[0] = Val((int32_t)out.size());
|
results[0] = Val((int32_t)out.size());
|
||||||
return(std::monostate());
|
return(std::monostate());
|
||||||
}));
|
}));
|
||||||
@@ -3208,7 +3206,6 @@ private:
|
|||||||
unsigned int remaining = ::sleep((unsigned int)(usec / 1000000ull));
|
unsigned int remaining = ::sleep((unsigned int)(usec / 1000000ull));
|
||||||
if(remaining != 0)
|
if(remaining != 0)
|
||||||
{
|
{
|
||||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
|
||||||
results[0] = Val((int32_t)remaining);
|
results[0] = Val((int32_t)remaining);
|
||||||
return(std::monostate());
|
return(std::monostate());
|
||||||
}
|
}
|
||||||
@@ -3216,7 +3213,6 @@ private:
|
|||||||
}
|
}
|
||||||
if(usec > 0)
|
if(usec > 0)
|
||||||
::usleep((useconds_t)usec);
|
::usleep((useconds_t)usec);
|
||||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
|
||||||
results[0] = Val((int32_t)0);
|
results[0] = Val((int32_t)0);
|
||||||
return(std::monostate());
|
return(std::monostate());
|
||||||
}));
|
}));
|
||||||
@@ -3275,7 +3271,6 @@ private:
|
|||||||
self->hostcall_write(args[6].i32(), resolved);
|
self->hostcall_write(args[6].i32(), resolved);
|
||||||
}
|
}
|
||||||
results[0] = Val(slot);
|
results[0] = Val(slot);
|
||||||
caller.context().set_epoch_deadline(self->worker.cfg.epoch_deadline_ticks);
|
|
||||||
return(std::monostate());
|
return(std::monostate());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user