Port registry: global coordination across projects
This is an implementation detail of slice give-me-a-port. It is not part of
the public API and is invisible to template scripts and programs.
Problem
The base problem I'm trying to solve is what port can I use that is free right
now. Some kind of slice give-me-a-port.
Now many would say its bonus points for giving me a port will still be free over the next week or so. To those that say bind() is all you need I say "expect more from your tools." I never know when I'm going to get to show someone one of the several ideas I'm juggling day to day, so I like to keep multiple worktrees in demo ready state.
Now you should have an idea why I really want slice give-me-a-port to avoid
port conflicts not just within a single project but across every project on my
laptop.
Now technically all I need to do is write down a list of all the projects using thinslice, and then every time I need a new port
- Start with the ~65k ports for 127.0.0.1
- I can scan all of those
/**/+state+/*/thinslice.tomlfiles on my laptop. - after subtracting out the "claimed" ports, find one port not open using bind()
My assumption is that a developer laptop has thousands of available ports and only a handful in active use. I don't actually know how coordinate with the OS. I'm not going to build in a list of "avoid" other programs. I think thinslice will work with just a crude best-effort registry.
I'm going to ignore the problem of the registry and
reality drifting apart by handwaving towards some sort of slice doctor that
can rebuild the global registry from scratch. When thinslice thinks you can use
a port, but then say a week later you discover some other program wants to use
that port too... my plan is to have you manually run slice give-me-a-port again
and hand edit the specific thinslice.toml file in conflict, and then rebuild the
global registry cache.
Solution
slice maintains a global cache as the port_assignments table inside
a SQLite database at:
$XDG_DATA_HOME/thinslice/thinslice.db
Falling back to ~/.local/share/thinslice/thinslice.db when XDG_DATA_HOME is unset,
per the XDG Base Directory Specification.
SQLite is not required, I just felt like it. Technically all we need is list of claimed ports, that could just be a text file. However, I'm the kind of person who likes collecting time series data and graphing things. Once I decided to have some kind of state file, I might as well make it SQLite just in case I get a free weekend and want to stash more debugging data, like command timings.
Schema
CREATE TABLE port_assignments (
port INTEGER NOT NULL,
project_root TEXT NOT NULL, -- absolute path to the +clone+ parent dir
slot_name TEXT NOT NULL, -- name of the slot inside +state+
process_display_name TEXT NOT NULL, -- key from [process.*] in thinslice.toml
assigned_at TEXT NOT NULL, -- ISO-8601 timestamp
PRIMARY KEY (port)
);
A port appears at most once. The combination of (project_root, slot_name, process_display_name) identifies what owns it.
Read/write lifecycle
On slice give-me-a-port: reads all rows from port_assignments to build
the global exclusion set, then combines it with the session-local reservation
list (ports already issued in the current template run) and the bind() test.
Picks the first port not excluded by any of these three checks. Writes the
chosen port to the registry immediately with a provisional entry
(process_display_name = "__pending__") so that subsequent calls within the same
template run see it as taken even if slice state new has not yet written
thinslice.toml.
On slice state new completing successfully: replaces any __pending__ rows for
this slot with final entries sourced from the [process.*] sections of the
written thinslice.toml.
On slice state new failing or being interrupted: the session tempfile is
cleaned up by slice state new, and provisional __pending__ rows for that run are
deleted. A crash before cleanup leaves orphan rows, which the reconciliation
pass handles.
Reconciliation
Registry entries can become stale when slots are deleted outside of slice
commands (e.g., rm -rf +state+/slot-a). slice give-me-a-port runs a
best-effort reconciliation pass before reading the exclusion set, with a
100 ms wall-clock budget:
- For each row in
port_assignments, check whether<project_root>/+state+/<slot_name>/thinslice.tomlexists and still declares that port for that process. - Delete any row where the file is missing or no longer claims the port.
- Stop early if the budget is exhausted — leave remaining rows for next time
or for
slice doctor.
The pass is intentionally shallow. Its goal is to prevent the most common
sources of drift (deleted slots, renamed projects) without making
slice give-me-a-port slow. It is not a consistency guarantee.
For a full, unbounded reconciliation across all known projects, run
slice doctor. This rescans every thinslice.toml file referenced in the
registry, rebuilds the global view from scratch, and reports any discrepancies
it finds.
slice debug
slice debug is a namespace of diagnostic commands for inspecting thinslice
internals. These are not part of normal workflow; they exist to help understand
what thinslice thinks is true when something seems wrong.
slice debug port <number>
Looks up a specific port in the registry and reports what thinslice believes owns it, or confirms it is unregistered. Useful when a process is failing to bind and you want to know if thinslice has already claimed that port for another slot.
slice doctor
Runs an unbounded reconciliation pass: scans every thinslice.toml referenced in
the registry, removes stale entries, and reports what it changed. Run this if
slice debug registry shows entries you do not recognize, or after manually
deleting slots or moving project directories.

