From 775f411606aba5c9c56cc32fcd6eb5b38a251823 Mon Sep 17 00:00:00 2001 From: rouggy Date: Wed, 29 Jul 2026 21:43:05 +0200 Subject: [PATCH] fix: Yaesu Connect leaked the serial handle on every reconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Connect assigned a fresh handle to y.port without closing the one it already held. It is called again on every reconnect, so a rig that fails to answer leaks one handle per attempt — every few seconds, indefinitely. Windows opens a serial port EXCLUSIVELY, which makes this visible as "yaesu: open COM7 @ 9600 baud: Serial port busy" in an operator's log: OpsLog was competing with itself for the port. This is a real defect found while reading the reconnect loop, but I want to be straight about its limits: it does NOT explain the seven minutes of "ID query failed — timeout" in the same log, where the port opened and the rig stayed silent. That one is still open, and the operator alternates between two separate radios (a TCI SDR and the Yaesu) rather than sharing one port, so a conflict between backends is not the explanation either. --- changelog.json | 6 ++++-- internal/cat/yaesu.go | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/changelog.json b/changelog.json index ca01adf..e2551c2 100644 --- a/changelog.json +++ b/changelog.json @@ -10,7 +10,8 @@ "Clicking a spot in DXHunter can now tune the radio: a remote-call UDP packet carrying and sets the frequency and mode on whichever CAT backend is active, as well as filling the callsign. A packet without them still just fills the callsign.", "The ADIF import option that fills your station fields from your profile also stamps your default confirmation statuses (paper QSL, LoTW, eQSL, Club Log, HRDLog, QRZ.com) on the ones the file leaves empty — it always did, but nothing said so, and a WSJT-X log carries almost none. Existing confirmations are never overwritten.", "QRZ.com confirmation download marked far too many QSOs as confirmed: it accepted the paper-QSL received flag, which you uploaded to QRZ yourself, as if it were a QRZ confirmation. Only QRZ own confirmation now counts — check the QRZ received column after your next download, as earlier ones may have set it wrongly.", - "The Recent QSOs counter labels each of its three numbers — shown, matching the filter, and in the log — with thousands separators. \"Showing 10000 of 23683 matches · 28648 total\" read as if the filter had found more QSOs than the log holds." + "The Recent QSOs counter labels each of its three numbers — shown, matching the filter, and in the log — with thousands separators. \"Showing 10000 of 23683 matches · 28648 total\" read as if the filter had found more QSOs than the log holds.", + "Switching the CAT backend back to a Yaesu no longer risks leaving the serial port held. Each reconnect opened a new handle without closing the previous one, and Windows opens a serial port exclusively, so the next attempt could fail with \"port busy\"." ], "fr": [ "Cliquer sur un spot du cluster en écoutant sur le VFO SUB ne ramène plus la radio sur le VFO principal. La commande utilisée pour s'accorder agit par définition sur le VFO principal ; sur SUB, c'est désormais le VFO secondaire qui est écrit, sans toucher à la sélection.", @@ -20,7 +21,8 @@ "Cliquer un spot dans DXHunter peut désormais accorder la radio : un paquet UDP remote_call contenant et règle la fréquence et le mode sur le backend CAT actif, en plus de renseigner l'indicatif. Un paquet sans ces balises se contente, comme avant, de renseigner l'indicatif.", "L'option d'import ADIF qui remplit vos champs station depuis votre profil applique aussi vos statuts de confirmation par défaut (QSL papier, LoTW, eQSL, Club Log, HRDLog, QRZ.com) sur ceux que le fichier laisse vides — c'était déjà le cas, mais rien ne le disait, et un log WSJT-X n'en contient pratiquement aucun. Les confirmations existantes ne sont jamais écrasées.", "Le téléchargement des confirmations QRZ.com marquait beaucoup trop de QSO comme confirmés : il acceptait l'indicateur de QSL papier reçue — que vous avez vous-même envoyé à QRZ — comme une confirmation QRZ. Seule la confirmation propre à QRZ compte désormais ; vérifiez la colonne QRZ.com reçu après votre prochain téléchargement, les précédents ayant pu la remplir à tort.", - "Le compteur des QSO récents nomme chacun de ses trois nombres — affichés, correspondant au filtre, et présents dans le log — avec des séparateurs de milliers. « Showing 10000 of 23683 matches · 28648 total » se lisait comme si le filtre trouvait plus de QSO que le log n'en contient." + "Le compteur des QSO récents nomme chacun de ses trois nombres — affichés, correspondant au filtre, et présents dans le log — avec des séparateurs de milliers. « Showing 10000 of 23683 matches · 28648 total » se lisait comme si le filtre trouvait plus de QSO que le log n'en contient.", + "Repasser le backend CAT sur un Yaesu ne risque plus de laisser le port série occupé. Chaque reconnexion ouvrait une nouvelle poignée sans fermer la précédente, et Windows ouvre un port série en exclusivité : la tentative suivante pouvait échouer avec « port occupé »." ] }, { diff --git a/internal/cat/yaesu.go b/internal/cat/yaesu.go index 99ce0a8..856298c 100644 --- a/internal/cat/yaesu.go +++ b/internal/cat/yaesu.go @@ -135,6 +135,17 @@ func (y *Yaesu) Connect() error { if y.portName == "" { return fmt.Errorf("yaesu: no serial port configured") } + // Close any handle we still hold before opening another. + // + // Connect is called again on every reconnect, and it used to overwrite y.port + // with a fresh handle and leak the old one. Windows opens a serial port + // EXCLUSIVELY, so a leaked handle makes the next open fail with "Serial port + // busy" — seen in the field — and the retry loop then leaks one handle per + // attempt, once every few seconds, for as long as it keeps failing. + if y.port != nil { + _ = y.port.Close() + y.port = nil + } p, err := serial.Open(y.portName, &serial.Mode{BaudRate: y.baud}) if err != nil { return fmt.Errorf("yaesu: open %s @ %d baud: %w", y.portName, y.baud, err)