Changeset 4ae254a in Klonkt for src/services/ap-transport.js
- Timestamp:
- 10/26/2026 01:07:44 PM (27 hours ago)
- Branches:
- main
- Children:
- 31288dc
- Parents:
- 5d0d3f35
- File:
-
- 1 edited
-
src/services/ap-transport.js (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/ap-transport.js
r5d0d3f35 r4ae254a 193 193 // doesn't silently miss the post. The signing key is NOT stored — the worker 194 194 // re-derives it from the actor slug at send time. 195 const DELIVERY_MAX_ATTEMPTS = 6; 196 const DELIVERY_BACKOFF_MIN = [1, 5, 15, 60, 180, 360]; 197 let _insDeliv, _dueDeliv, _delDeliv, _bumpDeliv; 195 // 196 // KLONKT DIE NIET ALTIJD AAN STAAT (shaer-xzab). Een Klonkt op een Mac thuis 197 // slaapt, gaat de trein in, staat een weekend uit. Daarom: 198 // 199 // - Een bezorging VERLOOPT OP LEEFTIJD (5 dagen sinds created_at), niet na een 200 // aantal pogingen (xzab.1). Zes pogingen waren ~4,5 uur, en de laatste trede 201 // van 360 minuten werd nooit gebruikt: de telling was er eerder. 202 // - OFFLINE ZIJN KOST GEEN POGINGEN (xzab.2). Mislukt een hele beurt op 203 // netwerkniveau, naar meer dan één server en zonder één HTTP-antwoord, dan 204 // ligt het aan ons: alles een paar minuten opschuiven, de telling ongemoeid, 205 // en niets laten verlopen. Slapen kost al niets, want dan loopt de timer niet. 206 // - BACKOFF PER SERVER, VOLGORDE PER INBOX (xzab.3). Eén mislukking naar een 207 // server schuift al zijn rijen op. En per inbox oudste eerst, ook bij het 208 // directe versturen: bij een venster van dagen mag een herhaalde Create niet 209 // NA de Delete van dezelfde post aankomen. 210 const DELIVERY_MAX_AGE_MS = 5 * 24 * 60 * 60 * 1000; 211 // Minuten tot de volgende poging, op de poging die net mislukte; de laatste 212 // trede herhaalt tot de bezorging verloopt. 213 const DELIVERY_BACKOFF_MIN = [1, 5, 15, 60, 180, 360, 720, 1440]; 214 // Hoe ver een beurt opschuift als WIJ offline blijken. 215 const OFFLINE_RETRY_MIN = 2; 216 217 export function deliveryBackoffMinutes(attemptsSoFar) { 218 return DELIVERY_BACKOFF_MIN[Math.min(Math.max(0, attemptsSoFar), DELIVERY_BACKOFF_MIN.length - 1)]; 219 } 220 221 /** De host (met poort) van een inbox, in kleine letters; null als het geen URL is. */ 222 export function inboxHost(inbox) { 223 try { return new URL(inbox).host.toLowerCase(); } catch { return null; } 224 } 225 226 /** Is een bezorging zo oud dat hij opgegeven wordt? created_at is SQLite-UTC 227 * ('YYYY-MM-DD HH:MM:SS', zonder zone). */ 228 export function deliveryExpired(createdAt, now = Date.now()) { 229 if (!createdAt) return false; 230 const t = Date.parse(String(createdAt).replace(' ', 'T') + (/[zZ]|[+-]\d\d:?\d\d$/.test(createdAt) ? '' : 'Z')); 231 return Number.isFinite(t) && now - t >= DELIVERY_MAX_AGE_MS; 232 } 233 234 /** Faalde dit op NETWERKNIVEAU (geen HTTP-antwoord)? Een SSRF-weigering is 235 * dat niet: die is een besluit, en blijft een besluit als we weer online zijn. */ 236 function isNetworkError(e) { 237 return !/^ssrf/.test(String((e && e.message) || '')); 238 } 239 240 let _insDeliv, _dueDeliv, _delDeliv, _bumpDeliv, _hostDeliv, _offDeliv, _pendInbox, _hostWaits; 198 241 function deliveryStmts() { 199 242 if (!_insDeliv) { 200 _insDeliv = db.prepare('INSERT INTO ap_delivery (slug, inbox, body, attempts, next_at) VALUES (?,?,?,0,CURRENT_TIMESTAMP)'); 201 _dueDeliv = db.prepare("SELECT * FROM ap_delivery WHERE datetime(next_at) <= datetime('now') ORDER BY next_at LIMIT 30"); 243 _insDeliv = db.prepare('INSERT INTO ap_delivery (slug, inbox, host, body, attempts, next_at) VALUES (?,?,?,?,0,CURRENT_TIMESTAMP)'); 244 // Alleen de OUDSTE openstaande rij per inbox: een nieuwere wacht op hem, ook 245 // als hij zelf al aan de beurt zou zijn. Op id, want dat is de volgorde 246 // waarin ze ontstonden. 247 _dueDeliv = db.prepare(`SELECT d.* FROM ap_delivery d 248 WHERE datetime(d.next_at) <= datetime('now') 249 AND NOT EXISTS (SELECT 1 FROM ap_delivery o WHERE o.inbox = d.inbox AND o.id < d.id) 250 ORDER BY d.id LIMIT 30`); 202 251 _delDeliv = db.prepare('DELETE FROM ap_delivery WHERE id = ?'); 203 252 _bumpDeliv = db.prepare('UPDATE ap_delivery SET attempts = ?, next_at = ? WHERE id = ?'); 204 } 205 return { ins: _insDeliv, due: _dueDeliv, del: _delDeliv, bump: _bumpDeliv }; 253 // Een server die faalde: al zijn rijen niet eerder dan de nieuwe poging. 254 _hostDeliv = db.prepare('UPDATE ap_delivery SET next_at = ? WHERE host = ? AND datetime(next_at) < datetime(?)'); 255 // Wij offline: opschuiven, de telling ongemoeid. 256 _offDeliv = db.prepare('UPDATE ap_delivery SET next_at = ? WHERE id = ?'); 257 _pendInbox = db.prepare('SELECT 1 FROM ap_delivery WHERE inbox = ? LIMIT 1'); 258 _hostWaits = db.prepare("SELECT 1 FROM ap_delivery WHERE host = ? AND datetime(next_at) > datetime('now') LIMIT 1"); 259 } 260 return { ins: _insDeliv, due: _dueDeliv, del: _delDeliv, bump: _bumpDeliv, host: _hostDeliv, off: _offDeliv, pendInbox: _pendInbox, hostWaits: _hostWaits }; 206 261 } 207 262 export function enqueueDelivery(slug, inbox, activity) { 208 263 if (!slug || !inbox || !activity) return; 209 try { deliveryStmts().ins.run(slug, inbox, JSON.stringify(activity)); } catch { /* ignore */ }264 try { deliveryStmts().ins.run(slug, inbox, inboxHost(inbox), JSON.stringify(activity)); } catch { /* ignore */ } 210 265 } 211 266 // Record delivery health per follower so the followers list can flag dead accounts. … … 224 279 } 225 280 // Deliver now; queue for retry if it fails. 281 // 282 // Niet meteen als er voor deze inbox al iets in de wachtrij staat (dan zou dit 283 // er voorbij gaan: een Delete vóór de Create die nog wacht), en niet als deze 284 // server net faalde en in zijn backoff zit (dan kost het alleen acht seconden 285 // time-out om hetzelfde te horen). Dan achteraan in de rij. 226 286 export async function deliverWithRetry(slug, inbox, activity, keyId, privPem) { 227 287 if (!inbox) return; 288 try { 289 const st = deliveryStmts(); 290 if (st.pendInbox.get(inbox) || st.hostWaits.get(inboxHost(inbox))) { enqueueDelivery(slug, inbox, activity); return; } 291 } catch { /* geen wachtrij te lezen: gewoon proberen */ } 228 292 try { const st = await deliver(inbox, activity, keyId, privPem); if (st >= 200 && st < 300) { markFollowerDelivery(slug, inbox, true); return; } } catch { /* queue below */ } 229 293 enqueueDelivery(slug, inbox, activity); … … 238 302 if (!rows || !rows.length) return; 239 303 const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''); 304 // Eerst alles proberen, pas daarna beslissen: of het aan ons lag, zie je 305 // pas aan de hele beurt. 306 const failed = []; // { row, network } 307 const down = new Set(); // servers die in deze beurt al faalden 308 const skipped = []; // rijen van zo'n server, niet meer geprobeerd 309 let answered = false; // kwam er ergens een HTTP-antwoord? 240 310 for (const row of rows) { 241 let ok = false; 311 const host = row.host || inboxHost(row.inbox); 312 if (host && down.has(host)) { skipped.push(row); continue; } 313 let ok = false, network = false; 242 314 try { 243 315 const keys = getOrCreateKeys(row.slug); 244 316 const st = await deliver(row.inbox, JSON.parse(row.body), `${actorId(base, row.slug)}#main-key`, keys.private_pem); 317 answered = true; 245 318 ok = st >= 200 && st < 300; 246 } catch { ok = false; }319 } catch (e) { network = isNetworkError(e); } 247 320 if (ok) { markFollowerDelivery(row.slug, row.inbox, true); deliveryStmts().del.run(row.id); continue; } 248 const attempts = row.attempts + 1; 249 if (attempts >= DELIVERY_MAX_ATTEMPTS) { markFollowerDelivery(row.slug, row.inbox, false); deliveryStmts().del.run(row.id); console.warn('[AP] delivery gave up after', attempts, 'tries →', row.inbox); continue; } 250 // Index the backoff on the CURRENT attempt count (row.attempts) so the first 251 // retry uses the 1-min tier instead of skipping it. 252 const mins = DELIVERY_BACKOFF_MIN[Math.min(row.attempts, DELIVERY_BACKOFF_MIN.length - 1)]; 253 deliveryStmts().bump.run(attempts, new Date(Date.now() + mins * 60000).toISOString(), row.id); 321 failed.push({ row, network, host }); 322 if (host) down.add(host); 323 } 324 if (!failed.length) return; 325 326 // Lag het aan ons? Niets beantwoord, alles op netwerkniveau, en naar meer 327 // dan één server -- bij één server is "hij ligt eruit" net zo waarschijnlijk. 328 const hosts = new Set(failed.map((f) => f.host)); 329 const offline = !answered && failed.every((f) => f.network) && hosts.size > 1; 330 if (offline) { 331 const later = new Date(Date.now() + OFFLINE_RETRY_MIN * 60000).toISOString(); 332 for (const r of [...failed.map((f) => f.row), ...skipped]) deliveryStmts().off.run(later, r.id); 333 console.warn('[AP] delivery: no network for', failed.length, 'deliveries to', hosts.size, 'servers; retrying in', OFFLINE_RETRY_MIN, 'min, attempts untouched'); 334 return; 335 } 336 337 for (const { row, host } of failed) { 338 if (deliveryExpired(row.created_at)) { 339 markFollowerDelivery(row.slug, row.inbox, false); 340 deliveryStmts().del.run(row.id); 341 console.warn('[AP] delivery gave up after', row.attempts + 1, 'tries over 5 days →', row.inbox); 342 continue; 343 } 344 const next = new Date(Date.now() + deliveryBackoffMinutes(row.attempts) * 60000).toISOString(); 345 deliveryStmts().bump.run(row.attempts + 1, next, row.id); 346 // De hele server wacht mee: zijn andere rijen niet eerder dan deze. 347 if (host) deliveryStmts().host.run(next, host, next); 254 348 } 255 349 } finally { _processingDeliv = false; }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)