Browse Source

fix frozen publish

imwald
Silberengel 1 month ago
parent
commit
fb4b622f3c
  1. 6
      src/constants.ts
  2. 56
      src/services/client.service.ts

6
src/constants.ts

@ -126,6 +126,12 @@ export const PUBLIC_MESSAGE_RSVP_PUBLISH_AUTHOR_WRITE_CAP = 10 @@ -126,6 +126,12 @@ export const PUBLIC_MESSAGE_RSVP_PUBLISH_AUTHOR_WRITE_CAP = 10
/** After a publish wave, failed NIP-65 write (outbox) relays are retried once after this delay. */
export const OUTBOX_PUBLISH_RETRY_DELAY_MS = 5000
/**
* After the first relay accepts a publish, resolve {@link ClientService.publishEvent} after this many ms
* so the UI does not wait for every slow or dead relay (callers typically only need 1 success).
*/
export const EARLY_PUBLISH_SUCCESS_GRACE_MS = 1200
/**
* Cap how long we wait on NIP-65 / inbox relay-list resolution (including `fetchRelayLists` network phase
* and kind-10432 fetch) before publishing or falling back to IndexedDB-only merge.

56
src/services/client.service.ts

@ -23,6 +23,7 @@ import { @@ -23,6 +23,7 @@ import {
RELAY_READ_ONLY_POOL_CONNECT_TIMEOUT_MS,
TIMELINE_SHARD_SUBSCRIBE_CONCURRENCY,
OUTBOX_PUBLISH_RETRY_DELAY_MS,
EARLY_PUBLISH_SUCCESS_GRACE_MS,
DEFAULT_FAVORITE_RELAYS,
NIP66_DISCOVERY_RELAY_URLS,
PROFILE_FETCH_RELAY_URLS,
@ -570,6 +571,19 @@ class ClientService extends EventTarget { @@ -570,6 +571,19 @@ class ClientService extends EventTarget {
const failedOutboxes = userOutboxUrls.filter((u) => !hadSuccess.has(norm(u)))
if (failedOutboxes.length === 0) return
const anyRelaySucceeded = relayStatuses.some((r) => r.success)
if (
anyRelaySucceeded &&
failedOutboxes.length > 0 &&
failedOutboxes.every((u) => isLocalNetworkUrl(u))
) {
logger.info(
'[Publish] Skipping NIP-65 outbox retry: unreachable local write relay(s) only; note already reached at least one relay.',
{ failedOutboxes }
)
return
}
const statusHint = (url: string): string => {
const n = norm(url)
const forUrl = relayStatuses.filter((r) => norm(r.url) === n)
@ -1521,6 +1535,7 @@ class ClientService extends EventTarget { @@ -1521,6 +1535,7 @@ class ClientService extends EventTarget {
slotCap
})
let hasResolved = false
let earlyGraceTimer: ReturnType<typeof setTimeout> | null = null
const globalTimeout = setTimeout(() => {
if (hasResolved) {
@ -1548,6 +1563,10 @@ class ClientService extends EventTarget { @@ -1548,6 +1563,10 @@ class ClientService extends EventTarget {
// Ensure we resolve even if not all relays finished
if (!hasResolved) {
if (earlyGraceTimer != null) {
clearTimeout(earlyGraceTimer)
earlyGraceTimer = null
}
hasResolved = true
logger.debug('[PublishEvent] Resolving due to timeout', {
success: successCount >= uniqueRelayUrls.length / 3,
@ -1704,7 +1723,11 @@ class ClientService extends EventTarget { @@ -1704,7 +1723,11 @@ class ClientService extends EventTarget {
break
} catch (wsErr) {
const msg = wsErr instanceof Error ? wsErr.message : String(wsErr)
const localConnDead =
isLocal &&
/Local relay connection timeout|connection timed out/i.test(msg)
const retriable =
!localConnDead &&
wsAttempt === 0 &&
/Remote relay connection timeout|Local relay connection timeout|Publish timeout after|publish timed out|websocket closed|connection failed|relay connection closed|SendingOnClosedConnection/i.test(
msg
@ -1759,6 +1782,10 @@ class ClientService extends EventTarget { @@ -1759,6 +1782,10 @@ class ClientService extends EventTarget {
this.emitNewEvent(event)
}
if (currentFinished >= uniqueRelayUrls.length && !hasResolved) {
if (earlyGraceTimer != null) {
clearTimeout(earlyGraceTimer)
earlyGraceTimer = null
}
hasResolved = true
logger.debug('[PublishEvent] All relays finished, resolving', {
success: successCount >= uniqueRelayUrls.length / 3,
@ -1774,32 +1801,27 @@ class ClientService extends EventTarget { @@ -1774,32 +1801,27 @@ class ClientService extends EventTarget {
successCount,
totalCount: uniqueRelayUrls.length
})
}
// Also resolve early if we have enough successes (1/3 of relays)
// This prevents waiting for slow/failing relays
if (!hasResolved && successCount >= Math.max(1, Math.ceil(uniqueRelayUrls.length / 3)) && currentFinished >= Math.max(1, Math.ceil(uniqueRelayUrls.length / 3))) {
// Wait a bit more to see if more relays succeed quickly
setTimeout(() => {
if (!hasResolved) {
} else if (!hasResolved && successCount >= 1 && earlyGraceTimer == null) {
earlyGraceTimer = setTimeout(() => {
earlyGraceTimer = null
if (hasResolved) return
hasResolved = true
logger.debug('[PublishEvent] Resolving early with enough successes', {
success: true,
clearTimeout(globalTimeout)
flushPublishOpBatch('early_any_success_grace')
logger.debug('[PublishEvent] Resolving after first success grace', {
success: successCount >= uniqueRelayUrls.length / 3,
successCount,
totalCount: uniqueRelayUrls.length,
finishedCount: currentFinished,
relayStatusesCount: relayStatuses.length
finishedRelays: currentFinished,
graceMs: EARLY_PUBLISH_SUCCESS_GRACE_MS
})
clearTimeout(globalTimeout)
flushPublishOpBatch('early_success_threshold')
resolve({
success: true,
success: successCount >= uniqueRelayUrls.length / 3,
relayStatuses,
successCount,
totalCount: uniqueRelayUrls.length
})
}
}, 2000) // Wait 2 more seconds for quick responses
}, EARLY_PUBLISH_SUCCESS_GRACE_MS)
}
}
})

Loading…
Cancel
Save