Fix broadcast timeout: increase connection timeout to 15s for temporary relays, race publish against 45s overall deadline so one slow relay doesn't block
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.7.72",
|
||||
"VERSION_NUMBER": "0.7.72",
|
||||
"BUILD_DATE": "2026-06-30T13:37:36.886Z"
|
||||
"VERSION": "v0.7.73",
|
||||
"VERSION_NUMBER": "0.7.73",
|
||||
"BUILD_DATE": "2026-06-30T14:02:00.201Z"
|
||||
}
|
||||
|
||||
@@ -6204,6 +6204,21 @@ async function handlePublish(requestId, event, port) {
|
||||
const allUrls = [...new Set([...outboxWriteUrls, ...activeBroadcastUrls])];
|
||||
if (NDKRelaySet?.fromRelayUrls) {
|
||||
targetRelaySet = NDKRelaySet.fromRelayUrls(allUrls, ndk);
|
||||
// Increase the connection timeout on all relays in the set.
|
||||
// NDK's default connectionTimeout is 4400ms, which is too short
|
||||
// for hundreds of temporary broadcast relays that need to
|
||||
// establish WebSocket connections. Give them 15s to connect.
|
||||
// The publish timeout (30s below) covers the full connect+publish
|
||||
// cycle per relay.
|
||||
if (targetRelaySet?.relays) {
|
||||
for (const relay of targetRelaySet.relays) {
|
||||
try {
|
||||
if (relay.connectionTimeout !== undefined) {
|
||||
relay.connectionTimeout = 15000;
|
||||
}
|
||||
} catch (_) { /* relay may be read-only */ }
|
||||
}
|
||||
}
|
||||
console.log(`[Worker] Broadcasting to ${allUrls.length} relays ` +
|
||||
`(${activeBroadcastUrls.length} broadcast + ${outboxWriteUrls.length} outbox, ` +
|
||||
`${skippedRelayUrls.size} skipped)`);
|
||||
@@ -6283,23 +6298,48 @@ async function handlePublish(requestId, event, port) {
|
||||
|
||||
// Publish to relays (with broadcast relay set if broadcasting, else default outbox).
|
||||
// When broadcasting to many relays (potentially hundreds), use a much longer
|
||||
// timeout than NDK's default 4400ms — temporary relays need time to establish
|
||||
// WebSocket connections. Use 30s for broadcasts, default for normal publishes.
|
||||
// per-relay timeout than NDK's default 4400ms — temporary relays need time to
|
||||
// establish WebSocket connections. Use 30s per relay for broadcasts.
|
||||
// requiredRelayCount=1 so NDK doesn't throw if only 1 of 600 relays succeeds.
|
||||
const BROADCAST_TIMEOUT_MS = 30000;
|
||||
const publishTimeoutMs = isBroadcast ? BROADCAST_TIMEOUT_MS : undefined;
|
||||
const BROADCAST_PER_RELAY_TIMEOUT_MS = 30000;
|
||||
const publishTimeoutMs = isBroadcast ? BROADCAST_PER_RELAY_TIMEOUT_MS : undefined;
|
||||
const publishRequiredCount = isBroadcast ? 1 : undefined;
|
||||
|
||||
let relaySet;
|
||||
try {
|
||||
relaySet = await ndkEvent.publish(targetRelaySet, publishTimeoutMs, publishRequiredCount);
|
||||
} catch (publishError) {
|
||||
// NDK throws NDKPublishError if requiredRelayCount isn't met. With
|
||||
// requiredRelayCount=1 this shouldn't happen for broadcasts, but handle
|
||||
// it gracefully — the live progress listeners already captured whatever
|
||||
// succeeded. Log and continue so we still report results to the page.
|
||||
console.warn('[Worker] publish() threw (some relays may still have succeeded):', publishError?.message || publishError);
|
||||
relaySet = null;
|
||||
if (isBroadcast) {
|
||||
// For broadcasts, don't await the full Promise.all — NDK's publish()
|
||||
// waits for ALL relays to resolve, so one slow relay blocks the entire
|
||||
// operation. Instead, race the publish against an overall deadline.
|
||||
// The live progress listeners (relay:published / relay:publish:failed)
|
||||
// already track per-relay results in real time, so we can resolve early
|
||||
// once the deadline hits. Relays that haven't responded by the deadline
|
||||
// are simply not counted — they may still complete in the background.
|
||||
const BROADCAST_OVERALL_DEADLINE_MS = 45000; // 45s overall cap
|
||||
const publishPromise = ndkEvent.publish(targetRelaySet, publishTimeoutMs, publishRequiredCount)
|
||||
.catch((err) => {
|
||||
// NDKPublishError is expected when not all relays succeed —
|
||||
// the live listeners already captured the successes.
|
||||
console.warn('[Worker] Broadcast publish() resolved with error (expected):', err?.message || err);
|
||||
return null;
|
||||
});
|
||||
const deadlinePromise = new Promise((resolve) => {
|
||||
setTimeout(() => resolve('__DEADLINE__'), BROADCAST_OVERALL_DEADLINE_MS);
|
||||
});
|
||||
const result = await Promise.race([publishPromise, deadlinePromise]);
|
||||
if (result === '__DEADLINE__') {
|
||||
console.log(`[Worker] Broadcast overall deadline (${BROADCAST_OVERALL_DEADLINE_MS}ms) reached — ` +
|
||||
`${publishedSoFar.size} succeeded, ${failedSoFar.size + timedOutSoFar.size} failed/timeout, ` +
|
||||
`${totalTarget - publishedSoFar.size - failedSoFar.size - timedOutSoFar.size} still pending`);
|
||||
}
|
||||
relaySet = result && result !== '__DEADLINE__' ? result : null;
|
||||
} else {
|
||||
// Normal publish — await the full result as before.
|
||||
try {
|
||||
relaySet = await ndkEvent.publish(targetRelaySet, publishTimeoutMs, publishRequiredCount);
|
||||
} catch (publishError) {
|
||||
console.warn('[Worker] publish() threw:', publishError?.message || publishError);
|
||||
relaySet = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Final broadcast progress message with complete results.
|
||||
|
||||
Reference in New Issue
Block a user