Wait quietly for an absent braille display

Helper retries BrlAPI open in-process until BRLTTY answers, supervisor waits with the parked helper instead of kill/respawn cycles, exponential backoff and warn-once for real helper exits, bounded give-up so permanent failures still surface.
This commit is contained in:
hjozwiak committed 2026-11-03 10:35:00 -05:00
1 parent 6c10ed0517
commit 002e515edf
5 files changed
+279 -15

No files matched your search

+64 -13
View File
@@ -17,16 +17,55 @@ import (
"git.stormux.org/storm/cthulhu/internal/braille"
)
// A helper connection attempt blocks inside BRLTTY until BRLTTY answers,
// which is exactly when a display becomes available: BRLTTY stalls BrlAPI
// while it looks for a switched-off (e.g. Bluetooth) display, and there is
// no cheaper presence query. The session therefore waits with the single
// helper instead of killing and respawning it. brailleReadyGiveUp only
// recycles a permanently wedged helper (e.g. BRLTTY died mid-handshake).
const (
brailleReadyGiveUp = 5 * time.Minute
brailleReadyNotice = 10 * time.Second
)
const brailleMaxBackoff = 30 * time.Second
// nextBrailleBackoff grows the delay between helper connection attempts so
// an absent display (switched off, BRLTTY not answering) is retried on a
// slow cadence instead of respawning the helper every few seconds forever.
func nextBrailleBackoff(current time.Duration) time.Duration {
if current <= 0 {
return time.Second
}
return min(current*2, brailleMaxBackoff)
}
func (worker *brailleWorker) run(ctx context.Context, done chan struct{}) {
defer close(done)
backoff := time.Duration(0)
lastError := ""
for ctx.Err() == nil {
if err := worker.session(ctx); err != nil && ctx.Err() == nil {
worker.logger.Warn("braille helper disconnected", "error", err)
started := time.Now()
err := worker.session(ctx)
if err != nil && ctx.Err() == nil {
if err.Error() != lastError {
worker.logger.Warn("braille helper disconnected", "error", err)
lastError = err.Error()
} else {
worker.logger.Debug("braille helper still unavailable", "error", err)
}
}
// A session that stayed up a while was a real connection; do not
// keep growing the delay after it ends.
if time.Since(started) > 10*time.Second {
backoff = 0
lastError = ""
}
backoff = nextBrailleBackoff(backoff)
select {
case <-ctx.Done():
return
case <-time.After(time.Second):
case <-time.After(backoff):
}
}
}
@@ -102,17 +141,29 @@ func (worker *brailleWorker) session(ctx context.Context) (result error) {
}
readErrors <- err
}()
timer := time.NewTimer(2 * time.Second)
defer timer.Stop()
select {
case err := <-ready:
if err != nil {
return err
giveUp := worker.readyGiveUp
if giveUp <= 0 {
giveUp = brailleReadyGiveUp
}
giveUpTimer := time.NewTimer(giveUp)
defer giveUpTimer.Stop()
notice := time.NewTimer(brailleReadyNotice)
defer notice.Stop()
readyWait:
for {
select {
case err := <-ready:
if err != nil {
return err
}
break readyWait
case <-notice.C:
worker.logger.Info("waiting for braille display")
case <-giveUpTimer.C:
return errors.New("braille helper connection timed out")
case <-ctx.Done():
return nil
}
case <-timer.C:
return errors.New("braille helper connection timed out")
case <-ctx.Done():
return nil
}
worker.logger.Info("braille helper connected")
worker.mu.Lock()