From aa537f9eeab99156651a63b764859b46feb96926 Mon Sep 17 00:00:00 2001 From: rouggy Date: Tue, 21 Jul 2026 16:20:49 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20log=20returns=20as=20soon=20as=20the=20Q?= =?UTF-8?q?SO=20row=20is=20inserted;=20award=5Frefs/recording/uploads/eQSL?= =?UTF-8?q?=20now=20run=20off=20the=20critical=20path=20(both=20manual=20a?= =?UTF-8?q?nd=20UDP)=20=E2=80=94=20a=20busy=20multi-op=20MySQL=20no=20long?= =?UTF-8?q?er=20freezes=20the=20entry=20form=20~40s=20waiting=20on=20the?= =?UTF-8?q?=20award=5Frefs=20UPDATE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.go | 81 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/app.go b/app.go index 54c727b..035101c 100644 --- a/app.go +++ b/app.go @@ -1948,25 +1948,36 @@ func (a *App) AddQSO(q qso.QSO) (id int64, err error) { } if err == nil { q.ID = id - a.noteWorked(q.Callsign, q.Band, q.Mode) // keep the alert worked-index fresh - a.noteLiveQSO() // multi-op: flip this operator back "online" - a.materializeAwardRefs(q) // stamp award_refs so the grid columns show at once - // Announce the log so UI widgets can react (e.g. the Flex panel zeroing RIT). + a.noteWorked(q.Callsign, q.Band, q.Mode) // keep the alert worked-index fresh (in-memory) + a.noteLiveQSO() // multi-op: flip this operator back "online" (publishes async) + // Announce the log RIGHT AWAY so the grid/UI refresh at once and the entry + // form clears immediately — the operator is not made to wait on the DB. wruntime.EventsEmit(a.ctx, "qso:logged", id) - a.saveQSORecording(&q) - if a.extsvc != nil { - a.extsvc.OnQSOLogged(id) - } - a.maybeAutoSendEQSL(q) - // Forward the ADIF of this QSO to any outbound "ADIF message" UDP rows. - if a.udp != nil { - go a.udp.EmitLoggedADIF(adif.SingleRecordADIF(q)) - } - // A successful write means the link is healthy again — if QSOs are still - // parked, get them in now rather than waiting for the next tick. - if a.offlineMode && a.offlineQ != nil && a.offlineQ.Count() > 0 { - go func() { _, _ = a.replayOfflineQueue() }() - } + // Everything below writes to (or reads from) the shared DB. Run it OFF the + // critical path: on a busy multi-op MySQL the award_refs UPDATE alone could + // block ~40s (and hit "Too many connections"), which used to freeze the log + // (form stuck, "…" on the button) until it finished. The QSO row is already + // safely inserted; these just enrich it, so they can lag a beat. + qc := q + go func() { + a.materializeAwardRefs(qc) // fills the award_refs column + a.saveQSORecording(&qc) + if a.extsvc != nil { + a.extsvc.OnQSOLogged(id) + } + a.maybeAutoSendEQSL(qc) + if a.udp != nil { + a.udp.EmitLoggedADIF(adif.SingleRecordADIF(qc)) + } + // Nudge the grid again so the award_refs columns appear once materialised. + if a.ctx != nil { + wruntime.EventsEmit(a.ctx, "qso:logged", id) + } + // A successful write means the link is healthy — flush any parked QSOs. + if a.offlineMode && a.offlineQ != nil && a.offlineQ.Count() > 0 { + _, _ = a.replayOfflineQueue() + } + }() } return id, err } @@ -9634,24 +9645,28 @@ func (a *App) LogUDPLoggedADIF(adifText string) (int64, error) { return 0, fmt.Errorf("insert qso: %w", err) } q.ID = id - a.noteLiveQSO() // multi-op: flip this operator back "online" - a.materializeAwardRefs(q) - // Announce the log so UI widgets refresh AT ONCE (the Recent-QSOs grid, the - // ON-AIR badge and the "stations on air" widget). The manual log path always - // did this; the UDP/FT8 path did not, so a station running MSHV saw the top - // "on air" list lag ~15-45s behind the instant bottom badge. + a.noteLiveQSO() // multi-op: flip this operator back "online" (publishes async) + // Announce the log AT ONCE so the grid / ON-AIR badge / stations-on-air widget + // refresh immediately, then run the DB-heavy enrichment off the critical path + // (same reasoning as the manual path — award_refs on a busy shared MySQL blocked). if a.ctx != nil { wruntime.EventsEmit(a.ctx, "qso:logged", id) } - a.saveQSORecording(&q) - if a.extsvc != nil { - a.extsvc.OnQSOLogged(id) - } - a.maybeAutoSendEQSL(q) - // A successful write means the link is healthy again — flush anything parked. - if a.offlineMode && a.offlineQ != nil && a.offlineQ.Count() > 0 { - go func() { _, _ = a.replayOfflineQueue() }() - } + qc := q + go func() { + a.materializeAwardRefs(qc) + a.saveQSORecording(&qc) + if a.extsvc != nil { + a.extsvc.OnQSOLogged(id) + } + a.maybeAutoSendEQSL(qc) + if a.ctx != nil { + wruntime.EventsEmit(a.ctx, "qso:logged", id) // refresh again so award_refs show + } + if a.offlineMode && a.offlineQ != nil && a.offlineQ.Count() > 0 { + _, _ = a.replayOfflineQueue() + } + }() return id, nil }