park

Pause a process. Free its port. Resume later.
Ctrl+Z for servers, but the port actually frees.

curl -fsSL https://raw.githubusercontent.com/mr-vaibh/park/main/install.sh | sh

v1.0.2 · macOS (Apple Silicon) · Linux x86_64

See It In Action

The Problem

You are running npm run dev on :3000. You need that port for 30 seconds to test something else. Today you kill the dev server, losing in-memory state, build caches, websocket clients.

With park, you freeze the process, free the port, do your thing, then bring it back -- same PID, same memory, same everything:

# your dev server is running on :3000
$ park 3000
parked pid 48291 (was listening on :3000)
resume with: park resume 3000

# port 3000 is genuinely free -- use it for whatever
$ python3 -m http.server 3000 &
Serving HTTP on 0.0.0.0 port 3000 ...
$ kill %1

# bring the original server back, exactly where it was
$ park resume 3000
resumed pid 48291 on :3000

# same server, same PID, same memory
$ curl localhost:3000
{"hello":"world","warm_state":4431194800}

Commands

park <port>Freeze the listener and release the port
park <port> --for 30sPark and auto-resume after a timer
park <port> --holdPark and hold TCP connections while parked
park resume <port|pid>Resume a parked process and rebind its port
park swap <p1> <p2>Atomically swap which processes own two ports
park uiLive TUI dashboard of parked processes
park listShow all parked processes
park release <pid>Wake without rebinding (process runs, listener gone)
park release --kill <pid>Kill the parked process (SIGTERM, or --force for SIGKILL)
park upgradeUpdate park to the latest release

How It Works

park does not just send SIGSTOP. It reaches inside the target process and closes the listening socket file descriptor, which is what tells the kernel to actually release the port. On resume, it recreates the socket and rebinds it at the same fd number so the process never knows anything happened.

Linux (x86_64) -- ptrace + syscall injection

Attaches to the target via ptrace. Finds the listening socket fd by parsing /proc/net/tcp. Writes a syscall; int3 stub to the process's [vdso] page and injects a close(fd) call. On resume, injects socket + setsockopt + bind + listen + dup2 to recreate the listener at the exact same fd number.

macOS (Apple Silicon) -- Mach kernel APIs

Uses task_for_pid, mach_vm_allocate, and thread_create_running to create a brand-new injection thread inside the target -- never touches the target's existing threads. Writes an arm64 LDR+svc+brk stub to executable scratch memory and catches breakpoint exceptions via a Mach exception port. On resume, also injects kevent() to re-register with kqueue and fcntl(O_NONBLOCK) so async frameworks like uvicorn and asyncio pick up new connections.

Why Not Just SIGSTOP?

SIGSTOP freezes the process but does not free the port. The kernel still considers the listen socket bound. Anything trying to bind to that port gets EADDRINUSE.

park works because closing the file descriptor from inside the target process is what tells the kernel to actually release the listening socket. That is the difference: SIGSTOP freezes the process but keeps the port locked; park freezes the process and frees the port.

Benchmarks

park resume brings a server back in under 50ms -- no matter how long the original cold start takes. The entire cycle is dominated by kernel overhead, not application startup.

FrameworkCold startpark resumeSpeedup
Next.js (npm run dev)5-15s~40ms125-375x
Vite (npm run dev)1-3s~40ms25-75x
Django (runserver)2-4s~40ms50-100x
Flask (flask run)0.5-1s~40ms12-25x
FastAPI (uvicorn)1-2s~40ms25-50x
Go net/http0.1-0.5s~40ms2-12x

Cold start includes module loading, template compilation, build watchers, and warm-up. park resume skips all of it.

Internals

$ go test -bench=. -benchmem ./...
BenchmarkSave 9,477 119,990 ns/op
BenchmarkLoadByPID 94,096 12,039 ns/op
BenchmarkEncodeSockaddr 51M 23 ns/op
BenchmarkParseProcNet 6.8M 176 ns/op

Sockaddr encoding (the hot path during syscall injection) takes 23 nanoseconds.

Compatibility

TargetmacOS (Apple Silicon)Linux x86_64
uvicorn / FastAPIpark + resumepark + resume
python http.serverpark + resumepark + resume
Flask / Django devpark + resumepark + resume
Go net/httppark onlypark + resume
npm run dev (Node)park onlypark + resume

"park only" means the port is freed and the process is frozen, but resume does not yet rebind the listener for that runtime on that platform.

Install

curl -fsSL https://raw.githubusercontent.com/mr-vaibh/park/main/install.sh | sh

Or grab a binary from the releases page.