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
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:
park <port>Freeze the listener and release the portpark <port> --for 30sPark and auto-resume after a timerpark <port> --holdPark and hold TCP connections while parkedpark resume <port|pid>Resume a parked process and rebind its portpark swap <p1> <p2>Atomically swap which processes own two portspark uiLive TUI dashboard of parked processespark listShow all parked processespark 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 releasepark 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.
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.
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.
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.
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.
| Framework | Cold start | park resume | Speedup |
|---|---|---|---|
| Next.js (npm run dev) | 5-15s | ~40ms | 125-375x |
| Vite (npm run dev) | 1-3s | ~40ms | 25-75x |
| Django (runserver) | 2-4s | ~40ms | 50-100x |
| Flask (flask run) | 0.5-1s | ~40ms | 12-25x |
| FastAPI (uvicorn) | 1-2s | ~40ms | 25-50x |
| Go net/http | 0.1-0.5s | ~40ms | 2-12x |
Cold start includes module loading, template compilation, build watchers, and warm-up. park resume skips all of it.
Sockaddr encoding (the hot path during syscall injection) takes 23 nanoseconds.
| Target | macOS (Apple Silicon) | Linux x86_64 |
|---|---|---|
| uvicorn / FastAPI | park + resume | park + resume |
| python http.server | park + resume | park + resume |
| Flask / Django dev | park + resume | park + resume |
| Go net/http | park only | park + resume |
| npm run dev (Node) | park only | park + 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.
curl -fsSL https://raw.githubusercontent.com/mr-vaibh/park/main/install.sh | sh
Or grab a binary from the releases page.