fix: daemon port conflict handling, multi-port TUI connect

- start-daemon: handle ADDRESS-IN-USE-ERROR by trying ports 9105-9115
  instead of crashing. Logs which port is used.
- Add *daemon-port* defvar to track actual listening port
- main: wrap start-daemon in handler-case so the daemon doesn't
  crash if all ports are in use
- connect-daemon (TUI): try ports 9105-9115 with 2s timeout each
  instead of retrying the same port 3 times
- Add debug messages for connection success and disconnection timestamp
This commit is contained in:
2026-05-15 10:56:09 -04:00
parent 5924994202
commit d14ff3a316
3 changed files with 42 additions and 30 deletions

View File

@@ -147,6 +147,7 @@ The daemon sends a handshake message on connection, then enters a read loop, inj
#+begin_src lisp
(defvar *daemon-socket* nil)
(defvar *daemon-port* nil "The port the daemon is actually listening on (may differ from default if 9105 was in use).")
(defun client-handle-connection (socket)
"Handles a single TUI/CLI client connection in a dedicated thread."
@@ -174,18 +175,30 @@ The daemon sends a handshake message on connection, then enters a read loop, inj
(error (c) (log-message "CLIENT ERROR: ~a" c)))
(ignore-errors (usocket:socket-close socket))))
(defun start-daemon (&key (port 9105))
"Starts the network listener for TUI/CLI clients."
(setf *daemon-socket* (usocket:socket-listen "127.0.0.1" port :reuse-address t))
(log-message "DAEMON: Listening on localhost:~a" port)
(bt:make-thread
(lambda ()
(loop
(let ((client-socket (usocket:socket-accept *daemon-socket*)))
(when client-socket
(bt:make-thread (lambda () (client-handle-connection client-socket))
:name "passepartout-client-handler")))))
:name "passepartout-server-listener"))
(defun start-daemon (&key (port 9105) (max-retries 10))
"Starts the network listener for TUI/CLI clients.
If PORT is taken, tries subsequent ports up to PORT+MAX-RETRIES."
(loop for attempt from 0 below max-retries
for p = (+ port attempt)
do (handler-case
(progn
(setf *daemon-socket* (usocket:socket-listen "127.0.0.1" p :reuse-address t))
(log-message "DAEMON: Listening on localhost:~a" p)
(setf *daemon-port* p)
(bt:make-thread
(lambda ()
(loop
(let ((client-socket (usocket:socket-accept *daemon-socket*)))
(when client-socket
(bt:make-thread (lambda () (client-handle-connection client-socket))
:name "passepartout-client-handler")))))
:name "passepartout-server-listener")
(return p))
(usocket:address-in-use-error ()
(when (= attempt (1- max-retries))
(log-message "DAEMON: All ports ~d-~d in use — giving up" port (+ port max-retries -1))
(error "No available port for daemon"))
(log-message "DAEMON: Port ~d in use, trying ~d..." p (1+ p))))))
#+end_src
** Handshake Logic