Class: Portless::RouteStore
- Inherits:
-
Object
- Object
- Portless::RouteStore
- Defined in:
- lib/portless/route_store.rb
Overview
The on-disk routing table (routes.json): host → backend port → owning pid. No daemon API — apps register/deregister by editing this file under a directory mutex (atomic mkdir), and the proxy watches it. Dead-pid entries are reaped on every load. Mirrors portless's RouteStore.
Defined Under Namespace
Classes: Route
Constant Summary collapse
- LOCK_STALE_SECONDS =
10- LOCK_BUDGET_SECONDS =
5
Instance Method Summary collapse
-
#add(hostname:, port:, pid:, force: false, tailscale: nil, ngrok: nil) ⇒ Object
Register (or replace) a route.
-
#initialize(file: State.routes_file, lock: State.routes_lock) ⇒ RouteStore
constructor
A new instance of RouteStore.
-
#prune ⇒ Object
Drop dead-pid routes and return them (so callers can reap the orphaned process still holding the backend port).
-
#remove(hostname, owner_pid: nil) ⇒ Object
Remove a route only if still owned by
owner_pid(so a force-replaced predecessor doesn't delete the successor's route on its way out). - #routes ⇒ Object
Constructor Details
#initialize(file: State.routes_file, lock: State.routes_lock) ⇒ RouteStore
Returns a new instance of RouteStore.
19 20 21 22 |
# File 'lib/portless/route_store.rb', line 19 def initialize(file: State.routes_file, lock: State.routes_lock) @file = file @lock = lock end |
Instance Method Details
#add(hostname:, port:, pid:, force: false, tailscale: nil, ngrok: nil) ⇒ Object
Register (or replace) a route. Conflicts with a live different owner raise
unless force, which SIGTERMs the incumbent. Alias routes use pid 0. Public
share URLs (tailscale/ngrok), when present, are recorded so list can show
them while the run is active.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/portless/route_store.rb', line 35 def add(hostname:, port:, pid:, force: false, tailscale: nil, ngrok: nil) with_lock do all = load.reject { |r| dead?(r["pid"]) } existing = all.find { |r| r["hostname"] == hostname } if existing && existing["pid"].to_i != pid.to_i && !dead?(existing["pid"]) unless force raise RouteConflictError, "#{hostname} is already served by pid #{existing['pid']} — pass --force to take it over" end terminate(existing["pid"]) end all.reject! { |r| r["hostname"] == hostname } entry = { "hostname" => hostname, "port" => port, "pid" => pid } entry["tailscale"] = tailscale if tailscale entry["ngrok"] = ngrok if ngrok all << entry write(all) end end |
#prune ⇒ Object
Drop dead-pid routes and return them (so callers can reap the orphaned process still holding the backend port). Alias routes (pid 0) are kept.
71 72 73 74 75 76 77 |
# File 'lib/portless/route_store.rb', line 71 def prune with_lock do dead, alive = load.partition { |r| dead?(r["pid"]) } write(alive) dead.map { |r| Route.new(hostname: r["hostname"], port: r["port"], pid: r["pid"]) } end end |
#remove(hostname, owner_pid: nil) ⇒ Object
Remove a route only if still owned by owner_pid (so a force-replaced
predecessor doesn't delete the successor's route on its way out). Returns
whether anything was actually removed.
58 59 60 61 62 63 64 65 66 67 |
# File 'lib/portless/route_store.rb', line 58 def remove(hostname, owner_pid: nil) with_lock do before = load after = before.reject do |r| r["hostname"] == hostname && (owner_pid.nil? || r["pid"].to_i == owner_pid.to_i) end write(after) after.size < before.size end end |