diff --git a/org/channel-tui-main.org b/org/channel-tui-main.org index 3371c85..cf4c8f8 100644 --- a/org/channel-tui-main.org +++ b/org/channel-tui-main.org @@ -756,35 +756,31 @@ supplied (e.g. \"/\"), pre-fill the select filter with it." ** Connection #+BEGIN_SRC lisp :tangle /home/user/.local/share/passepartout/lisp/channel-tui-main.lisp -(defun connect-daemon (&optional (host "127.0.0.1") (port 9105)) - (add-msg :system "* Connecting to daemon... *") - (loop for attempt from 1 to 3 - for backoff = 0 then 3 - do (sleep backoff) - (handler-case - (let ((s (usocket:socket-connect host port :timeout 5))) +(defun connect-daemon (&optional (host "127.0.0.1") (start-port 9105) (end-port 9115)) + "Connect to daemon, trying ports START-PORT to END-PORT." + (add-msg :system (format nil "* Connecting to daemon... *")) + (loop for port from start-port to end-port + do (handler-case + (let ((s (usocket:socket-connect host port :timeout 2))) (setf (st :stream) (usocket:socket-stream s) (st :connected) t) + (add-msg :system (format nil "* Connected to daemon on port ~d *" port)) (bt:make-thread (lambda () (reader-loop (st :stream))) :name "tui-reader") (return-from connect-daemon t)) - (usocket:connection-refused-error (c) - (declare (ignore c)) - (when (= attempt 3) - (add-msg :system (format nil "* No daemon on port ~a after ~a attempts *" - port attempt)))) + (usocket:connection-refused-error () + (when (= port end-port) + (add-msg :system (format nil "* No daemon on ports ~d-~d *" start-port end-port)))) (error (c) - (add-msg :system (format nil "* Connection attempt ~a failed: ~a *" - attempt c)) - (when (= attempt 3) - (add-msg :system "* TIP: run 'passepartout daemon' first *"))))) + (when (= port end-port) + (add-msg :system (format nil "* No daemon on ports ~d-~d (error: ~a) *" start-port end-port c)))))) nil) (defun disconnect-daemon () (when (st :stream) (ignore-errors (close (st :stream))) (setf (st :stream) nil (st :connected) nil) - (add-msg :system "* Disconnected *"))) + (add-msg :system (format nil "* Disconnected [now=~a] *" (now)))) #+END_SRC ** Main Loop diff --git a/org/core-pipeline.org b/org/core-pipeline.org index ad154b1..cf4708a 100644 --- a/org/core-pipeline.org +++ b/org/core-pipeline.org @@ -367,7 +367,10 @@ Boot sequence: (when (fboundp 'events-start-heartbeat) (events-start-heartbeat)) - (start-daemon) + (handler-case (start-daemon) + (error (c) + (log-message "DAEMON: Failed to start — ~a" c) + (format *error-output* "~&DAEMON: Failed to start — ~a~%" c))) #+sbcl (sb-sys:enable-interrupt sb-unix:sigint diff --git a/org/core-transport.org b/org/core-transport.org index 2c44f6e..2709699 100644 --- a/org/core-transport.org +++ b/org/core-transport.org @@ -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