lumbda-www: asm-gc static file server for lumbda.com + caddy race
Ships examples/http-static-server.lsp — ~65 lines of portable Scheme
that reads files from a docroot (default ./www) and serves them over
HTTP/1.0 with MIME dispatch, path-traversal rejection, heap-snapshot
per request. Runs in any tier; target deployment is asm-gc for the
27 KB stripped binary + bounded memory backstop.
Required one asm fix first: heap_grow was mmap'ing fixed HEAP_SIZE
chunks, so any single allocation larger than a chunk (notably the
2.67 MB whitepaper PDF read via file->string) loop-looped through
.ha_overflow forever. Now heap_grow rounds required bytes up to
HEAP_SIZE multiples on oversize alloc, so a big request carves its
own big chunk in one go. Small allocs still land in standard-sized
chunks.
Two new benches:
tests/bench-lumbda-www.sh — drive N small + M large requests against
asm-gc, verify PDF round-trip, sample peak RSS. At 1000/100: 331 req/s
small, 120 req/s large (304 MiB/s), peak 15.5 MB.
tests/bench-www-race.sh — adjacent A/B vs caddy v2.5.1 on the same
docroot. Numbers on this laptop, concurrency 8, 2000 small + 200 large:
small req/s PDF req/s PDF MiB/s peak RSS binary
lumbda-www (asm-gc) 375 137 349 7–16 MB 27 KB
caddy file-server 358 231 588 38 MB 38 MB
Reading: lumbda edges caddy on small files (less per-request overhead),
caddy wins 1.7x on large files (sendfile zero-copy; we allocate the
whole file into a string and write it with one syscall). Both byte-
identical on the PDF. Memory: lumbda 2.5-5x less at steady state.
Binary size: 1400x smaller (27 KB vs 38 MB).
Feature gap: caddy has HTTPS, HTTP/2, range, middleware, etc. lumbda
has none of that yet — but for the specific job of serving lumbda.com's
six-file docroot it is viable right now.
Makefile adds `bench-lumbda-www` and `bench-www-race` targets.
137 asm no-GC + 137 asm GC tests still pass.
This commit is contained in:
parent
e2a74832ef
commit
dd961d2133
7 changed files with 364 additions and 2 deletions
5
Makefile
5
Makefile
|
|
@ -138,6 +138,11 @@ bench-gc-adaptive: asm-build
|
|||
bench-gc-http: asm-build
|
||||
@bash tests/bench-gc-http.sh
|
||||
|
||||
bench-lumbda-www: asm-build
|
||||
@bash tests/bench-lumbda-www.sh
|
||||
|
||||
bench-www-race: asm-build
|
||||
@bash tests/bench-www-race.sh
|
||||
bench-all: bench c-bench bench-3way bench-portal bench-portal-cross bench-web bench-rpc-chain bench-proof
|
||||
@echo "═══════════════════════════════════════════════════════════"
|
||||
@echo "All benchmarks complete. Numbers in the whitepaper §6.4,"
|
||||
|
|
|
|||
BIN
asm/lumbda-gc
BIN
asm/lumbda-gc
Binary file not shown.
BIN
asm/lumbda-gc.o
BIN
asm/lumbda-gc.o
Binary file not shown.
24
asm/lumbda.s
24
asm/lumbda.s
|
|
@ -788,9 +788,27 @@ heap_alloc:
|
|||
movq %r15, gc_free_list(%rip)
|
||||
addq %rax, %r15 # %r15 now == %r13
|
||||
.ha_grow_no_pad:
|
||||
# Chunk size: at least HEAP_SIZE, but large enough for this one
|
||||
# block. A single big allocation (e.g. file->string on a ~3 MB
|
||||
# PDF) would otherwise loop forever because each mmap'd 1 MB
|
||||
# chunk still doesn't fit. Round up (8+%rbx) to HEAP_SIZE
|
||||
# multiples so small allocs still land in standard-sized chunks.
|
||||
leaq 8(%rbx), %rcx # required bytes
|
||||
cmpq $HEAP_SIZE, %rcx
|
||||
jbe .ha_grow_std
|
||||
# Oversize: pad up to HEAP_SIZE alignment and use that.
|
||||
addq $(HEAP_SIZE - 1), %rcx
|
||||
movq $HEAP_SIZE, %rdx
|
||||
negq %rdx # rdx = -HEAP_SIZE (low bits = 0-HEAP_SIZE mask)
|
||||
andq %rdx, %rcx
|
||||
jmp .ha_grow_do_mmap
|
||||
.ha_grow_std:
|
||||
movq $HEAP_SIZE, %rcx
|
||||
.ha_grow_do_mmap:
|
||||
movq %rcx, %r12 # stash size across the syscall
|
||||
movq $SYS_MMAP, %rax
|
||||
xorq %rdi, %rdi
|
||||
movq $HEAP_SIZE, %rsi
|
||||
movq %r12, %rsi # chunk size
|
||||
movq $3, %rdx
|
||||
movq $0x22, %r10
|
||||
movq $-1, %r8
|
||||
|
|
@ -799,7 +817,9 @@ heap_alloc:
|
|||
cmpq $-1, %rax
|
||||
je die_oom
|
||||
movq %rax, %r15
|
||||
leaq HEAP_SIZE(%rax), %r13
|
||||
addq %r12, %rax # chunk end
|
||||
movq %rax, %r13
|
||||
movq %r15, %rax # restore ptr (we'll use below)
|
||||
movq gc_chunk_count(%rip), %rdx
|
||||
cmpq $GC_MAX_CHUNKS, %rdx
|
||||
jae die_oom
|
||||
|
|
|
|||
124
examples/http-static-server.lsp
Normal file
124
examples/http-static-server.lsp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
;;; http-static-server.lsp — serve lumbda.com's docroot from pure Scheme.
|
||||
;;;
|
||||
;;; Runs in any tier that has file->string + the tcp-* family: Python,
|
||||
;;; C, and both asm builds. The asm-gc build is the target deployment
|
||||
;;; (~27 KB binary, bounded memory via heap-snapshot per request).
|
||||
;;;
|
||||
;;; ./asm/lumbda-gc < examples/http-static-server.lsp
|
||||
;;;
|
||||
;;; Serves GET requests under *docroot*. Default docroot is "www" —
|
||||
;;; run from the repo root so the relative path resolves.
|
||||
|
||||
(define *port* 8080)
|
||||
(define *docroot* "www")
|
||||
(define *max-requests* 100000)
|
||||
|
||||
(define *crlf* "\r\n")
|
||||
(define *crlf-crlf* "\r\n\r\n")
|
||||
|
||||
;;; ── HTTP helpers ────────────────────────────────────────────
|
||||
|
||||
(define (http-response status ctype body)
|
||||
(string-append
|
||||
"HTTP/1.0 " status *crlf*
|
||||
"Content-Type: " ctype *crlf*
|
||||
"Content-Length: " (number->string (string-length body)) *crlf*
|
||||
"Connection: close" *crlf-crlf*
|
||||
body))
|
||||
|
||||
(define SPACE 32)
|
||||
(define DOT 46)
|
||||
(define (char-at s i) (char->integer (string-ref s i)))
|
||||
|
||||
;;; Pull the URL path out of the request line "GET /path HTTP/1.0\r\n..."
|
||||
(define (second-token s)
|
||||
(let ((len (string-length s)))
|
||||
(let loop1 ((i 0))
|
||||
(cond
|
||||
((= i len) "/")
|
||||
((= (char-at s i) SPACE)
|
||||
(let loop2 ((j (+ i 1)))
|
||||
(cond
|
||||
((= j len) (substring s (+ i 1) len))
|
||||
((= (char-at s j) SPACE) (substring s (+ i 1) j))
|
||||
(else (loop2 (+ j 1))))))
|
||||
(else (loop1 (+ i 1)))))))
|
||||
|
||||
;;; ── Path safety ─────────────────────────────────────────────
|
||||
|
||||
;;; Reject any path containing ".." — keeps requests under docroot.
|
||||
(define (has-dotdot? s)
|
||||
(let ((n (string-length s)))
|
||||
(let loop ((i 0))
|
||||
(cond
|
||||
((>= (+ i 1) n) #f)
|
||||
((and (= (char-at s i) DOT) (= (char-at s (+ i 1)) DOT)) #t)
|
||||
(else (loop (+ i 1)))))))
|
||||
|
||||
;;; ── MIME dispatch ───────────────────────────────────────────
|
||||
|
||||
(define (ends-with? s suffix)
|
||||
(let ((ns (string-length s)) (nf (string-length suffix)))
|
||||
(if (< ns nf) #f
|
||||
(string=? suffix (substring s (- ns nf) ns)))))
|
||||
|
||||
(define (mime-of path)
|
||||
(cond
|
||||
((ends-with? path ".html") "text/html; charset=utf-8")
|
||||
((ends-with? path ".css") "text/css; charset=utf-8")
|
||||
((ends-with? path ".pdf") "application/pdf")
|
||||
((ends-with? path ".txt") "text/plain; charset=utf-8")
|
||||
((ends-with? path ".js") "application/javascript")
|
||||
((ends-with? path ".png") "image/png")
|
||||
((ends-with? path ".jpg") "image/jpeg")
|
||||
((ends-with? path ".svg") "image/svg+xml")
|
||||
((ends-with? path ".ico") "image/x-icon")
|
||||
(else "application/octet-stream")))
|
||||
|
||||
;;; ── File resolution ─────────────────────────────────────────
|
||||
|
||||
(define (resolve-fs-path url-path)
|
||||
(cond
|
||||
((string=? url-path "/") (string-append *docroot* "/index.html"))
|
||||
(else (string-append *docroot* url-path))))
|
||||
|
||||
(define (serve-file url-path)
|
||||
(cond
|
||||
((has-dotdot? url-path)
|
||||
(http-response "403 Forbidden" "text/plain" "forbidden\n"))
|
||||
(else
|
||||
(let ((fs-path (resolve-fs-path url-path)))
|
||||
(let ((body (file->string fs-path)))
|
||||
(if body
|
||||
(http-response "200 OK" (mime-of fs-path) body)
|
||||
(let ((nf (file->string (string-append *docroot* "/404.html"))))
|
||||
(http-response "404 Not Found" "text/html; charset=utf-8"
|
||||
(if nf nf "not found\n")))))))))
|
||||
|
||||
(define (handle-request req)
|
||||
(let ((path (second-token req)))
|
||||
(serve-file path)))
|
||||
|
||||
;;; ── Main loop ───────────────────────────────────────────────
|
||||
|
||||
(define server (tcp-listen *port*))
|
||||
|
||||
(define (server-loop n snap)
|
||||
(if (>= n *max-requests*)
|
||||
(begin
|
||||
(display "request cap reached, exiting\n")
|
||||
(tcp-close server))
|
||||
(begin
|
||||
(let ((client (tcp-accept server)))
|
||||
(let ((req (tcp-recv client 4096)))
|
||||
(if (and req (> (string-length req) 0))
|
||||
(tcp-send client (handle-request req))
|
||||
#f))
|
||||
(tcp-close client))
|
||||
;; Rewind per-request allocations on asm; no-op on Python/C.
|
||||
(heap-restore snap)
|
||||
(server-loop (+ n 1) snap))))
|
||||
|
||||
(display "lumbda-www on :") (display *port*)
|
||||
(display " serving ") (display *docroot*) (newline)
|
||||
(server-loop 0 (heap-snapshot))
|
||||
105
tests/bench-lumbda-www.sh
Executable file
105
tests/bench-lumbda-www.sh
Executable file
|
|
@ -0,0 +1,105 @@
|
|||
#!/bin/bash
|
||||
# bench-lumbda-www.sh — serve lumbda.com's docroot from asm-gc
|
||||
# and bombard it with small-file + large-file requests. Verifies:
|
||||
# (a) throughput is viable for real traffic,
|
||||
# (b) peak RSS stays bounded under mixed payloads (2.67 MB PDF
|
||||
# per request would otherwise blow up without heap-restore),
|
||||
# (c) PDF bytes round-trip identically across thousands of
|
||||
# requests (no truncation, no partial writes).
|
||||
set -u
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
SMALL_N=${SMALL_N:-5000}
|
||||
LARGE_N=${LARGE_N:-500}
|
||||
CONCURRENCY=${CONCURRENCY:-8}
|
||||
BIN=${BIN:-./asm/lumbda-gc}
|
||||
|
||||
declare -a SPAWNED=()
|
||||
cleanup() {
|
||||
for pid in "${SPAWNED[@]}"; do kill -9 "$pid" 2>/dev/null; done
|
||||
sleep 0.2
|
||||
pkill -9 -u "$USER" -f 'examples/http-static-server' 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
make -s -C asm all
|
||||
|
||||
ulimit -v 524288
|
||||
|
||||
printf "\n═══════════════════════════════════════════════════════\n"
|
||||
printf "lumbda.com under asm-gc: %s small (/) + %s large (/whitepaper.pdf)\n" "$SMALL_N" "$LARGE_N"
|
||||
printf " concurrency %s\n" "$CONCURRENCY"
|
||||
printf "═══════════════════════════════════════════════════════\n"
|
||||
|
||||
"$BIN" < examples/http-static-server.lsp >/dev/null 2>&1 &
|
||||
pid=$!
|
||||
SPAWNED+=("$pid")
|
||||
|
||||
# Wait for port
|
||||
for _ in $(seq 1 30); do
|
||||
if curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/ 2>/dev/null | grep -q 200; then
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
echo "server failed to start"; exit 1
|
||||
fi
|
||||
|
||||
# Verify once that the PDF round-trips byte-identically.
|
||||
curl -s -o /tmp/bench-pdf-check http://localhost:8080/whitepaper.pdf
|
||||
if cmp /tmp/bench-pdf-check whitepaper/lumbda-whitepaper.pdf >/dev/null 2>&1; then
|
||||
echo " pdf-integrity: byte-identical ($(wc -c < /tmp/bench-pdf-check) bytes)"
|
||||
else
|
||||
echo " pdf-integrity: DIFFERS"; exit 1
|
||||
fi
|
||||
rm -f /tmp/bench-pdf-check
|
||||
|
||||
# Baseline RSS
|
||||
rss0=$(awk '/^VmRSS:/ {print $2}' /proc/$pid/status)
|
||||
echo " baseline_rss_kb=$rss0"
|
||||
|
||||
# Drive small-file traffic, sample RSS while it runs.
|
||||
peak=0
|
||||
t0=$(date +%s.%N)
|
||||
( seq 1 "$SMALL_N" | xargs -P "$CONCURRENCY" -I_ \
|
||||
curl -s -o /dev/null http://localhost:8080/ ) &
|
||||
curl_pid=$!
|
||||
while kill -0 "$curl_pid" 2>/dev/null; do
|
||||
rss=$(awk '/^VmRSS:/ {print $2}' /proc/$pid/status 2>/dev/null || echo 0)
|
||||
[ "$rss" -gt "$peak" ] && peak=$rss
|
||||
sleep 0.2
|
||||
done
|
||||
wait "$curl_pid" 2>/dev/null || true
|
||||
t1=$(date +%s.%N)
|
||||
small_rps=$(python3 -c "print(f'{$SMALL_N / ((float(\"$t1\") - float(\"$t0\")) if float(\"$t1\") > float(\"$t0\") else 1):.0f}')")
|
||||
printf " small (/) %s req/s peak_rss_kb=%s\n" "$small_rps" "$peak"
|
||||
|
||||
# Reset peak for large-file phase
|
||||
peak=0
|
||||
t0=$(date +%s.%N)
|
||||
( seq 1 "$LARGE_N" | xargs -P "$CONCURRENCY" -I_ \
|
||||
curl -s -o /dev/null http://localhost:8080/whitepaper.pdf ) &
|
||||
curl_pid=$!
|
||||
while kill -0 "$curl_pid" 2>/dev/null; do
|
||||
rss=$(awk '/^VmRSS:/ {print $2}' /proc/$pid/status 2>/dev/null || echo 0)
|
||||
[ "$rss" -gt "$peak" ] && peak=$rss
|
||||
sleep 0.2
|
||||
done
|
||||
wait "$curl_pid" 2>/dev/null || true
|
||||
t1=$(date +%s.%N)
|
||||
large_rps=$(python3 -c "print(f'{$LARGE_N / ((float(\"$t1\") - float(\"$t0\")) if float(\"$t1\") > float(\"$t0\") else 1):.0f}')")
|
||||
large_mib=$(python3 -c "print(f'{$LARGE_N * 2669058 / (1024 * 1024) / ((float(\"$t1\") - float(\"$t0\")) if float(\"$t1\") > float(\"$t0\") else 1):.0f}')")
|
||||
printf " large (/pdf 2.67M) %s req/s %s MiB/s peak_rss_kb=%s\n" "$large_rps" "$large_mib" "$peak"
|
||||
|
||||
# Final RSS before kill
|
||||
final=$(awk '/^VmRSS:/ {print $2}' /proc/$pid/status 2>/dev/null || echo 0)
|
||||
growth=$((final - rss0))
|
||||
printf " final_rss_kb=%s growth_kb=%s\n" "$final" "$growth"
|
||||
|
||||
kill -9 "$pid" 2>/dev/null
|
||||
wait "$pid" 2>/dev/null || true
|
||||
|
||||
if pgrep -u "$USER" -f 'examples/http-static-server' > /dev/null; then
|
||||
echo "STRAGGLER" >&2; exit 1
|
||||
fi
|
||||
108
tests/bench-www-race.sh
Executable file
108
tests/bench-www-race.sh
Executable file
|
|
@ -0,0 +1,108 @@
|
|||
#!/bin/bash
|
||||
# bench-www-race.sh — asm-gc lumbda-www vs caddy file-server on the
|
||||
# same lumbda.com docroot. Small-request (index.html) and large-
|
||||
# request (2.67 MB PDF) workloads, identical concurrency, adjacent
|
||||
# runs. Reports req/s, MiB/s, and peak RSS for each server.
|
||||
set -u
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
SMALL_N=${SMALL_N:-2000}
|
||||
LARGE_N=${LARGE_N:-200}
|
||||
CONCURRENCY=${CONCURRENCY:-8}
|
||||
LUMBDA_PORT=${LUMBDA_PORT:-8080}
|
||||
CADDY_PORT=${CADDY_PORT:-8081}
|
||||
CADDY_BIN=${CADDY_BIN:-/home/fox/git/make_post_sell/caddy}
|
||||
|
||||
declare -a SPAWNED=()
|
||||
cleanup() {
|
||||
for pid in "${SPAWNED[@]}"; do kill -9 "$pid" 2>/dev/null; done
|
||||
sleep 0.2
|
||||
pkill -9 -u "$USER" -f 'http-static-server' 2>/dev/null || true
|
||||
pkill -9 -u "$USER" -f 'caddy file-server' 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
make -s -C asm all
|
||||
|
||||
printf "\n═══════════════════════════════════════════════════════\n"
|
||||
printf "asm-gc lumbda-www vs caddy — same docroot, same workload\n"
|
||||
printf " %s small (/), %s large (/whitepaper.pdf 2.67 MiB)\n" "$SMALL_N" "$LARGE_N"
|
||||
printf " concurrency %s\n" "$CONCURRENCY"
|
||||
printf "═══════════════════════════════════════════════════════\n"
|
||||
|
||||
# ── start both servers ──
|
||||
./asm/lumbda-gc < examples/http-static-server.lsp >/dev/null 2>&1 &
|
||||
LUMBDA_PID=$!
|
||||
SPAWNED+=("$LUMBDA_PID")
|
||||
|
||||
"$CADDY_BIN" file-server --root www --listen ":$CADDY_PORT" >/dev/null 2>&1 &
|
||||
CADDY_PID=$!
|
||||
SPAWNED+=("$CADDY_PID")
|
||||
|
||||
# Wait for both to be ready
|
||||
for _ in $(seq 1 30); do
|
||||
a=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$LUMBDA_PORT/" 2>/dev/null || echo 0)
|
||||
b=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$CADDY_PORT/" 2>/dev/null || echo 0)
|
||||
[ "$a" = "200" ] && [ "$b" = "200" ] && break
|
||||
sleep 0.1
|
||||
done
|
||||
kill -0 "$LUMBDA_PID" 2>/dev/null || { echo "lumbda-www failed to start"; exit 1; }
|
||||
kill -0 "$CADDY_PID" 2>/dev/null || { echo "caddy failed to start"; exit 1; }
|
||||
|
||||
# ── Byte-integrity check (both ways) ──
|
||||
curl -s "http://localhost:$LUMBDA_PORT/whitepaper.pdf" -o /tmp/a-lumbda.pdf
|
||||
curl -s "http://localhost:$CADDY_PORT/whitepaper.pdf" -o /tmp/a-caddy.pdf
|
||||
echo -n " pdf-integrity (lumbda): "
|
||||
cmp /tmp/a-lumbda.pdf whitepaper/lumbda-whitepaper.pdf >/dev/null 2>&1 && echo "byte-identical" || echo "DIFFERS"
|
||||
echo -n " pdf-integrity (caddy): "
|
||||
cmp /tmp/a-caddy.pdf whitepaper/lumbda-whitepaper.pdf >/dev/null 2>&1 && echo "byte-identical" || echo "DIFFERS"
|
||||
rm -f /tmp/a-lumbda.pdf /tmp/a-caddy.pdf
|
||||
|
||||
# ── benchmark one (pid, port, label, N, path) ──
|
||||
bench_one() {
|
||||
local pid="$1" port="$2" label="$3" n="$4" path="$5"
|
||||
local peak=0 rss t0 t1
|
||||
t0=$(date +%s.%N)
|
||||
( seq 1 "$n" | xargs -P "$CONCURRENCY" -I_ \
|
||||
curl -s -o /dev/null "http://localhost:$port$path" ) &
|
||||
local curl_pid=$!
|
||||
while kill -0 "$curl_pid" 2>/dev/null; do
|
||||
rss=$(awk '/^VmRSS:/ {print $2}' /proc/$pid/status 2>/dev/null || echo 0)
|
||||
[ "$rss" -gt "$peak" ] && peak=$rss
|
||||
sleep 0.2
|
||||
done
|
||||
wait "$curl_pid" 2>/dev/null || true
|
||||
t1=$(date +%s.%N)
|
||||
local rps=$(python3 -c "
|
||||
t = float('$t1') - float('$t0')
|
||||
print(f'{$n / max(t, 1e-6):.0f}')
|
||||
")
|
||||
local mibs=0
|
||||
if [ "$path" = "/whitepaper.pdf" ]; then
|
||||
mibs=$(python3 -c "
|
||||
t = float('$t1') - float('$t0')
|
||||
print(f'{$n * 2669058 / (1024*1024) / max(t, 1e-6):.0f}')
|
||||
")
|
||||
printf " %-20s %6s req/s %4s MiB/s peak_rss_kb=%s\n" "$label" "$rps" "$mibs" "$peak"
|
||||
else
|
||||
printf " %-20s %6s req/s peak_rss_kb=%s\n" "$label" "$rps" "$peak"
|
||||
fi
|
||||
}
|
||||
|
||||
echo
|
||||
echo "── small (/) ──"
|
||||
bench_one "$LUMBDA_PID" "$LUMBDA_PORT" "lumbda-www (asm-gc)" "$SMALL_N" "/"
|
||||
bench_one "$CADDY_PID" "$CADDY_PORT" "caddy file-server" "$SMALL_N" "/"
|
||||
|
||||
echo
|
||||
echo "── large (/whitepaper.pdf, 2.67 MiB) ──"
|
||||
bench_one "$LUMBDA_PID" "$LUMBDA_PORT" "lumbda-www (asm-gc)" "$LARGE_N" "/whitepaper.pdf"
|
||||
bench_one "$CADDY_PID" "$CADDY_PORT" "caddy file-server" "$LARGE_N" "/whitepaper.pdf"
|
||||
|
||||
echo
|
||||
echo "── binary sizes ──"
|
||||
printf " lumbda-gc %10s bytes stripped\n" "$(du -b asm/lumbda-gc | cut -f1)"
|
||||
printf " caddy %10s bytes (Go, v2.5.1, static)\n" "$(du -b "$CADDY_BIN" | cut -f1)"
|
||||
|
||||
kill -9 "$LUMBDA_PID" "$CADDY_PID" 2>/dev/null
|
||||
wait "$LUMBDA_PID" "$CADDY_PID" 2>/dev/null || true
|
||||
Loading…
Add table
Add a link
Reference in a new issue