Compare commits

...
Author SHA1 Message Date
Kit Langton d30ec60481 feat(server): run modal sandboxes on the vm runtime 2026-08-07 21:58:46 -04:00
+36 -11
View File
@@ -13,15 +13,30 @@ trap 'rm -f -- "$pidfile"' EXIT
"$@"
`
// Modal's VM runtime accepts process-group signals without delivering them
// (kill(-pgid) returns 0 and nothing dies; direct-pid signals work), so the
// group is enumerated from /proc and each member is signalled directly. The
// second pass catches children forked between scan and signal.
const KILL = `
pidfile=$1
sig=$2
i=0
while [ ! -s "$1" ] && [ "$i" -lt 250 ]; do sleep 0.02; i=$((i + 1)); done
if [ -s "$1" ]; then
pid=$(cat "$1")
/bin/kill "-$2" "-$pid" 2>/dev/null || true
else
exit 47
fi
while [ ! -s "$pidfile" ] && [ "$i" -lt 250 ]; do sleep 0.02; i=$((i + 1)); done
[ -s "$pidfile" ] || exit 47
target=$(cat "$pidfile")
pass=0
while [ "$pass" -lt 2 ]; do
for stat in /proc/[0-9]*/stat; do
[ -e "$stat" ] || continue
pid=\${stat#/proc/}
pid=\${pid%/stat}
set -- $(sed "s/.*) //" "$stat" 2>/dev/null)
if [ "\${3:-}" = "$target" ]; then
/bin/kill "-$sig" "$pid" 2>/dev/null || true
fi
done
pass=$((pass + 1))
done
`
export interface ModalImageSpec {
@@ -54,7 +69,15 @@ export const createModalSandbox = async (options: ModalSandboxOptions) => {
const app = await client.apps.fromName(options.app, { createIfMissing: true })
const imageSpec = options.image ?? ubuntuImage
const image = client.images.fromRegistry(imageSpec.registry).dockerfileCommands([...imageSpec.dockerfileCommands])
const sandbox = await client.sandboxes.create(app, image, options.sandbox)
// Always Modal's Full-VM runtime (beta, enabled per account): a real kernel
// with real device nodes, so workspaces can run Docker and other
// kernel-dependent workloads. Costs versus gVisor, measured Aug 2026:
// per-exec floor ~285-535ms versus ~90-165ms, and filesystem snapshots only
// (no memory snapshots — acceptable; fs-snapshot is the persistence design).
const sandbox = await client.sandboxes.create(app, image, {
...options.sandbox,
experimentalOptions: { ...options.sandbox?.experimentalOptions, vm_runtime: true },
})
return {
driver: makeModalDriver(sandbox),
sandbox,
@@ -64,12 +87,14 @@ export const createModalSandbox = async (options: ModalSandboxOptions) => {
/**
* Adapts Modal exec to the Environment driver. Files intentionally has no native
* overrides: Modal exec and filesystem tools share the same roughly 175ms floor,
* so the derived exec defaults are the simplest implementation with no measured loss.
* overrides: exec latency dominates payload work (VM runtime floor measured
* ~285-535ms per exec, Aug 2026), so the derived exec defaults are the simplest
* implementation with no measured loss.
*
* Modal cannot signal a ContainerProcess. Each command therefore starts a new
* process group and records its leader in a unique pid file; kill runs a second
* sandbox command that signals that group. Pid files are removed best-effort.
* sandbox command that enumerates that group from /proc and signals each member
* directly (see KILL). Pid files are removed best-effort.
*/
export const makeModalDriver = (sandbox: Sandbox): Driver => {
const spawn = Effect.fnUntraced(function* (command: Command) {