Enforce absolute Wasm invocation deadlines
This commit is contained in:
@@ -199,6 +199,7 @@ WASM_CORE_PATH=<UCE_REPO_ROOT>/bin/wasm/core.wasm
|
||||
WASM_MEMORY_LIMIT_BYTES=536870912
|
||||
WASM_EPOCH_DEADLINE_TICKS=200
|
||||
WASM_EPOCH_PERIOD_MS=50
|
||||
WASM_INVOCATION_TIMEOUT_MS=30000
|
||||
MYSQL_PERSISTENT_POOL_SIZE=8
|
||||
|
||||
WORKER_COUNT=4
|
||||
@@ -227,6 +228,16 @@ Important settings:
|
||||
- `WASM_COMPILE_SCRIPT` must point to `scripts/compile_wasm_unit` unless you provide an equivalent compiler. Relative paths are resolved from the runtime root/`COMPILER_SYS_PATH`. That script calls `scripts/check_unit_wasm.py` after linking each unit and uses the pinned WASI SDK on every deployment host.
|
||||
- `PROACTIVE_COMPILE_JOBS` selects 1–16 low-priority full-site scanner processes (default `2`). Each canonical unit path has one scanner owner. The separate higher-priority demand compiler remains reserved for stale units requested over HTTP, so total background compile concurrency can reach this value plus one.
|
||||
- `WASM_CORE_PATH` must point at the built `core.wasm` file.
|
||||
- `WASM_EPOCH_DEADLINE_TICKS` and `WASM_EPOCH_PERIOD_MS` bound one
|
||||
uninterrupted guest CPU segment. `WASM_INVOCATION_TIMEOUT_MS` is the
|
||||
separate absolute wall-clock bound for app-owned unit loading,
|
||||
initialization, the selected handler, and all nested component/unit calls.
|
||||
The three values must be positive integers; the ticker period is capped at
|
||||
`1000` ms and the invocation timeout at `86400000` ms. Invalid values prevent
|
||||
the Wasm backend from starting. The invocation timeout defaults to `30000` and is
|
||||
enforced to the epoch ticker's period resolution. Blocking host helpers
|
||||
retain their own shorter limits and are capped to the remaining invocation
|
||||
budget where the underlying operation is cancellable.
|
||||
|
||||
After editing settings, restart UCE:
|
||||
|
||||
|
||||
@@ -200,11 +200,16 @@ 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.
|
||||
Epoch interruption measures uninterrupted guest CPU segments. A separate
|
||||
absolute workspace invocation deadline starts before app-owned entry-unit
|
||||
loading and initialization and remains unchanged through the selected handler,
|
||||
ONCE, and every nested component/unit call. The common hostcall membrane checks
|
||||
that deadline before and after every native call and re-arms the store with the
|
||||
smaller of the remaining absolute budget and the CPU-segment budget. A cheap
|
||||
hostcall loop therefore cannot renew an invocation indefinitely. Blocking host
|
||||
helpers retain operation-specific limits and cap them to the remaining
|
||||
invocation budget where the underlying operation is cancellable. Forked task
|
||||
callbacks receive a fresh invocation deadline capped by the task lifetime.
|
||||
|
||||
`request_perf()` reports worker module-cache hits and misses and divides a miss
|
||||
into artifact lookup, wasm read, custom-section parse, serialized-module
|
||||
@@ -506,6 +511,9 @@ header free-functions are `inline`. The wasm backend exposes only declarations
|
||||
| Key | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `WASM_BACKEND_VERBOSE` | `0` | Emit `X-UCE-Wasm-*` workspace timing headers (benchmark only). |
|
||||
| `WASM_EPOCH_DEADLINE_TICKS` | `200` | Maximum uninterrupted guest CPU segment in epoch ticks; must be positive. |
|
||||
| `WASM_EPOCH_PERIOD_MS` | `50` | Worker epoch-ticker period and timeout resolution; range `1`–`1000` ms. |
|
||||
| `WASM_INVOCATION_TIMEOUT_MS` | `30000` | Absolute app-owned unit load/init/handler/nested-call deadline; range `1`–`86400000` ms and nested calls cannot renew it. |
|
||||
| `FCGI_SOCKET_PATH` | runtime-configured (`/run/uce/fastcgi.sock` in this doc) | Worker pool FastCGI socket (brokers forward here). |
|
||||
| `CLI_SOCKET_PATH` | `/run/uce/cli.sock` | Worker CLI/admin socket. Keep private; reference `CLI_SOCKET_MODE` is `0600`. |
|
||||
| `FCGI_SOCKET_MODE` | `0666` | Permission mode applied to `FCGI_SOCKET_PATH` after bind; set tighter if nginx/Apache can use a trusted group. |
|
||||
|
||||
@@ -48,6 +48,7 @@ WASM_CORE_PATH=bin/wasm/core.wasm
|
||||
WASM_MEMORY_LIMIT_BYTES=536870912
|
||||
WASM_EPOCH_DEADLINE_TICKS=200
|
||||
WASM_EPOCH_PERIOD_MS=50
|
||||
WASM_INVOCATION_TIMEOUT_MS=30000
|
||||
MYSQL_PERSISTENT_POOL_SIZE=8
|
||||
|
||||
# ENABLE THE BACKGROUND PROACTIVE COMPILER LOOP
|
||||
|
||||
@@ -58,7 +58,7 @@ if [[ "$action" == "list" ]]; then
|
||||
fi
|
||||
|
||||
if [[ "$action" == "run" ]]; then
|
||||
groups=(demo http site doc-gate security task-lifetime pool-isolation starter tcp)
|
||||
groups=(demo http site doc-gate-{1..30} security task-lifetime pool-isolation starter tcp)
|
||||
if [[ "$include_kill" == "1" ]]; then
|
||||
groups+=(wasm-kill)
|
||||
fi
|
||||
@@ -71,6 +71,7 @@ if [[ "$action" == "run" ]]; then
|
||||
scripts/test_parallel_precompile.sh
|
||||
timeout --signal=TERM --kill-after=5s 175s scripts/test_parallel_proactive_compile.sh
|
||||
scripts/test_cold_component_deadline.sh
|
||||
scripts/test_wasm_invocation_timeout.sh
|
||||
scripts/test_nested_component_props.sh
|
||||
scripts/test_component_once_prefetch.sh
|
||||
scripts/test_relative_component_cache.sh
|
||||
|
||||
Executable
+126
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
raw_calls=$(grep -En -- '(->|\.)call\(' src/wasm/worker.cpp)
|
||||
if [[ $(wc -l <<<"$raw_calls") -ne 1 || "$raw_calls" != *'return(func.call(context, args));'* ]]; then
|
||||
echo "raw Wasmtime call bypasses call_guest(): $raw_calls" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${1:-}" == "--static-only" ]]; then
|
||||
echo "wasm invocation timeout static call gate passed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
settings_file="${UCE_SETTINGS_FILE:-/etc/uce/settings.cfg}"
|
||||
site_directory="${UCE_TEST_SITE_DIRECTORY:-site}"
|
||||
socket_path="${UCE_CLI_SOCKET:-/run/uce/cli.sock}"
|
||||
bin_directory="${BIN_DIRECTORY:-/tmp/uce/work}"
|
||||
if [[ -r "$settings_file" ]]; then
|
||||
[[ -n "${UCE_TEST_SITE_DIRECTORY:-}" ]] || site_directory=$(awk -F= '/^[[:space:]]*SITE_DIRECTORY[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' "$settings_file")
|
||||
[[ -n "${UCE_CLI_SOCKET:-}" ]] || socket_path=$(awk -F= '/^[[:space:]]*CLI_SOCKET_PATH[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' "$settings_file")
|
||||
[[ -n "${BIN_DIRECTORY:-}" ]] || bin_directory=$(awk -F= '/^[[:space:]]*BIN_DIRECTORY[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' "$settings_file")
|
||||
invocation_ms=$(awk -F= '/^[[:space:]]*WASM_INVOCATION_TIMEOUT_MS[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' "$settings_file")
|
||||
fi
|
||||
site_directory="${site_directory:-site}"
|
||||
socket_path="${socket_path:-/run/uce/cli.sock}"
|
||||
bin_directory="${bin_directory:-/tmp/uce/work}"
|
||||
invocation_ms="${invocation_ms:-30000}"
|
||||
test_name="invocation-timeout-test-$$"
|
||||
source_dir="$site_directory/$test_name"
|
||||
pid_file="/tmp/uce-$test_name-worker"
|
||||
cache_dir=""
|
||||
body_file="/tmp/uce-$test_name-body"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$source_dir"
|
||||
[[ -z "$cache_dir" ]] || rm -rf "$cache_dir"
|
||||
rm -f "$pid_file" "$body_file"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
mkdir -p "$source_dir"
|
||||
cache_dir="$(scripts/unit_cache_directory "$bin_directory")$(realpath "$source_dir")"
|
||||
|
||||
printf '%s\n' \
|
||||
'CLI(Request& context) {' \
|
||||
' if(context.get["warm"] == "1") { print("warm"); return; }' \
|
||||
" file_put_contents(\"$pid_file\", std::to_string(request_perf()[\"worker_pid\"].to_u64()));" \
|
||||
' while(true) time_precise();' \
|
||||
'}' >"$source_dir/hostcall-loop.uce"
|
||||
printf '%s\n' \
|
||||
'CLI(Request& context) {' \
|
||||
' if(context.get["warm"] == "1") { print("warm"); return; }' \
|
||||
' if(context.get["quick"] == "1") { print(shell_exec("printf quick")); return; }' \
|
||||
' if(context.get["status"] == "1") { DValue spec; spec["cmd"] = "exit 7"; spec["timeout_ms"] = (f64)500; print(shell_exec(spec)["exit_code"].to_u64()); return; }' \
|
||||
' if(context.get["zero"] == "1") { DValue spec; spec["cmd"] = "printf zero"; spec["timeout_ms"] = (f64)0; print(shell_exec(spec)["stdout"].to_string()); return; }' \
|
||||
' if(context.get["job"] == "1") { DValue spec; spec["cmd"] = "sleep 2"; spec["timeout_ms"] = (f64)5000; u64 job = shell_spawn(spec); f64 started = time_precise(); job_await(job, 300); u64 elapsed = (u64)((time_precise() - started) * 1000); job_cancel(job); print(elapsed); return; }' \
|
||||
" file_put_contents(\"$pid_file\", std::to_string(request_perf()[\"worker_pid\"].to_u64()));" \
|
||||
' print(shell_exec("printf shell-start; sleep 60 & printf shell-end"));' \
|
||||
'}' >"$source_dir/legacy-shell.uce"
|
||||
printf '%s\n' \
|
||||
'CLI(Request& context) {' \
|
||||
' if(context.get["warm"] == "1") { print("warm"); return; }' \
|
||||
" file_put_contents(\"$pid_file\", std::to_string(request_perf()[\"worker_pid\"].to_u64()));" \
|
||||
' while(true) sleep(60);' \
|
||||
' print("sleep-end");' \
|
||||
'}' >"$source_dir/sleep.uce"
|
||||
printf '%s\n' \
|
||||
'CLI(Request& context) { print(request_perf()["worker_pid"].to_u64(), "|health"); }' >"$source_dir/health.uce"
|
||||
|
||||
max_seconds=$(( (invocation_ms + 15000) / 1000 ))
|
||||
(( max_seconds >= 10 )) || max_seconds=10
|
||||
|
||||
request() {
|
||||
local unit="$1"
|
||||
curl -sS --max-time "$max_seconds" -o "$body_file" -w '%{http_code}' --unix-socket "$socket_path" "http://localhost/$test_name/$unit"
|
||||
}
|
||||
|
||||
same_worker_health() {
|
||||
local expected_pid="$1"
|
||||
for _ in $(seq 1 32); do
|
||||
local health
|
||||
health=$(curl -sS --max-time 5 --fail-with-body --unix-socket "$socket_path" "http://localhost/$test_name/health.uce")
|
||||
[[ "$health" == "$expected_pid|health" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
curl -sS --max-time "$max_seconds" --fail-with-body --unix-socket "$socket_path" "http://localhost/$test_name/hostcall-loop.uce?warm=1" >/dev/null
|
||||
curl -sS --max-time "$max_seconds" --fail-with-body --unix-socket "$socket_path" "http://localhost/$test_name/legacy-shell.uce?warm=1" >/dev/null
|
||||
curl -sS --max-time "$max_seconds" --fail-with-body --unix-socket "$socket_path" "http://localhost/$test_name/sleep.uce?warm=1" >/dev/null
|
||||
curl -sS --max-time "$max_seconds" --fail-with-body --unix-socket "$socket_path" "http://localhost/$test_name/health.uce" >/dev/null
|
||||
quick=$(curl -sS --max-time 5 --fail-with-body --unix-socket "$socket_path" "http://localhost/$test_name/legacy-shell.uce?quick=1")
|
||||
[[ "$quick" == quick ]] || { echo "quick legacy shell returned: $quick" >&2; exit 1; }
|
||||
exit_status=$(curl -sS --max-time 5 --fail-with-body --unix-socket "$socket_path" "http://localhost/$test_name/legacy-shell.uce?status=1")
|
||||
[[ "$exit_status" == 7 ]] || { echo "structured shell lost exit status: $exit_status" >&2; exit 1; }
|
||||
zero_timeout=$(curl -sS --max-time 5 --fail-with-body --unix-socket "$socket_path" "http://localhost/$test_name/legacy-shell.uce?zero=1")
|
||||
[[ "$zero_timeout" == zero ]] || { echo "structured shell zero-timeout default changed: $zero_timeout" >&2; exit 1; }
|
||||
job_elapsed=$(curl -sS --max-time 5 --fail-with-body --unix-socket "$socket_path" "http://localhost/$test_name/legacy-shell.uce?job=1")
|
||||
[[ "$job_elapsed" =~ ^[0-9]+$ && "$job_elapsed" -ge 200 && "$job_elapsed" -lt 500 ]] || { echo "job_await two-call duration was ${job_elapsed}ms" >&2; exit 1; }
|
||||
|
||||
for unit in hostcall-loop.uce legacy-shell.uce sleep.uce; do
|
||||
rm -f "$pid_file"
|
||||
started_ns=$(date +%s%N)
|
||||
status=$(request "$unit")
|
||||
elapsed_ms=$(( ($(date +%s%N) - started_ns) / 1000000 ))
|
||||
[[ "$status" == "500" ]] || { echo "$unit returned HTTP $status" >&2; exit 1; }
|
||||
if [[ "$unit" == legacy-shell.uce ]]; then
|
||||
if (( invocation_ms <= 5000 )); then
|
||||
grep -q 'UCE_INVOCATION_TIMEOUT:' "$body_file" || { echo "$unit lacked invocation-timeout classification" >&2; exit 1; }
|
||||
expected_ms=$invocation_ms
|
||||
else
|
||||
grep -q 'UCE_HOSTCALL_TIMEOUT:' "$body_file" || { echo "$unit lacked hostcall-timeout classification" >&2; exit 1; }
|
||||
expected_ms=5000
|
||||
fi
|
||||
else
|
||||
grep -q 'UCE_INVOCATION_TIMEOUT:' "$body_file" || { echo "$unit lacked invocation-timeout classification" >&2; exit 1; }
|
||||
expected_ms=$invocation_ms
|
||||
fi
|
||||
(( elapsed_ms >= expected_ms - 1000 && elapsed_ms <= expected_ms + 3000 )) || { echo "$unit completed in ${elapsed_ms}ms; expected about ${expected_ms}ms" >&2; exit 1; }
|
||||
grep -q "$unit" "$body_file" || { echo "$unit lacked a source-mapped trace" >&2; exit 1; }
|
||||
[[ -s "$pid_file" ]] || { echo "$unit did not record its worker" >&2; exit 1; }
|
||||
worker_pid=$(<"$pid_file")
|
||||
same_worker_health "$worker_pid" || { echo "worker $worker_pid did not survive $unit" >&2; exit 1; }
|
||||
done
|
||||
|
||||
echo "wasm invocation timeout passed (absolute ${invocation_ms}ms; sleep and process hostcalls bounded)"
|
||||
@@ -197,7 +197,7 @@ bool cli_doc_example_is_placeholder(String source)
|
||||
return(false);
|
||||
}
|
||||
|
||||
void cli_run_doc_pages_gate()
|
||||
void cli_run_doc_pages_gate(u64 shard = 0, u64 shard_count = 1)
|
||||
{
|
||||
StringList error_markers;
|
||||
error_markers.push_back("doc example error");
|
||||
@@ -207,12 +207,15 @@ void cli_run_doc_pages_gate()
|
||||
error_markers.push_back(String("uncaught exception during ") + "request");
|
||||
error_markers.push_back("timed out acquiring compile lock");
|
||||
|
||||
for(String file_name : ls("../doc/pages/"))
|
||||
u64 page_index = 0;
|
||||
for(String file_name : ls("../doc/pages/").sort())
|
||||
{
|
||||
String source = file_get_contents("../doc/pages/" + file_name);
|
||||
String page = nibble(file_name, ".");
|
||||
if(page == "")
|
||||
continue;
|
||||
if(page_index++ % shard_count != shard)
|
||||
continue;
|
||||
bool has_example = cli_doc_source_has_example(source);
|
||||
String path = "/doc/index.uce?p=" + uri_encode(page);
|
||||
CliHttpResponse res;
|
||||
@@ -465,7 +468,7 @@ void cli_run_wasm_kill(bool include_kill)
|
||||
void cli_print_list(bool include_kill)
|
||||
{
|
||||
print("UCE CLI test groups:\n");
|
||||
print(" demo, http, site, doc-gate, security, task-lifetime, pool-isolation, starter, tcp");
|
||||
print(" demo, http, site, doc-gate-1..30, security, task-lifetime, pool-isolation, starter, tcp");
|
||||
if(include_kill)
|
||||
print(", wasm-kill");
|
||||
print("\n");
|
||||
@@ -480,8 +483,16 @@ bool cli_run_group(String group, bool skip_local_service_pages, bool include_kil
|
||||
cli_run_http_smoke();
|
||||
else if(group == "site")
|
||||
cli_run_site_suite(skip_local_service_pages);
|
||||
else if(group == "doc-gate")
|
||||
cli_run_doc_pages_gate();
|
||||
else if(str_starts_with(group, "doc-gate-"))
|
||||
{
|
||||
u64 shard = int_val(group.substr(9));
|
||||
if(shard < 1 || shard > 30)
|
||||
{
|
||||
cli_test_case("uce_cli_runner:known doc gate shard", false, "unknown group: " + group);
|
||||
return(false);
|
||||
}
|
||||
cli_run_doc_pages_gate(shard - 1, 30);
|
||||
}
|
||||
else if(group == "security")
|
||||
cli_run_security_smoke();
|
||||
else if(group == "task-lifetime")
|
||||
@@ -526,7 +537,11 @@ CLI(Request& context)
|
||||
cli_run_group(group, input["skip_local_service_pages"].to_bool(), include_kill);
|
||||
else
|
||||
{
|
||||
for(String item : { "demo", "http", "site", "doc-gate", "security", "task-lifetime", "pool-isolation", "starter", "tcp" })
|
||||
for(String item : { "demo", "http", "site" })
|
||||
cli_run_group(item, input["skip_local_service_pages"].to_bool(), include_kill);
|
||||
for(u64 shard = 1; shard <= 30; shard++)
|
||||
cli_run_group("doc-gate-" + std::to_string(shard), input["skip_local_service_pages"].to_bool(), include_kill);
|
||||
for(String item : { "security", "task-lifetime", "pool-isolation", "starter", "tcp" })
|
||||
cli_run_group(item, input["skip_local_service_pages"].to_bool(), include_kill);
|
||||
cli_run_group("wasm-kill", input["skip_local_service_pages"].to_bool(), include_kill);
|
||||
}
|
||||
|
||||
@@ -1396,6 +1396,48 @@ std::map<pid_t, Worker> workers;
|
||||
#include <sys/resource.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
// The process-wide reaper may run on a runtime thread while a request thread
|
||||
// synchronously owns a child. Preserve unknown statuses until that owner takes
|
||||
// them instead of forcing it to guess success after waitpid reports ECHILD.
|
||||
static const size_t child_exit_status_capacity = 256;
|
||||
static volatile sig_atomic_t child_exit_status_cursor = 0;
|
||||
static volatile sig_atomic_t child_exit_status_pids[child_exit_status_capacity] = {0};
|
||||
static volatile sig_atomic_t child_exit_status_values[child_exit_status_capacity] = {0};
|
||||
static volatile sig_atomic_t child_exit_status_sequences[child_exit_status_capacity] = {0};
|
||||
|
||||
static void child_exit_status_publish(pid_t pid, int status)
|
||||
{
|
||||
sig_atomic_t cursor = __atomic_fetch_add(&child_exit_status_cursor, 1, __ATOMIC_RELAXED);
|
||||
size_t slot = (size_t)(unsigned int)cursor % child_exit_status_capacity;
|
||||
__atomic_store_n(&child_exit_status_pids[slot], 0, __ATOMIC_RELEASE);
|
||||
__atomic_store_n(&child_exit_status_values[slot], (sig_atomic_t)status, __ATOMIC_RELAXED);
|
||||
__atomic_store_n(&child_exit_status_sequences[slot], cursor + 1, __ATOMIC_RELAXED);
|
||||
__atomic_store_n(&child_exit_status_pids[slot], (sig_atomic_t)pid, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
unsigned int child_exit_status_snapshot()
|
||||
{
|
||||
return((unsigned int)__atomic_load_n(&child_exit_status_cursor, __ATOMIC_ACQUIRE));
|
||||
}
|
||||
|
||||
bool child_exit_status_take(pid_t pid, int& status, unsigned int since)
|
||||
{
|
||||
for(size_t slot = 0; slot < child_exit_status_capacity; slot++)
|
||||
{
|
||||
unsigned int sequence = (unsigned int)__atomic_load_n(&child_exit_status_sequences[slot], __ATOMIC_ACQUIRE);
|
||||
unsigned int distance = sequence - since;
|
||||
if(sequence == 0 || distance == 0 || distance >= 0x80000000u)
|
||||
continue;
|
||||
sig_atomic_t expected = (sig_atomic_t)pid;
|
||||
if(__atomic_compare_exchange_n(&child_exit_status_pids[slot], &expected, 0, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
|
||||
{
|
||||
status = (int)__atomic_load_n(&child_exit_status_values[slot], __ATOMIC_RELAXED);
|
||||
return(true);
|
||||
}
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
pid_t spawn_subprocess(std::function<void()> exec_after_spawn)
|
||||
{
|
||||
parent_pid = getpid();
|
||||
@@ -1648,6 +1690,7 @@ void on_child_exit(int sig)
|
||||
}
|
||||
else
|
||||
{
|
||||
child_exit_status_publish(pid, status);
|
||||
printf("(P) task child reaped (PID:%i)\n", pid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ s32 usleep(u32 usec);
|
||||
#else
|
||||
#include <sys/file.h>
|
||||
#include <signal.h>
|
||||
unsigned int child_exit_status_snapshot();
|
||||
bool child_exit_status_take(pid_t pid, int& status, unsigned int since);
|
||||
#endif
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
|
||||
+22
-2
@@ -50,7 +50,24 @@ static String wasm_backend_ensure_started(Request* context)
|
||||
if(cfg[key] != "")
|
||||
wc.write_roots.push_back(cfg[key]);
|
||||
wc.memory_limit = (int64_t)to_u64(cfg["WASM_MEMORY_LIMIT_BYTES"], 512ull * 1024 * 1024);
|
||||
wc.epoch_deadline_ticks = to_u64(cfg["WASM_EPOCH_DEADLINE_TICKS"], 200);
|
||||
wc.epoch_deadline_ticks = to_u64(first(cfg["WASM_EPOCH_DEADLINE_TICKS"], "200"), 0);
|
||||
wc.epoch_period_ms = to_u64(first(cfg["WASM_EPOCH_PERIOD_MS"], "50"), 0);
|
||||
wc.invocation_timeout_ms = to_u64(first(cfg["WASM_INVOCATION_TIMEOUT_MS"], "30000"), 0);
|
||||
if(wc.epoch_deadline_ticks == 0 || wc.epoch_deadline_ticks > (u64)INT64_MAX)
|
||||
{
|
||||
g_wasm_init_error = "WASM_EPOCH_DEADLINE_TICKS must be a positive integer no greater than INT64_MAX";
|
||||
return(g_wasm_init_error);
|
||||
}
|
||||
if(wc.epoch_period_ms == 0 || wc.epoch_period_ms > 1000)
|
||||
{
|
||||
g_wasm_init_error = "WASM_EPOCH_PERIOD_MS must be a positive integer no greater than 1000";
|
||||
return(g_wasm_init_error);
|
||||
}
|
||||
if(wc.invocation_timeout_ms == 0 || wc.invocation_timeout_ms > 86400000)
|
||||
{
|
||||
g_wasm_init_error = "WASM_INVOCATION_TIMEOUT_MS must be a positive integer no greater than 86400000";
|
||||
return(g_wasm_init_error);
|
||||
}
|
||||
wc.mysql_persistent_pool_size = std::min<u64>(to_u64(cfg["MYSQL_PERSISTENT_POOL_SIZE"], 8), 64);
|
||||
wc.profile_hostcall_cpu = to_bool(cfg["WASM_PROFILE_HOSTCALL_CPU"], false);
|
||||
wc.profile_thread_runtime = to_bool(cfg["WASM_PROFILE_THREAD_RUNTIME"], false);
|
||||
@@ -84,7 +101,9 @@ static String wasm_backend_ensure_started(Request* context)
|
||||
|
||||
g_wasm_epoch_running.store(true);
|
||||
WasmWorker* worker = g_wasm_worker;
|
||||
u64 period_ms = to_u64(cfg["WASM_EPOCH_PERIOD_MS"], 50);
|
||||
u64 period_ms = wc.epoch_period_ms;
|
||||
// The ticker inherits this block so it cannot run the process-wide child reaper.
|
||||
WasmSigchldBlock sigchld;
|
||||
g_wasm_epoch_ticker = new std::thread([worker, period_ms] {
|
||||
while(g_wasm_epoch_running.load())
|
||||
{
|
||||
@@ -92,6 +111,7 @@ static String wasm_backend_ensure_started(Request* context)
|
||||
worker->engine.increment_epoch();
|
||||
}
|
||||
});
|
||||
sigchld.restore();
|
||||
return("");
|
||||
}
|
||||
|
||||
|
||||
+350
-78
@@ -148,6 +148,8 @@ struct WasmWorkerConfig
|
||||
int64_t memory_limit = 512ll * 1024 * 1024;
|
||||
u32 table_headroom = 4096;
|
||||
u64 epoch_deadline_ticks = 200; // ticker period × ticks = CPU budget
|
||||
u64 epoch_period_ms = 50;
|
||||
u64 invocation_timeout_ms = 30000;
|
||||
u64 mysql_persistent_pool_size = 8;
|
||||
bool profile_hostcall_cpu = false;
|
||||
bool profile_thread_runtime = false;
|
||||
@@ -291,6 +293,130 @@ static u64 wasm_monotonic_ms()
|
||||
return((u64)ts.tv_sec * 1000ull + (u64)ts.tv_nsec / 1000000ull);
|
||||
}
|
||||
|
||||
class WasmSigchldBlock
|
||||
{
|
||||
sigset_t previous;
|
||||
bool blocked = false;
|
||||
public:
|
||||
WasmSigchldBlock()
|
||||
{
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGCHLD);
|
||||
blocked = pthread_sigmask(SIG_BLOCK, &mask, &previous) == 0;
|
||||
}
|
||||
void restore()
|
||||
{
|
||||
if(blocked)
|
||||
{
|
||||
pthread_sigmask(SIG_SETMASK, &previous, 0);
|
||||
blocked = false;
|
||||
}
|
||||
}
|
||||
~WasmSigchldBlock() { restore(); }
|
||||
};
|
||||
|
||||
static u64 wasm_deadline_after_ms(u64 timeout_ms)
|
||||
{
|
||||
u64 now = wasm_monotonic_ms();
|
||||
return(timeout_ms > UINT64_MAX - now ? UINT64_MAX : now + timeout_ms);
|
||||
}
|
||||
|
||||
static bool wasm_socket_wait(int fd, short events, u64 deadline)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
u64 now = wasm_monotonic_ms();
|
||||
if(now >= deadline)
|
||||
return(false);
|
||||
u64 remaining_ms = deadline - now;
|
||||
struct pollfd item = { fd, events, 0 };
|
||||
int rc = poll(&item, 1, (int)std::min<u64>(INT_MAX, remaining_ms));
|
||||
if(rc > 0)
|
||||
return((item.revents & (events | POLLERR | POLLHUP)) != 0);
|
||||
if(rc == 0)
|
||||
return(false);
|
||||
if(errno != EINTR)
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
|
||||
static u64 wasm_socket_connect_bounded(const String& host, u16 port, u64 timeout_ms)
|
||||
{
|
||||
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if(fd < 0)
|
||||
return(0);
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if(flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0)
|
||||
{
|
||||
close(fd);
|
||||
return(0);
|
||||
}
|
||||
struct sockaddr_in address = {0};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_port = htons(port);
|
||||
if(inet_pton(AF_INET, host.c_str(), &address.sin_addr) != 1)
|
||||
{
|
||||
close(fd);
|
||||
return(0);
|
||||
}
|
||||
int rc = connect(fd, (struct sockaddr*)&address, sizeof(address));
|
||||
if(rc != 0 && errno == EINPROGRESS && wasm_socket_wait(fd, POLLOUT, wasm_deadline_after_ms(timeout_ms)))
|
||||
{
|
||||
int error = 0;
|
||||
socklen_t error_size = sizeof(error);
|
||||
rc = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &error_size) == 0 && error == 0 ? 0 : -1;
|
||||
}
|
||||
if(rc != 0)
|
||||
{
|
||||
close(fd);
|
||||
return(0);
|
||||
}
|
||||
if(fcntl(fd, F_SETFL, flags) != 0)
|
||||
{
|
||||
close(fd);
|
||||
return(0);
|
||||
}
|
||||
if(fd == 0)
|
||||
{
|
||||
int moved = dup(fd);
|
||||
close(fd);
|
||||
fd = moved;
|
||||
}
|
||||
if(fd <= 0)
|
||||
return(0);
|
||||
if(context)
|
||||
context->resources.sockets.push_back(fd);
|
||||
return((u64)fd);
|
||||
}
|
||||
|
||||
static bool wasm_socket_write_bounded(u64 socket_fd, const String& data, u64 timeout_ms)
|
||||
{
|
||||
int fd = (int)socket_fd;
|
||||
u64 deadline = wasm_deadline_after_ms(timeout_ms);
|
||||
size_t offset = 0;
|
||||
while(offset < data.size())
|
||||
{
|
||||
if(!wasm_socket_wait(fd, POLLOUT, deadline))
|
||||
return(false);
|
||||
ssize_t written = send(fd, data.data() + offset, data.size() - offset, MSG_DONTWAIT | MSG_NOSIGNAL);
|
||||
if(written > 0)
|
||||
offset += (size_t)written;
|
||||
else if(written < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
return(false);
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
static String wasm_socket_read_bounded(u64 socket_fd, u32 max_length, u64 timeout_ms)
|
||||
{
|
||||
if(max_length == 0 || !wasm_socket_wait((int)socket_fd, POLLIN, wasm_deadline_after_ms(timeout_ms)))
|
||||
return("");
|
||||
std::vector<char> buffer(max_length);
|
||||
ssize_t count = recv((int)socket_fd, buffer.data(), buffer.size(), MSG_DONTWAIT);
|
||||
return(count > 0 ? String(buffer.data(), (size_t)count) : String(""));
|
||||
}
|
||||
|
||||
static f64 wasm_thread_cpu_time()
|
||||
{
|
||||
struct timespec ts;
|
||||
@@ -426,30 +552,50 @@ static DValue uce_process_exec(String cmd, String input, StringMap env, u64 time
|
||||
if(timeout_ms == 0) timeout_ms = 5000;
|
||||
int inpipe[2], outpipe[2], errpipe[2];
|
||||
if(pipe(inpipe) || pipe(outpipe) || pipe(errpipe)) { r["stderr"]="pipe failed"; return(r); }
|
||||
// The process-wide handler reaps background children; this caller owns this child's status.
|
||||
WasmSigchldBlock sigchld;
|
||||
unsigned int child_status_snapshot = child_exit_status_snapshot();
|
||||
pid_t pid = fork();
|
||||
if(pid < 0)
|
||||
{
|
||||
close(inpipe[0]); close(inpipe[1]); close(outpipe[0]); close(outpipe[1]); close(errpipe[0]); close(errpipe[1]);
|
||||
r["stderr"] = "fork failed";
|
||||
return(r);
|
||||
}
|
||||
if(pid == 0)
|
||||
{
|
||||
sigchld.restore();
|
||||
setpgid(0, 0);
|
||||
dup2(inpipe[0], 0); dup2(outpipe[1], 1); dup2(errpipe[1], 2);
|
||||
close(inpipe[0]); close(inpipe[1]); close(outpipe[0]); close(outpipe[1]); close(errpipe[0]); close(errpipe[1]);
|
||||
for(auto& kv : env) setenv(kv.first.c_str(), kv.second.c_str(), 1);
|
||||
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
|
||||
_exit(127);
|
||||
}
|
||||
setpgid(pid, pid);
|
||||
close(inpipe[0]); close(outpipe[1]); close(errpipe[1]);
|
||||
fcntl(inpipe[1], F_SETFL, fcntl(inpipe[1], F_GETFL, 0) | O_NONBLOCK);
|
||||
fcntl(outpipe[0], F_SETFL, fcntl(outpipe[0], F_GETFL, 0) | O_NONBLOCK);
|
||||
fcntl(errpipe[0], F_SETFL, fcntl(errpipe[0], F_GETFL, 0) | O_NONBLOCK);
|
||||
size_t input_off = 0; bool in_open = true, out_open = true, err_open = true; int status = 0; bool exited = false;
|
||||
size_t input_off = 0; bool in_open = true, out_open = true, err_open = true; int status = 0; bool exited = false, status_valid = false;
|
||||
u64 deadline = wasm_monotonic_ms() + timeout_ms;
|
||||
while(out_open || err_open || !exited)
|
||||
{
|
||||
if(!exited)
|
||||
{
|
||||
pid_t w = waitpid(pid, &status, WNOHANG);
|
||||
if(w == pid) exited = true;
|
||||
if(w == pid) { exited = true; status_valid = true; }
|
||||
else if(w < 0 && errno == ECHILD)
|
||||
{
|
||||
u64 transfer_deadline = wasm_deadline_after_ms(50);
|
||||
do { status_valid = child_exit_status_take(pid, status, child_status_snapshot); if(!status_valid) sched_yield(); } while(!status_valid && wasm_monotonic_ms() < transfer_deadline);
|
||||
exited = true;
|
||||
if(!status_valid) r["stderr"] = r["stderr"].to_string() + "lost child exit status";
|
||||
}
|
||||
}
|
||||
if(in_open)
|
||||
{
|
||||
if(input_off < input.size()) { ssize_t n=write(inpipe[1], input.data()+input_off, input.size()-input_off); if(n>0) input_off += (size_t)n; else if(n<0 && errno!=EINTR) { close(inpipe[1]); in_open=false; } }
|
||||
if(input_off < input.size()) { ssize_t n=write(inpipe[1], input.data()+input_off, input.size()-input_off); if(n>0) input_off += (size_t)n; else if(n<0 && errno!=EINTR && errno!=EAGAIN && errno!=EWOULDBLOCK) { close(inpipe[1]); in_open=false; } }
|
||||
else { close(inpipe[1]); in_open=false; }
|
||||
}
|
||||
char buf[4096];
|
||||
@@ -458,17 +604,22 @@ static DValue uce_process_exec(String cmd, String input, StringMap env, u64 time
|
||||
if(n == 0 && out_open) { close(outpipe[0]); out_open=false; }
|
||||
while((n=read(errpipe[0], buf, sizeof(buf))) > 0) r["stderr"] = r["stderr"].to_string() + String(buf, n);
|
||||
if(n == 0 && err_open) { close(errpipe[0]); err_open=false; }
|
||||
if(!exited && wasm_monotonic_ms() >= deadline)
|
||||
if((out_open || err_open || !exited) && wasm_monotonic_ms() >= deadline)
|
||||
{
|
||||
r["timed_out"].set_bool(true);
|
||||
kill(-pid, SIGKILL);
|
||||
kill(pid, SIGKILL);
|
||||
waitpid(pid, &status, 0);
|
||||
if(!exited)
|
||||
status_valid = waitpid(pid, &status, 0) == pid;
|
||||
exited = true;
|
||||
if(in_open) { close(inpipe[1]); in_open=false; }
|
||||
if(out_open) { close(outpipe[0]); out_open=false; }
|
||||
if(err_open) { close(errpipe[0]); err_open=false; }
|
||||
}
|
||||
if((out_open || err_open || !exited)) usleep(10000);
|
||||
}
|
||||
if(WIFEXITED(status)) { r["exit_code"] = (f64)WEXITSTATUS(status); r["timed_out"].set_bool(false); }
|
||||
else if(WIFSIGNALED(status)) r["exit_code"] = (f64)(128 + WTERMSIG(status));
|
||||
if(status_valid && WIFEXITED(status)) r["exit_code"] = (f64)WEXITSTATUS(status);
|
||||
else if(status_valid && WIFSIGNALED(status)) r["exit_code"] = (f64)(128 + WTERMSIG(status));
|
||||
return(r);
|
||||
}
|
||||
|
||||
@@ -514,27 +665,38 @@ static DValue uce_exec_argv_capture(std::vector<String> argv, String input, u64
|
||||
if(timeout_ms == 0) timeout_ms = 5000;
|
||||
int inpipe[2], outpipe[2], errpipe[2];
|
||||
if(pipe(inpipe)||pipe(outpipe)||pipe(errpipe)) { r["stderr"]="pipe failed"; return(r); }
|
||||
WasmSigchldBlock sigchld;
|
||||
unsigned int child_status_snapshot=child_exit_status_snapshot();
|
||||
pid_t pid=fork();
|
||||
if(pid==0)
|
||||
{
|
||||
sigchld.restore();
|
||||
setpgid(0,0);
|
||||
dup2(inpipe[0],0); dup2(outpipe[1],1); dup2(errpipe[1],2);
|
||||
close(inpipe[0]); close(inpipe[1]); close(outpipe[0]); close(outpipe[1]); close(errpipe[0]); close(errpipe[1]);
|
||||
std::vector<char*> args; for(auto& a: argv) args.push_back((char*)a.c_str()); args.push_back(0);
|
||||
execvp(args[0], args.data()); _exit(127);
|
||||
}
|
||||
if(pid < 0)
|
||||
{
|
||||
close(inpipe[0]); close(inpipe[1]); close(outpipe[0]); close(outpipe[1]); close(errpipe[0]); close(errpipe[1]);
|
||||
r["stderr"] = "fork failed";
|
||||
return(r);
|
||||
}
|
||||
setpgid(pid,pid);
|
||||
close(inpipe[0]); close(outpipe[1]); close(errpipe[1]);
|
||||
fcntl(outpipe[0], F_SETFL, fcntl(outpipe[0], F_GETFL, 0)|O_NONBLOCK); fcntl(errpipe[0], F_SETFL, fcntl(errpipe[0], F_GETFL, 0)|O_NONBLOCK);
|
||||
size_t input_off=0; bool in_open=true,out_open=true,err_open=true,exited=false; int status=0; u64 deadline=wasm_monotonic_ms()+timeout_ms;
|
||||
fcntl(inpipe[1], F_SETFL, fcntl(inpipe[1], F_GETFL, 0)|O_NONBLOCK); fcntl(outpipe[0], F_SETFL, fcntl(outpipe[0], F_GETFL, 0)|O_NONBLOCK); fcntl(errpipe[0], F_SETFL, fcntl(errpipe[0], F_GETFL, 0)|O_NONBLOCK);
|
||||
size_t input_off=0; bool in_open=true,out_open=true,err_open=true,exited=false,status_valid=false; int status=0; u64 deadline=wasm_monotonic_ms()+timeout_ms;
|
||||
while(out_open || err_open || !exited)
|
||||
{
|
||||
if(!exited) { pid_t w=waitpid(pid,&status,WNOHANG); if(w==pid) exited=true; }
|
||||
if(in_open) { if(input_off<input.size()) { ssize_t n=write(inpipe[1], input.data()+input_off, input.size()-input_off); if(n>0) input_off+=(size_t)n; else if(n<0 && errno!=EINTR) { close(inpipe[1]); in_open=false; } } else { close(inpipe[1]); in_open=false; } }
|
||||
if(!exited) { pid_t w=waitpid(pid,&status,WNOHANG); if(w==pid) { exited=true; status_valid=true; } else if(w<0&&errno==ECHILD) { u64 transfer_deadline=wasm_deadline_after_ms(50); do { status_valid=child_exit_status_take(pid,status,child_status_snapshot); if(!status_valid)sched_yield(); } while(!status_valid&&wasm_monotonic_ms()<transfer_deadline); exited=true; if(!status_valid)r["stderr"]=r["stderr"].to_string()+"lost child exit status"; } }
|
||||
if(in_open) { if(input_off<input.size()) { ssize_t n=write(inpipe[1], input.data()+input_off, input.size()-input_off); if(n>0) input_off+=(size_t)n; else if(n<0 && errno!=EINTR && errno!=EAGAIN && errno!=EWOULDBLOCK) { close(inpipe[1]); in_open=false; } } else { close(inpipe[1]); in_open=false; } }
|
||||
char buf[4096]; ssize_t n; while((n=read(outpipe[0],buf,sizeof(buf)))>0) r["stdout"] = r["stdout"].to_string()+String(buf,n); if(n==0&&out_open){close(outpipe[0]);out_open=false;}
|
||||
while((n=read(errpipe[0],buf,sizeof(buf)))>0) r["stderr"] = r["stderr"].to_string()+String(buf,n); if(n==0&&err_open){close(errpipe[0]);err_open=false;}
|
||||
if(!exited && wasm_monotonic_ms() >= deadline) { r["timed_out"].set_bool(true); kill(pid,SIGKILL); waitpid(pid,&status,0); exited=true; }
|
||||
if((out_open || err_open || !exited) && wasm_monotonic_ms() >= deadline) { r["timed_out"].set_bool(true); kill(-pid,SIGKILL); kill(pid,SIGKILL); if(!exited) status_valid=waitpid(pid,&status,0)==pid; exited=true; if(in_open){close(inpipe[1]);in_open=false;} if(out_open){close(outpipe[0]);out_open=false;} if(err_open){close(errpipe[0]);err_open=false;} }
|
||||
if(out_open || err_open || !exited) usleep(10000);
|
||||
}
|
||||
if(WIFEXITED(status)) { r["exit_code"]=(f64)WEXITSTATUS(status); r["timed_out"].set_bool(false); } else if(WIFSIGNALED(status)) r["exit_code"]=(f64)(128+WTERMSIG(status));
|
||||
if(status_valid && WIFEXITED(status)) r["exit_code"]=(f64)WEXITSTATUS(status); else if(status_valid && WIFSIGNALED(status)) r["exit_code"]=(f64)(128+WTERMSIG(status));
|
||||
return(r);
|
||||
}
|
||||
|
||||
@@ -1433,6 +1595,98 @@ public:
|
||||
worker.active_workspace = this;
|
||||
}
|
||||
|
||||
using InvocationClock = std::chrono::steady_clock;
|
||||
bool invocation_active = false;
|
||||
InvocationClock::time_point invocation_deadline;
|
||||
u64 invocation_budget_ms = 0;
|
||||
|
||||
u64 invocation_remaining_ms(InvocationClock::time_point now = InvocationClock::now()) const
|
||||
{
|
||||
if(!invocation_active)
|
||||
return(UINT64_MAX);
|
||||
if(now >= invocation_deadline)
|
||||
return(0);
|
||||
u64 remaining_us = (u64)std::chrono::duration_cast<std::chrono::microseconds>(invocation_deadline - now).count();
|
||||
return(remaining_us / 1000 + (remaining_us % 1000 != 0));
|
||||
}
|
||||
|
||||
bool invocation_expired(InvocationClock::time_point now = InvocationClock::now()) const
|
||||
{
|
||||
return(invocation_active && now >= invocation_deadline);
|
||||
}
|
||||
|
||||
String invocation_timeout_error() const
|
||||
{
|
||||
return("UCE_INVOCATION_TIMEOUT: wasm invocation exceeded " + std::to_string(invocation_budget_ms) + " ms");
|
||||
}
|
||||
|
||||
void arm_guest_deadline(wasmtime::Store::Context context)
|
||||
{
|
||||
u64 ticks = worker.cfg.epoch_deadline_ticks;
|
||||
if(invocation_active)
|
||||
{
|
||||
u64 remaining_ms = invocation_remaining_ms();
|
||||
if(remaining_ms == 0)
|
||||
ticks = 0;
|
||||
else
|
||||
{
|
||||
u64 remaining_ticks = remaining_ms / worker.cfg.epoch_period_ms +
|
||||
(remaining_ms % worker.cfg.epoch_period_ms != 0);
|
||||
u64 segment_ms = worker.cfg.epoch_deadline_ticks > UINT64_MAX / worker.cfg.epoch_period_ms ?
|
||||
UINT64_MAX : worker.cfg.epoch_deadline_ticks * worker.cfg.epoch_period_ms;
|
||||
// One extra tick prevents the engine ticker's current phase from
|
||||
// interrupting just before the absolute steady-clock deadline.
|
||||
if(remaining_ms <= segment_ms)
|
||||
ticks = remaining_ticks == UINT64_MAX ? UINT64_MAX : remaining_ticks + 1;
|
||||
}
|
||||
}
|
||||
context.set_epoch_deadline(ticks);
|
||||
}
|
||||
|
||||
u64 bounded_hostcall_timeout_ms(u64 requested_ms) const
|
||||
{
|
||||
if(!invocation_active)
|
||||
return(requested_ms);
|
||||
u64 remaining_ms = invocation_remaining_ms();
|
||||
return(std::min(requested_ms, remaining_ms));
|
||||
}
|
||||
|
||||
class InvocationScope
|
||||
{
|
||||
WasmWorkspace& workspace;
|
||||
bool replaced = false;
|
||||
bool previous_active = false;
|
||||
InvocationClock::time_point previous_deadline;
|
||||
u64 previous_budget_ms = 0;
|
||||
public:
|
||||
InvocationScope(WasmWorkspace& workspace, u64 timeout_cap_ms = 0, bool force_new = false) : workspace(workspace)
|
||||
{
|
||||
if(!workspace.invocation_active || force_new)
|
||||
{
|
||||
replaced = true;
|
||||
previous_active = workspace.invocation_active;
|
||||
previous_deadline = workspace.invocation_deadline;
|
||||
previous_budget_ms = workspace.invocation_budget_ms;
|
||||
u64 budget_ms = workspace.worker.cfg.invocation_timeout_ms;
|
||||
if(timeout_cap_ms > 0)
|
||||
budget_ms = std::min(budget_ms, timeout_cap_ms);
|
||||
workspace.invocation_active = true;
|
||||
workspace.invocation_budget_ms = budget_ms;
|
||||
workspace.invocation_deadline = InvocationClock::now() + std::chrono::milliseconds(budget_ms);
|
||||
}
|
||||
workspace.arm_guest_deadline(workspace.ctx());
|
||||
}
|
||||
~InvocationScope()
|
||||
{
|
||||
if(!replaced)
|
||||
return;
|
||||
workspace.invocation_active = previous_active;
|
||||
workspace.invocation_deadline = previous_deadline;
|
||||
workspace.invocation_budget_ms = previous_budget_ms;
|
||||
workspace.arm_guest_deadline(workspace.ctx());
|
||||
}
|
||||
};
|
||||
|
||||
void set_perf_snapshot(u64 worker_pid, u64 parent_pid, u64 request_count,
|
||||
f64 time_init, f64 time_params, f64 time_input, f64 time_start,
|
||||
u64 ready_normalize_us, u64 ready_mutation_check_us, u64 ready_artifact_stat_us,
|
||||
@@ -1534,7 +1788,7 @@ public:
|
||||
};
|
||||
auto cx = ctx();
|
||||
store.limiter(worker.cfg.memory_limit, -1, -1, -1, -1);
|
||||
cx.set_epoch_deadline(worker.cfg.epoch_deadline_ticks);
|
||||
arm_guest_deadline(cx);
|
||||
birth_policy_us = phase_us();
|
||||
|
||||
auto& module = *worker.core_module;
|
||||
@@ -1708,6 +1962,7 @@ public:
|
||||
// empty body, a missing cli/serve handler to a 404).
|
||||
String invoke_entry(const String& entry_source_path, const String& handler, bool* handler_present = 0)
|
||||
{
|
||||
InvocationScope invocation(*this);
|
||||
auto phase_started = std::chrono::steady_clock::now();
|
||||
auto phase_us = [&]() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
@@ -1719,6 +1974,8 @@ public:
|
||||
size_t unit_index = 0;
|
||||
String error = load_unit(entry_source_path, "entry", unit_index);
|
||||
entry_load_us = phase_us();
|
||||
if(invocation_expired())
|
||||
return(invocation_timeout_error());
|
||||
if(error != "")
|
||||
return(error);
|
||||
String handler_symbol = handler_export_symbol(handler);
|
||||
@@ -1761,7 +2018,7 @@ public:
|
||||
if(error != "")
|
||||
return(error);
|
||||
entry_link_us = phase_us();
|
||||
auto result = entry->call(ctx(), { wasmtime::Val((int32_t)handler_slot), wasmtime::Val((int32_t)once_slot) });
|
||||
auto result = call_guest(*entry, { wasmtime::Val((int32_t)handler_slot), wasmtime::Val((int32_t)once_slot) });
|
||||
entry_dispatch_us = phase_us();
|
||||
if(!result)
|
||||
return(trap_text(result.err()));
|
||||
@@ -1839,6 +2096,8 @@ private:
|
||||
String trap_text(const wasmtime::TrapError& error)
|
||||
{
|
||||
String result = wasm_trace_collapse(String(error.message()));
|
||||
if(invocation_expired() && result.find("interrupt") != String::npos && result.find("UCE_INVOCATION_TIMEOUT:") == String::npos)
|
||||
result = invocation_timeout_error() + "\n" + result;
|
||||
struct Frame
|
||||
{
|
||||
String module;
|
||||
@@ -1952,6 +2211,13 @@ private:
|
||||
return(std::nullopt);
|
||||
}
|
||||
|
||||
wasmtime::TrapResult<std::vector<wasmtime::Val>> call_guest(wasmtime::Func& func, std::vector<wasmtime::Val> args)
|
||||
{
|
||||
auto context = ctx();
|
||||
arm_guest_deadline(context);
|
||||
return(func.call(context, args));
|
||||
}
|
||||
|
||||
String call_core(const String& name, std::vector<int32_t> argv, int32_t* result_out)
|
||||
{
|
||||
auto func = core_func(name);
|
||||
@@ -1960,7 +2226,7 @@ private:
|
||||
std::vector<wasmtime::Val> args;
|
||||
for(auto value : argv)
|
||||
args.push_back(wasmtime::Val(value));
|
||||
auto result = func->call(ctx(), args);
|
||||
auto result = call_guest(*func, args);
|
||||
if(!result)
|
||||
return(trap_text(result.err()));
|
||||
auto values = result.ok();
|
||||
@@ -2082,7 +2348,7 @@ private:
|
||||
// watchdog before the first core call so that wall time cannot make the
|
||||
// otherwise harmless malloc/reloc sequence trap immediately.
|
||||
auto allocate_start = std::chrono::steady_clock::now();
|
||||
ctx().set_epoch_deadline(worker.cfg.epoch_deadline_ticks);
|
||||
arm_guest_deadline(ctx());
|
||||
if(mod->abi.version != abi_version)
|
||||
return(mod->wasm_path + ": uce.abi version " + std::to_string(mod->abi.version)
|
||||
+ " does not match core ABI " + std::to_string(abi_version));
|
||||
@@ -2255,19 +2521,19 @@ private:
|
||||
// init sequence, then bind this unit's context to the request
|
||||
if(auto relocs = unit_func(unit_index, "__wasm_apply_data_relocs"))
|
||||
{
|
||||
auto result = relocs->call(ctx(), {});
|
||||
auto result = call_guest(*relocs, {});
|
||||
if(!result)
|
||||
return(trap_text(result.err()));
|
||||
}
|
||||
if(auto ctors = unit_func(unit_index, "__wasm_call_ctors"))
|
||||
{
|
||||
auto result = ctors->call(ctx(), {});
|
||||
auto result = call_guest(*ctors, {});
|
||||
if(!result)
|
||||
return(trap_text(result.err()));
|
||||
}
|
||||
if(auto set_request = unit_func(unit_index, "__uce_set_current_request"))
|
||||
{
|
||||
auto result = set_request->call(ctx(), { wasmtime::Val(request_ptr) });
|
||||
auto result = call_guest(*set_request, { wasmtime::Val(request_ptr) });
|
||||
if(!result)
|
||||
return(trap_text(result.err()));
|
||||
}
|
||||
@@ -2295,7 +2561,7 @@ private:
|
||||
unit_module_operations[module_operation_index].materialize_us = materialize_us;
|
||||
// Exclude the rest of host-side loading as well. A genuine runaway loop
|
||||
// makes no loads, so it still trips the deadline.
|
||||
ctx().set_epoch_deadline(worker.cfg.epoch_deadline_ticks);
|
||||
arm_guest_deadline(ctx());
|
||||
return("");
|
||||
}
|
||||
|
||||
@@ -2714,12 +2980,13 @@ private:
|
||||
return(slot);
|
||||
}
|
||||
|
||||
String run_task_callback(u64 callback_id)
|
||||
String run_task_callback(u64 callback_id, u64 timeout_cap_ms)
|
||||
{
|
||||
InvocationScope invocation(*this, timeout_cap_ms, true);
|
||||
auto runner = core_func("uce_wasm_task_run");
|
||||
if(!runner)
|
||||
return("core does not export uce_wasm_task_run");
|
||||
auto result = runner->call(ctx(), { wasmtime::Val((int64_t)callback_id) });
|
||||
auto result = call_guest(*runner, { wasmtime::Val((int64_t)callback_id) });
|
||||
if(!result)
|
||||
return(trap_text(result.err()));
|
||||
return("");
|
||||
@@ -2749,17 +3016,16 @@ private:
|
||||
bool profile_memcache = name == "uce_host_memcache_command";
|
||||
auto profiled = [self, callback, profile_index, profile_enabled, profile_mysql, profile_memcache](Caller caller, Span<const Val> args, Span<Val> results) mutable -> Result<std::monostate, Trap> {
|
||||
auto started = std::chrono::steady_clock::now();
|
||||
if(self->invocation_expired(started))
|
||||
return(Trap(self->invocation_timeout_error()));
|
||||
f64 cpu_started = profile_enabled && self->worker.cfg.profile_hostcall_cpu ? wasm_thread_cpu_time() : 0;
|
||||
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);
|
||||
auto finished = std::chrono::steady_clock::now();
|
||||
self->arm_guest_deadline(caller.context());
|
||||
f64 cpu_finished = profile_enabled && self->worker.cfg.profile_hostcall_cpu ? wasm_thread_cpu_time() : 0;
|
||||
if(profile_enabled)
|
||||
{
|
||||
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>(finished - started).count();
|
||||
u64 cpu_elapsed = cpu_started > 0 && cpu_finished > cpu_started ?
|
||||
(u64)((cpu_finished - cpu_started) * 1000000.0) : 0;
|
||||
self->hostcall_count++;
|
||||
@@ -2782,6 +3048,8 @@ private:
|
||||
self->memcache_hostcall_total_us += elapsed;
|
||||
}
|
||||
}
|
||||
if(self->invocation_expired(finished))
|
||||
return(Trap(self->invocation_timeout_error()));
|
||||
return(result);
|
||||
};
|
||||
auto defined = linker.func_new(mod, name, func_type, profiled);
|
||||
@@ -3072,7 +3340,16 @@ private:
|
||||
String stage_key = "shell:" + cmd;
|
||||
if(!self->hostcall_staged(stage_key, out))
|
||||
{
|
||||
out = ::shell_exec(cmd);
|
||||
u64 remaining_ms = self->invocation_remaining_ms();
|
||||
u64 timeout_ms = std::max<u64>(1, self->bounded_hostcall_timeout_ms(5000));
|
||||
DValue execution = uce_process_exec(cmd + " 2>&1", "", StringMap(), timeout_ms);
|
||||
if(execution["timed_out"].to_bool())
|
||||
{
|
||||
String kind = self->invocation_expired() || remaining_ms <= 5000 ?
|
||||
"UCE_INVOCATION_TIMEOUT" : "UCE_HOSTCALL_TIMEOUT";
|
||||
return(Trap(kind + ": shell_exec exceeded " + std::to_string(timeout_ms) + " ms"));
|
||||
}
|
||||
out = execution["stdout"].to_string();
|
||||
if(buf == 0)
|
||||
self->hostcall_stage(stage_key, out);
|
||||
}
|
||||
@@ -3084,7 +3361,7 @@ private:
|
||||
if(mod == "env" && name == "uce_host_http_request")
|
||||
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;
|
||||
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)) { u64 requested=req.key("timeout_ms")?req.key("timeout_ms")->to_u64(5000):5000; if(requested==0)requested=5000; req["timeout_ms"]=(f64)std::max<u64>(1,self->bounded_hostcall_timeout_ms(requested)); 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); results[0]=Val((int32_t)out.size()); return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_http_request_async")
|
||||
@@ -3094,7 +3371,7 @@ private:
|
||||
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="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)) { u64 requested=spec.key("timeout_ms")?spec.key("timeout_ms")->to_u64(5000):5000; if(requested==0)requested=5000; spec["timeout_ms"]=(f64)std::max<u64>(1,self->bounded_hostcall_timeout_ms(requested)); 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);
|
||||
results[0]=Val((int32_t)out.size()); return(std::monostate());
|
||||
}));
|
||||
@@ -3103,11 +3380,23 @@ private:
|
||||
String encoded; self->hostcall_read(args[0].i32(), args[1].i32(), encoded); DValue spec; String err; u64 id=0; if(ucb_decode(encoded,spec,&err)) id=uce_shell_spawn_spec(spec); results[0]=Val((int64_t)id); return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_job_status")
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> { String out=ucb_encode(uce_job_status_value((u64)args[0].i64())); 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> { u64 job_id=(u64)args[0].i64(); String out,stage_key="job_status:"+std::to_string(job_id); u32 cap=(u32)args[2].i32(); int32_t buf=args[1].i32(); if(!self->hostcall_staged(stage_key,out)){out=ucb_encode(uce_job_status_value(job_id));if(buf==0)self->hostcall_stage(stage_key,out);} 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_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> { u64 job_id=(u64)args[0].i64(); String out,stage_key="job_result:"+std::to_string(job_id); u32 cap=(u32)args[2].i32(); int32_t buf=args[1].i32(); if(!self->hostcall_staged(stage_key,out)){out=ucb_encode(uce_job_result_value(job_id, self->bounded_hostcall_timeout_ms(100)));if(buf==0)self->hostcall_stage(stage_key,out);} 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")
|
||||
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()); }));
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
u64 job_id=(u64)args[0].i64(), requested_timeout=(u64)args[1].i64();
|
||||
u32 cap=(u32)args[3].i32(); int32_t buf=args[2].i32();
|
||||
String out, stage_key="job_await:"+std::to_string(job_id)+":"+std::to_string(requested_timeout);
|
||||
if(!self->hostcall_staged(stage_key,out))
|
||||
{
|
||||
u64 timeout=self->bounded_hostcall_timeout_ms(std::min<u64>(requested_timeout, 30000));
|
||||
out=ucb_encode(uce_job_result_value(job_id, timeout));
|
||||
if(buf==0) self->hostcall_stage(stage_key,out);
|
||||
}
|
||||
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")
|
||||
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")
|
||||
@@ -3671,8 +3960,9 @@ private:
|
||||
}
|
||||
else
|
||||
{
|
||||
::socket_write((u64)args[0].i64(), command + "\r\n");
|
||||
out = ::socket_read((u64)args[0].i64());
|
||||
u64 socket_fd = (u64)args[0].i64();
|
||||
wasm_socket_write_bounded(socket_fd, command + "\r\n", self->bounded_hostcall_timeout_ms(1000));
|
||||
out = wasm_socket_read_bounded(socket_fd, 1024 * 128, self->bounded_hostcall_timeout_ms(1000));
|
||||
if(buf == 0)
|
||||
{
|
||||
self->staged_memcache_key = key;
|
||||
@@ -3810,28 +4100,8 @@ private:
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String host;
|
||||
self->hostcall_read(args[0].i32(), args[1].i32(), host);
|
||||
int fd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if(fd >= 0)
|
||||
{
|
||||
struct sockaddr_in addr = {0};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons((short)args[2].i32());
|
||||
addr.sin_addr.s_addr = inet_addr(host.c_str());
|
||||
if(::connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
|
||||
{
|
||||
::close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
else if(fd == 0)
|
||||
{
|
||||
int moved = ::dup(fd);
|
||||
::close(fd);
|
||||
fd = moved;
|
||||
}
|
||||
if(fd > 0 && context)
|
||||
context->resources.sockets.push_back(fd);
|
||||
}
|
||||
results[0] = Val((int64_t)(fd > 0 ? fd : 0));
|
||||
u64 fd = wasm_socket_connect_bounded(host, (u16)args[2].i32(), self->bounded_hostcall_timeout_ms(self->worker.cfg.invocation_timeout_ms));
|
||||
results[0] = Val((int64_t)fd);
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_socket_close")
|
||||
@@ -3843,17 +4113,20 @@ private:
|
||||
return(add([self](Caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String data;
|
||||
self->hostcall_read(args[1].i32(), args[2].i32(), data);
|
||||
results[0] = Val(::socket_write((u64)args[0].i64(), data) ? (int32_t)1 : (int32_t)0);
|
||||
results[0] = Val(wasm_socket_write_bounded((u64)args[0].i64(), data, self->bounded_hostcall_timeout_ms(self->worker.cfg.invocation_timeout_ms)) ? (int32_t)1 : (int32_t)0);
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_socket_read")
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
u64 sockfd = (u64)args[0].i64();
|
||||
u32 max_length = (u32)args[1].i32();
|
||||
u32 timeout = (u32)args[2].i32();
|
||||
u32 requested_timeout = (u32)args[2].i32();
|
||||
u64 requested_ms = requested_timeout == 0 ? self->invocation_remaining_ms() : (u64)requested_timeout * 1000;
|
||||
u64 bounded_ms = self->bounded_hostcall_timeout_ms(requested_ms);
|
||||
int32_t buf = args[3].i32();
|
||||
u32 cap = (u32)args[4].i32();
|
||||
String key = std::to_string(sockfd) + ":" + std::to_string(max_length) + ":" + std::to_string(timeout);
|
||||
// The size and fetch calls share this key, while the remaining budget may change between them.
|
||||
String key = std::to_string(sockfd) + ":" + std::to_string(max_length) + ":" + std::to_string(requested_timeout);
|
||||
String out;
|
||||
if(buf != 0 && self->staged_socket_read_key == key)
|
||||
{
|
||||
@@ -3863,7 +4136,7 @@ private:
|
||||
}
|
||||
else
|
||||
{
|
||||
out = ::socket_read(sockfd, max_length, timeout);
|
||||
out = wasm_socket_read_bounded(sockfd, max_length, bounded_ms);
|
||||
if(buf == 0)
|
||||
{
|
||||
self->staged_socket_read_key = key;
|
||||
@@ -3920,8 +4193,9 @@ private:
|
||||
// before the hostcall stack unwinds, so `self` points to the child's
|
||||
// copy of this per-request workspace. The parent request can return and
|
||||
// destroy its workspace without invalidating the child copy.
|
||||
auto run_callback = [self, callback_id]() {
|
||||
String error = self->run_task_callback(callback_id);
|
||||
u64 task_timeout_ms = timeout > UINT64_MAX / 1000 ? UINT64_MAX : timeout * 1000;
|
||||
auto run_callback = [self, callback_id, task_timeout_ms]() {
|
||||
String error = self->run_task_callback(callback_id, task_timeout_ms);
|
||||
if(error != "")
|
||||
fprintf(stderr, "[wasm task] callback failed: %s\n", error.c_str());
|
||||
};
|
||||
@@ -3959,19 +4233,17 @@ private:
|
||||
if(mod == "env" && name == "uce_host_sleep_us")
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
u64 usec = (u64)args[0].i64();
|
||||
while(usec >= 1000000ull)
|
||||
{
|
||||
unsigned int remaining = ::sleep((unsigned int)(usec / 1000000ull));
|
||||
if(remaining != 0)
|
||||
{
|
||||
results[0] = Val((int32_t)remaining);
|
||||
return(std::monostate());
|
||||
}
|
||||
usec %= 1000000ull;
|
||||
}
|
||||
if(usec > 0)
|
||||
::usleep((useconds_t)usec);
|
||||
results[0] = Val((int32_t)0);
|
||||
u64 requested_ms = usec / 1000 + (usec % 1000 != 0);
|
||||
u64 bounded_ms = self->bounded_hostcall_timeout_ms(requested_ms);
|
||||
u64 bounded_usec = bounded_ms > UINT64_MAX / 1000 ? UINT64_MAX : bounded_ms * 1000;
|
||||
u64 sleep_usec = std::min(usec, bounded_usec);
|
||||
struct timespec requested = { (time_t)(sleep_usec / 1000000), (long)((sleep_usec % 1000000) * 1000) };
|
||||
struct timespec interrupted = { 0, 0 };
|
||||
u64 unslept_usec = usec - sleep_usec;
|
||||
if(sleep_usec > 0 && nanosleep(&requested, &interrupted) != 0 && errno == EINTR)
|
||||
unslept_usec += (u64)interrupted.tv_sec * 1000000 + (u64)interrupted.tv_nsec / 1000;
|
||||
u64 unslept_seconds = unslept_usec / 1000000 + (unslept_usec % 1000000 != 0);
|
||||
results[0] = Val((int32_t)std::min<u64>(UINT32_MAX, unslept_seconds));
|
||||
return(std::monostate());
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_regex")
|
||||
|
||||
Reference in New Issue
Block a user