fix: log returns as soon as the QSO row is inserted; award_refs/recording/uploads/eQSL now run off the critical path (both manual and UDP) — a busy multi-op MySQL no longer freezes the entry form ~40s waiting on the award_refs UPDATE

This commit is contained in:
2026-07-21 16:20:49 +02:00
parent ea1bd2a66d
commit aa537f9eea
+37 -22
View File
@@ -1948,25 +1948,36 @@ func (a *App) AddQSO(q qso.QSO) (id int64, err error) {
} }
if err == nil { if err == nil {
q.ID = id q.ID = id
a.noteWorked(q.Callsign, q.Band, q.Mode) // keep the alert worked-index fresh 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" a.noteLiveQSO() // multi-op: flip this operator back "online" (publishes async)
a.materializeAwardRefs(q) // stamp award_refs so the grid columns show at once // Announce the log RIGHT AWAY so the grid/UI refresh at once and the entry
// Announce the log so UI widgets can react (e.g. the Flex panel zeroing RIT). // form clears immediately — the operator is not made to wait on the DB.
wruntime.EventsEmit(a.ctx, "qso:logged", id) wruntime.EventsEmit(a.ctx, "qso:logged", id)
a.saveQSORecording(&q) // 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 { if a.extsvc != nil {
a.extsvc.OnQSOLogged(id) a.extsvc.OnQSOLogged(id)
} }
a.maybeAutoSendEQSL(q) a.maybeAutoSendEQSL(qc)
// Forward the ADIF of this QSO to any outbound "ADIF message" UDP rows.
if a.udp != nil { if a.udp != nil {
go a.udp.EmitLoggedADIF(adif.SingleRecordADIF(q)) a.udp.EmitLoggedADIF(adif.SingleRecordADIF(qc))
} }
// A successful write means the link is healthy again — if QSOs are still // Nudge the grid again so the award_refs columns appear once materialised.
// parked, get them in now rather than waiting for the next tick. 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 { if a.offlineMode && a.offlineQ != nil && a.offlineQ.Count() > 0 {
go func() { _, _ = a.replayOfflineQueue() }() _, _ = a.replayOfflineQueue()
} }
}()
} }
return id, err return id, err
} }
@@ -9634,24 +9645,28 @@ func (a *App) LogUDPLoggedADIF(adifText string) (int64, error) {
return 0, fmt.Errorf("insert qso: %w", err) return 0, fmt.Errorf("insert qso: %w", err)
} }
q.ID = id q.ID = id
a.noteLiveQSO() // multi-op: flip this operator back "online" a.noteLiveQSO() // multi-op: flip this operator back "online" (publishes async)
a.materializeAwardRefs(q) // Announce the log AT ONCE so the grid / ON-AIR badge / stations-on-air widget
// Announce the log so UI widgets refresh AT ONCE (the Recent-QSOs grid, the // refresh immediately, then run the DB-heavy enrichment off the critical path
// ON-AIR badge and the "stations on air" widget). The manual log path always // (same reasoning as the manual path award_refs on a busy shared MySQL blocked).
// 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.
if a.ctx != nil { if a.ctx != nil {
wruntime.EventsEmit(a.ctx, "qso:logged", id) wruntime.EventsEmit(a.ctx, "qso:logged", id)
} }
a.saveQSORecording(&q) qc := q
go func() {
a.materializeAwardRefs(qc)
a.saveQSORecording(&qc)
if a.extsvc != nil { if a.extsvc != nil {
a.extsvc.OnQSOLogged(id) a.extsvc.OnQSOLogged(id)
} }
a.maybeAutoSendEQSL(q) a.maybeAutoSendEQSL(qc)
// A successful write means the link is healthy again — flush anything parked. if a.ctx != nil {
if a.offlineMode && a.offlineQ != nil && a.offlineQ.Count() > 0 { wruntime.EventsEmit(a.ctx, "qso:logged", id) // refresh again so award_refs show
go func() { _, _ = a.replayOfflineQueue() }()
} }
if a.offlineMode && a.offlineQ != nil && a.offlineQ.Count() > 0 {
_, _ = a.replayOfflineQueue()
}
}()
return id, nil return id, nil
} }