fix(clublog): pace and cap the real-time deletes
Club Log wrote to this station about 167 requests in four minutes and a coming IP block. That was one deletion of a couple of hundred rows going through delete.php, which is a REAL-TIME endpoint: it exists for an operator removing a contact they just logged wrongly, at human pace. Today's earlier fix stops the requests that were pointless -- QSOs Club Log never had. This one handles the rest, which are legitimate but still a batch: one delete every 1.2 s, and a stop at 25 with a line saying to finish on clublog.org. There is no bulk-delete API to move to, so going slower and refusing to push a large deletion through a real-time endpoint is the whole of the answer. Costs the operator nothing: the withdrawals already run in the background.
This commit is contained in:
@@ -6510,6 +6510,21 @@ func (a *App) deleteRemoteCopies(ids []int64) {
|
||||
// withdrawals stop. Three is enough to tell a one-off from a blocked account.
|
||||
const maxClublogRefusals = 3
|
||||
|
||||
// Club Log's delete endpoint is a REAL-TIME one: it exists for an operator
|
||||
// removing a contact they just logged wrongly, at human pace. Club Log watches
|
||||
// the rate and blocks the IP of anything that batches through it — they wrote
|
||||
// to this station about 167 requests in four minutes, which was one deletion of
|
||||
// a couple of hundred rows, not a pile-up.
|
||||
//
|
||||
// There is no bulk-delete API to move to, so the only honest answer is to go at
|
||||
// the pace the endpoint is meant for and to stop rather than push a large
|
||||
// deletion through it. Whoever needs to remove hundreds of QSOs from Club Log
|
||||
// does it on their site, where the tool for it exists.
|
||||
const (
|
||||
clublogDeletePace = 1200 * time.Millisecond
|
||||
maxClublogDeletes = 25
|
||||
)
|
||||
|
||||
// clublogWasUploaded reports whether this QSO ever reached Club Log. "M"
|
||||
// (modified since upload) counts: the copy is there, it is merely out of date.
|
||||
func clublogWasUploaded(status string) bool {
|
||||
@@ -6523,6 +6538,7 @@ func clublogWasUploaded(status string) bool {
|
||||
func (a *App) withdrawRemoteCopies(rows []qso.QSO, cfg extsvc.ExternalServices, doQRZ, doClublog bool) {
|
||||
started := time.Now()
|
||||
clublogRefused := 0
|
||||
clublogSent := 0
|
||||
for i := range rows {
|
||||
q := rows[i]
|
||||
id := q.ID
|
||||
@@ -6553,6 +6569,17 @@ func (a *App) withdrawRemoteCopies(rows []qso.QSO, cfg extsvc.ExternalServices,
|
||||
if doClublog && !clublogWasUploaded(q.ClublogUploadStatus) {
|
||||
applog.Printf("extsvc: QSO %d (%s) was never uploaded to Club Log — nothing to withdraw", id, q.Callsign)
|
||||
} else if doClublog {
|
||||
if clublogSent >= maxClublogDeletes {
|
||||
applog.Printf("extsvc: %d QSOs already withdrawn from Club Log — stopping there. Their delete endpoint is for one contact at a time; remove the rest on clublog.org, which has a tool for it.", clublogSent)
|
||||
doClublog = false
|
||||
continue
|
||||
}
|
||||
if clublogSent > 0 {
|
||||
// Paced deliberately: see clublogDeletePace. This runs in the
|
||||
// background, so the wait costs the operator nothing.
|
||||
time.Sleep(clublogDeletePace)
|
||||
}
|
||||
clublogSent++
|
||||
if msg, err := extsvc.DeleteClublog(a.ctx, nil, cfg.Clublog, q.Callsign, q.QSODate, q.Band); err != nil {
|
||||
clublogRefused++
|
||||
applog.Printf("extsvc: Club Log delete of QSO %d (%s) failed: %v", id, q.Callsign, err)
|
||||
|
||||
Reference in New Issue
Block a user