refactor(platform): extract guild mesh peer mixin (Phase 741)
Move peer seen tracking, stale peer eviction, sweep timers, and adaptive mesh retry delay into platform-guild-mesh-peer-mixin.js (67 manifest rows; runtime registry 7 modules). Class ~33607 lines. No behavior change. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -54,6 +54,8 @@ Application facade: one `PearcordPlatform` class that wires identity, database,
|
||||
|
||||
**Phase 723 (v0.8.699):** Dedicated slash registry & invoke audit export JSON — `slash-registry-json-mixin.js`, `slash-invoke-audit-json-mixin.js`, spans `slash-registry.json` / `slash-invoke-audit.json`, `getSlashInvokeAuditExportJsonExport`, `clearSlashInvokeAuditExportJsonExports`, deep links `openSlashRegistryJson` / `openSlashInvokeAuditJson`. Bundle: `npm run test:ci-phase723`.
|
||||
|
||||
**Phase 741 (v0.8.717):** `platform-guild-mesh-peer-mixin.js` (67 mixin rows); stale peer evict + retry delay helpers extracted. Bundle: `npm run test:ci-phase741`.
|
||||
|
||||
**Phase 740 (v0.8.716):** `platform-guild-mesh-bounded-mixin.js`, `platform-federation-view-mixin.js` (66 mixin rows). Bundle: `npm run test:ci-phase740`.
|
||||
|
||||
**Phase 739 (v0.8.715):** `platform-guild-open-fallback-mixin.js`, `platform-partition-heal-state-mixin.js` (64 mixin rows); `platform-runtime-mixin-registry.js`. Bundle: `npm run test:ci-phase739`.
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
'use strict'
|
||||
|
||||
const localScope = require('./platform-index-local-imports')
|
||||
const swarmScope = require('./platform-swarm-manager-imports')
|
||||
|
||||
const platformGuildMeshPeerMixin = {
|
||||
_guildMeshPeerSeenMap (guildId) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid) return null
|
||||
let map = this._guildMeshPeerSeenByGuild.get(gid)
|
||||
if (!map) {
|
||||
map = new Map()
|
||||
this._guildMeshPeerSeenByGuild.set(gid, map)
|
||||
}
|
||||
return map
|
||||
},
|
||||
|
||||
_touchGuildMeshPeerSeen (guildId, peerId, source = 'activity', opts = {}) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid || !peerId) return null
|
||||
const map = this._guildMeshPeerSeenMap(gid)
|
||||
const now = Date.now()
|
||||
const prev = map.get(peerId) || { peerId, joinedAt: now, lastSeenAt: 0, dropCount: 0 }
|
||||
const sparseCursors = this._sparseAckCursorByGuild.get(gid) || {}
|
||||
const hasSparse = Object.keys(sparseCursors).length > 0
|
||||
const row = {
|
||||
...prev,
|
||||
peerId,
|
||||
joinedAt: prev.joinedAt || now,
|
||||
lastSeenAt: now,
|
||||
lastSource: source,
|
||||
rttMs:
|
||||
Number(opts.rttMs) ||
|
||||
Number(prev.rttMs) ||
|
||||
Number(this._lastGuildMeshWireRttMs) ||
|
||||
Number(process.env.PEARCORD_GUILD_MESH_RTT_MS) ||
|
||||
120,
|
||||
dropCount: Number(prev.dropCount) || 0,
|
||||
sparseCursorStale:
|
||||
hasSparse &&
|
||||
Object.values(sparseCursors).some((off) => Number(off) > 0) &&
|
||||
now - (Number(prev.lastSeenAt) || now) > 45000
|
||||
}
|
||||
row.qualityScore = localScope.computeMeshPeerQualityScore(row, {
|
||||
fallbackRttMs: this._lastGuildMeshWireRttMs
|
||||
})
|
||||
map.set(peerId, row)
|
||||
const health = this._guildSyncHealthByGuild.get(gid)
|
||||
const lastPeerSeenAt = Math.max(Number(health?.lastPeerSeenAt) || 0, now)
|
||||
this._patchGuildSyncHealth({
|
||||
guildId: gid,
|
||||
lastPeerSeenAt,
|
||||
lastSuccessfulPeerContactAt: lastPeerSeenAt
|
||||
})
|
||||
return row
|
||||
},
|
||||
|
||||
_touchAllGuildMeshPeersSeen (guildId, source = 'mesh-activity') {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid || !this.guild?.peers?.size) return 0
|
||||
let n = 0
|
||||
for (const peerId of this.guild.peers.keys()) {
|
||||
this._touchGuildMeshPeerSeen(gid, peerId, source)
|
||||
n++
|
||||
}
|
||||
return n
|
||||
},
|
||||
|
||||
_forgetGuildMeshPeerSeen (guildId, peerId) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid || !peerId) return
|
||||
this._guildMeshPeerSeenByGuild.get(gid)?.delete(peerId)
|
||||
},
|
||||
|
||||
_getGuildMeshPeerRetryAttempt (guildId) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid) return 0
|
||||
const meta = this._guildMeshPeerRetryMetaByGuild.get(gid)
|
||||
if (this.guild?.guild?.id === gid) {
|
||||
return Math.max(
|
||||
Number(meta?.attempt) || 0,
|
||||
Number(this._guildMeshPeerRetryAttempts) || 0
|
||||
)
|
||||
}
|
||||
return Number(meta?.attempt) || 0
|
||||
},
|
||||
|
||||
/**
|
||||
* Drop hyperswarm sockets that stayed connected without mesh activity.
|
||||
* @returns {{ evicted: number, peerRetryAttempt: number, lastPeerSeenAt: number }}
|
||||
*/
|
||||
_evictStaleGuildMeshPeers (guildId, opts = {}) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
const out = { evicted: 0, peerRetryAttempt: 0, lastPeerSeenAt: 0 }
|
||||
if (!gid || this.guild?.guild?.id !== gid || !this.guild?.peers?.size) {
|
||||
return out
|
||||
}
|
||||
const staleMs = Number(process.env.PEARCORD_GUILD_MESH_PEER_STALE_MS) || 180000
|
||||
const minAgeMs = Number(process.env.PEARCORD_GUILD_MESH_PEER_STALE_MIN_AGE_MS) || 45000
|
||||
const now = Date.now()
|
||||
const peerMap = this._guildMeshPeerSeenMap(gid)
|
||||
const peerRetryAttempt = this._getGuildMeshPeerRetryAttempt(gid)
|
||||
out.peerRetryAttempt = peerRetryAttempt
|
||||
|
||||
for (const [peerId, conn] of [...this.guild.peers.entries()]) {
|
||||
const row = peerMap.get(peerId) || {
|
||||
peerId,
|
||||
joinedAt: now,
|
||||
lastSeenAt: 0
|
||||
}
|
||||
const joinedAt = Number(row.joinedAt) || now
|
||||
const lastSeenAt = Number(row.lastSeenAt) || 0
|
||||
const connAgeMs = now - joinedAt
|
||||
const idleMs = lastSeenAt ? now - lastSeenAt : connAgeMs
|
||||
const idleThreshold = opts.force === true ? minAgeMs : staleMs
|
||||
const stale = connAgeMs >= minAgeMs && idleMs >= idleThreshold
|
||||
if (!stale) continue
|
||||
|
||||
try {
|
||||
conn.destroy?.()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
row.dropCount = (Number(row.dropCount) || 0) + 1
|
||||
this.guild.peers.delete(peerId)
|
||||
this.guild._channels?.delete(peerId)
|
||||
this.guild._attachChannels?.delete(peerId)
|
||||
this.guild._voiceChannels?.delete(peerId)
|
||||
this.guild._screenChannels?.delete(peerId)
|
||||
peerMap.delete(peerId)
|
||||
this._trackGuildTopicPeer(gid, peerId, 'leave')
|
||||
out.evicted += 1
|
||||
this.log.info('guild mesh stale peer evicted', {
|
||||
spanKind: 'guild.mesh.stale-peer-evict',
|
||||
guildId: gid,
|
||||
peerId,
|
||||
peerRetryAttempt,
|
||||
lastPeerSeenAt: lastSeenAt || 0,
|
||||
idleMs,
|
||||
connAgeMs,
|
||||
source: opts.source || 'stale-sweep'
|
||||
})
|
||||
}
|
||||
|
||||
if (out.evicted > 0) {
|
||||
const total =
|
||||
(this._guildMeshStalePeerEvictedTotalByGuild.get(gid) || 0) + out.evicted
|
||||
this._guildMeshStalePeerEvictedTotalByGuild.set(gid, total)
|
||||
const health = this._guildSyncHealthByGuild.get(gid)
|
||||
out.lastPeerSeenAt = Number(health?.lastPeerSeenAt) || 0
|
||||
let maxSeen = 0
|
||||
for (const row of peerMap.values()) {
|
||||
maxSeen = Math.max(maxSeen, Number(row.lastSeenAt) || 0)
|
||||
}
|
||||
const peers = this.guild.peers?.size ?? 0
|
||||
this._patchGuildSyncHealth({
|
||||
guildId: gid,
|
||||
peers,
|
||||
peerRetryAttempt,
|
||||
lastPeerSeenAt: maxSeen || out.lastPeerSeenAt,
|
||||
stalePeersEvictedLast: out.evicted,
|
||||
lastStalePeerEvictAt: now
|
||||
})
|
||||
this._logMeshChurn('stale-peer-evict', {
|
||||
guildId: gid,
|
||||
evicted: out.evicted,
|
||||
peerRetryAttempt,
|
||||
lastPeerSeenAt: maxSeen || out.lastPeerSeenAt,
|
||||
meshPeerCount: peers
|
||||
})
|
||||
if (peers === 0) this._scheduleGuildMeshPeerRetry(gid)
|
||||
}
|
||||
return out
|
||||
},
|
||||
|
||||
_maybeEvictStaleGuildMeshPeers (guildId, source = 'sweep') {
|
||||
if (!guildId || this.guild?.guild?.id !== guildId) return { evicted: 0 }
|
||||
if (!(this.guild?.peers?.size ?? 0)) return { evicted: 0 }
|
||||
return this._evictStaleGuildMeshPeers(guildId, { source })
|
||||
},
|
||||
|
||||
_stopGuildMeshStalePeerSweep () {
|
||||
if (this._guildMeshStalePeerSweepTimer) {
|
||||
clearInterval(this._guildMeshStalePeerSweepTimer)
|
||||
this._guildMeshStalePeerSweepTimer = null
|
||||
}
|
||||
},
|
||||
|
||||
_startGuildMeshStalePeerSweep (guildId) {
|
||||
this._stopGuildMeshStalePeerSweep()
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid) return
|
||||
const intervalMs = Number(process.env.PEARCORD_GUILD_MESH_PEER_SWEEP_MS) || 60000
|
||||
this._guildMeshStalePeerSweepTimer = setInterval(() => {
|
||||
if (this.guild?.guild?.id !== gid) {
|
||||
this._stopGuildMeshStalePeerSweep()
|
||||
return
|
||||
}
|
||||
this._maybeEvictStaleGuildMeshPeers(gid, 'interval-sweep')
|
||||
}, intervalMs)
|
||||
if (typeof this._guildMeshStalePeerSweepTimer.unref === 'function') {
|
||||
this._guildMeshStalePeerSweepTimer.unref()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Mesh peer retry delay from time since last successful peer contact (not fixed attempt cadence).
|
||||
* @param {string} guildId
|
||||
* @param {number} attempt 1-based retry attempt
|
||||
*/
|
||||
_computeGuildMeshPeerRetryDelayMs (guildId, attempt = 1) {
|
||||
const meshRtt =
|
||||
Number(this._lastGuildMeshWireRttMs) ||
|
||||
Number(process.env.PEARCORD_GUILD_MESH_RTT_MS) ||
|
||||
120
|
||||
const attemptIdx = Math.max(0, Number(attempt) - 1)
|
||||
let baseMs = 3000
|
||||
try {
|
||||
const scaled = swarmScope.adaptiveBurstDelays(meshRtt, [2800, 5000, 8000, 12000, 15000, 18000])
|
||||
baseMs = scaled[Math.min(attemptIdx, scaled.length - 1)] || 3000
|
||||
} catch {
|
||||
const retryDelays = [3000, 5000, 8000, 12000, 15000]
|
||||
baseMs = retryDelays[Math.min(attemptIdx, retryDelays.length - 1)] || 3000
|
||||
}
|
||||
|
||||
const health = this._guildSyncHealthByGuild.get(guildId)
|
||||
const lastContact = Math.max(
|
||||
Number(health?.lastPeerSeenAt) || 0,
|
||||
Number(health?.lastSuccessfulPeerContactAt) || 0
|
||||
)
|
||||
const now = Date.now()
|
||||
const minMs = Number(process.env.PEARCORD_GUILD_MESH_PEER_RETRY_MIN_MS) || 1500
|
||||
const maxMs = Number(process.env.PEARCORD_GUILD_MESH_PEER_RETRY_MAX_MS) || 60000
|
||||
|
||||
if (!lastContact) {
|
||||
return Math.min(maxMs, Math.round(baseMs * (attempt <= 2 ? 1.15 : 1.3)))
|
||||
}
|
||||
|
||||
const sinceContactMs = Math.max(0, now - lastContact)
|
||||
let delayMs = baseMs
|
||||
if (sinceContactMs < 30000) {
|
||||
delayMs = Math.max(minMs, Math.round(minMs + sinceContactMs * 0.08))
|
||||
} else if (sinceContactMs < 180000) {
|
||||
delayMs = Math.max(baseMs, Math.round(baseMs + sinceContactMs * 0.12))
|
||||
} else {
|
||||
const extra = Math.min(30000, Math.round((sinceContactMs - 180000) * 0.06))
|
||||
delayMs = Math.min(maxMs, Math.round(baseMs * 1.2 + extra))
|
||||
}
|
||||
const jitter = localScope.meshRejoinJitterMs(guildId, `retry-${attempt}`)
|
||||
const out = Math.min(maxMs, Math.max(minMs, delayMs + jitter))
|
||||
this.log.debug('guild.mesh.retry.adaptive', {
|
||||
spanKind: 'guild.mesh.retry.adaptive',
|
||||
guildId,
|
||||
attempt,
|
||||
meshRtt,
|
||||
sinceContactMs: lastContact ? now - lastContact : null,
|
||||
baseMs,
|
||||
delayMs: out,
|
||||
spacingSource: lastContact ? 'last-peer-contact' : 'cold-start'
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = { platformGuildMeshPeerMixin }
|
||||
@@ -4,7 +4,7 @@
|
||||
* Ordered PearcordPlatform prototype mixin registration (Phase 730).
|
||||
* Preserves assign order from index.js / apply-platform-mixins (Phase 727).
|
||||
*/
|
||||
const PLATFORM_MIXIN_ASSIGNMENT_COUNT = 66
|
||||
const PLATFORM_MIXIN_ASSIGNMENT_COUNT = 67
|
||||
|
||||
const PLATFORM_MIXIN_ASSIGNMENTS = [
|
||||
{ module: './polls-scheduling', export: 'pollSchedulingMixin' },
|
||||
@@ -72,6 +72,7 @@ const PLATFORM_MIXIN_ASSIGNMENTS = [
|
||||
{ module: './platform-partition-heal-state-mixin', export: 'platformPartitionHealStateMixin' },
|
||||
{ module: './platform-guild-mesh-bounded-mixin', export: 'platformGuildMeshBoundedMixin' },
|
||||
{ module: './platform-federation-view-mixin', export: 'platformFederationViewMixin' },
|
||||
{ module: './platform-guild-mesh-peer-mixin', export: 'platformGuildMeshPeerMixin' },
|
||||
{ module: './platform-diagnostics-mixin', export: 'platformDiagnosticsMixin' }
|
||||
]
|
||||
|
||||
|
||||
@@ -273,263 +273,6 @@ class PearcordPlatform extends EventEmitter {
|
||||
this._forumTagFilters = []
|
||||
}
|
||||
|
||||
_guildMeshPeerSeenMap (guildId) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid) return null
|
||||
let map = this._guildMeshPeerSeenByGuild.get(gid)
|
||||
if (!map) {
|
||||
map = new Map()
|
||||
this._guildMeshPeerSeenByGuild.set(gid, map)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
_touchGuildMeshPeerSeen (guildId, peerId, source = 'activity', opts = {}) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid || !peerId) return null
|
||||
const map = this._guildMeshPeerSeenMap(gid)
|
||||
const now = Date.now()
|
||||
const prev = map.get(peerId) || { peerId, joinedAt: now, lastSeenAt: 0, dropCount: 0 }
|
||||
const sparseCursors = this._sparseAckCursorByGuild.get(gid) || {}
|
||||
const hasSparse = Object.keys(sparseCursors).length > 0
|
||||
const row = {
|
||||
...prev,
|
||||
peerId,
|
||||
joinedAt: prev.joinedAt || now,
|
||||
lastSeenAt: now,
|
||||
lastSource: source,
|
||||
rttMs:
|
||||
Number(opts.rttMs) ||
|
||||
Number(prev.rttMs) ||
|
||||
Number(this._lastGuildMeshWireRttMs) ||
|
||||
Number(process.env.PEARCORD_GUILD_MESH_RTT_MS) ||
|
||||
120,
|
||||
dropCount: Number(prev.dropCount) || 0,
|
||||
sparseCursorStale:
|
||||
hasSparse &&
|
||||
Object.values(sparseCursors).some((off) => Number(off) > 0) &&
|
||||
now - (Number(prev.lastSeenAt) || now) > 45000
|
||||
}
|
||||
row.qualityScore = localScope.computeMeshPeerQualityScore(row, {
|
||||
fallbackRttMs: this._lastGuildMeshWireRttMs
|
||||
})
|
||||
map.set(peerId, row)
|
||||
const health = this._guildSyncHealthByGuild.get(gid)
|
||||
const lastPeerSeenAt = Math.max(Number(health?.lastPeerSeenAt) || 0, now)
|
||||
this._patchGuildSyncHealth({
|
||||
guildId: gid,
|
||||
lastPeerSeenAt,
|
||||
lastSuccessfulPeerContactAt: lastPeerSeenAt
|
||||
})
|
||||
return row
|
||||
}
|
||||
|
||||
_touchAllGuildMeshPeersSeen (guildId, source = 'mesh-activity') {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid || !this.guild?.peers?.size) return 0
|
||||
let n = 0
|
||||
for (const peerId of this.guild.peers.keys()) {
|
||||
this._touchGuildMeshPeerSeen(gid, peerId, source)
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
_forgetGuildMeshPeerSeen (guildId, peerId) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid || !peerId) return
|
||||
this._guildMeshPeerSeenByGuild.get(gid)?.delete(peerId)
|
||||
}
|
||||
|
||||
_getGuildMeshPeerRetryAttempt (guildId) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid) return 0
|
||||
const meta = this._guildMeshPeerRetryMetaByGuild.get(gid)
|
||||
if (this.guild?.guild?.id === gid) {
|
||||
return Math.max(
|
||||
Number(meta?.attempt) || 0,
|
||||
Number(this._guildMeshPeerRetryAttempts) || 0
|
||||
)
|
||||
}
|
||||
return Number(meta?.attempt) || 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop hyperswarm sockets that stayed connected without mesh activity.
|
||||
* @returns {{ evicted: number, peerRetryAttempt: number, lastPeerSeenAt: number }}
|
||||
*/
|
||||
_evictStaleGuildMeshPeers (guildId, opts = {}) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
const out = { evicted: 0, peerRetryAttempt: 0, lastPeerSeenAt: 0 }
|
||||
if (!gid || this.guild?.guild?.id !== gid || !this.guild?.peers?.size) {
|
||||
return out
|
||||
}
|
||||
const staleMs = Number(process.env.PEARCORD_GUILD_MESH_PEER_STALE_MS) || 180000
|
||||
const minAgeMs = Number(process.env.PEARCORD_GUILD_MESH_PEER_STALE_MIN_AGE_MS) || 45000
|
||||
const now = Date.now()
|
||||
const peerMap = this._guildMeshPeerSeenMap(gid)
|
||||
const peerRetryAttempt = this._getGuildMeshPeerRetryAttempt(gid)
|
||||
out.peerRetryAttempt = peerRetryAttempt
|
||||
|
||||
for (const [peerId, conn] of [...this.guild.peers.entries()]) {
|
||||
const row = peerMap.get(peerId) || {
|
||||
peerId,
|
||||
joinedAt: now,
|
||||
lastSeenAt: 0
|
||||
}
|
||||
const joinedAt = Number(row.joinedAt) || now
|
||||
const lastSeenAt = Number(row.lastSeenAt) || 0
|
||||
const connAgeMs = now - joinedAt
|
||||
const idleMs = lastSeenAt ? now - lastSeenAt : connAgeMs
|
||||
const idleThreshold = opts.force === true ? minAgeMs : staleMs
|
||||
const stale = connAgeMs >= minAgeMs && idleMs >= idleThreshold
|
||||
if (!stale) continue
|
||||
|
||||
try {
|
||||
conn.destroy?.()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
row.dropCount = (Number(row.dropCount) || 0) + 1
|
||||
this.guild.peers.delete(peerId)
|
||||
this.guild._channels?.delete(peerId)
|
||||
this.guild._attachChannels?.delete(peerId)
|
||||
this.guild._voiceChannels?.delete(peerId)
|
||||
this.guild._screenChannels?.delete(peerId)
|
||||
peerMap.delete(peerId)
|
||||
this._trackGuildTopicPeer(gid, peerId, 'leave')
|
||||
out.evicted += 1
|
||||
this.log.info('guild mesh stale peer evicted', {
|
||||
spanKind: 'guild.mesh.stale-peer-evict',
|
||||
guildId: gid,
|
||||
peerId,
|
||||
peerRetryAttempt,
|
||||
lastPeerSeenAt: lastSeenAt || 0,
|
||||
idleMs,
|
||||
connAgeMs,
|
||||
source: opts.source || 'stale-sweep'
|
||||
})
|
||||
}
|
||||
|
||||
if (out.evicted > 0) {
|
||||
const total =
|
||||
(this._guildMeshStalePeerEvictedTotalByGuild.get(gid) || 0) + out.evicted
|
||||
this._guildMeshStalePeerEvictedTotalByGuild.set(gid, total)
|
||||
const health = this._guildSyncHealthByGuild.get(gid)
|
||||
out.lastPeerSeenAt = Number(health?.lastPeerSeenAt) || 0
|
||||
let maxSeen = 0
|
||||
for (const row of peerMap.values()) {
|
||||
maxSeen = Math.max(maxSeen, Number(row.lastSeenAt) || 0)
|
||||
}
|
||||
const peers = this.guild.peers?.size ?? 0
|
||||
this._patchGuildSyncHealth({
|
||||
guildId: gid,
|
||||
peers,
|
||||
peerRetryAttempt,
|
||||
lastPeerSeenAt: maxSeen || out.lastPeerSeenAt,
|
||||
stalePeersEvictedLast: out.evicted,
|
||||
lastStalePeerEvictAt: now
|
||||
})
|
||||
this._logMeshChurn('stale-peer-evict', {
|
||||
guildId: gid,
|
||||
evicted: out.evicted,
|
||||
peerRetryAttempt,
|
||||
lastPeerSeenAt: maxSeen || out.lastPeerSeenAt,
|
||||
meshPeerCount: peers
|
||||
})
|
||||
if (peers === 0) this._scheduleGuildMeshPeerRetry(gid)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
_maybeEvictStaleGuildMeshPeers (guildId, source = 'sweep') {
|
||||
if (!guildId || this.guild?.guild?.id !== guildId) return { evicted: 0 }
|
||||
if (!(this.guild?.peers?.size ?? 0)) return { evicted: 0 }
|
||||
return this._evictStaleGuildMeshPeers(guildId, { source })
|
||||
}
|
||||
|
||||
_stopGuildMeshStalePeerSweep () {
|
||||
if (this._guildMeshStalePeerSweepTimer) {
|
||||
clearInterval(this._guildMeshStalePeerSweepTimer)
|
||||
this._guildMeshStalePeerSweepTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
_startGuildMeshStalePeerSweep (guildId) {
|
||||
this._stopGuildMeshStalePeerSweep()
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid) return
|
||||
const intervalMs = Number(process.env.PEARCORD_GUILD_MESH_PEER_SWEEP_MS) || 60000
|
||||
this._guildMeshStalePeerSweepTimer = setInterval(() => {
|
||||
if (this.guild?.guild?.id !== gid) {
|
||||
this._stopGuildMeshStalePeerSweep()
|
||||
return
|
||||
}
|
||||
this._maybeEvictStaleGuildMeshPeers(gid, 'interval-sweep')
|
||||
}, intervalMs)
|
||||
if (typeof this._guildMeshStalePeerSweepTimer.unref === 'function') {
|
||||
this._guildMeshStalePeerSweepTimer.unref()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mesh peer retry delay from time since last successful peer contact (not fixed attempt cadence).
|
||||
* @param {string} guildId
|
||||
* @param {number} attempt 1-based retry attempt
|
||||
*/
|
||||
_computeGuildMeshPeerRetryDelayMs (guildId, attempt = 1) {
|
||||
const meshRtt =
|
||||
Number(this._lastGuildMeshWireRttMs) ||
|
||||
Number(process.env.PEARCORD_GUILD_MESH_RTT_MS) ||
|
||||
120
|
||||
const attemptIdx = Math.max(0, Number(attempt) - 1)
|
||||
let baseMs = 3000
|
||||
try {
|
||||
const scaled = swarmScope.adaptiveBurstDelays(meshRtt, [2800, 5000, 8000, 12000, 15000, 18000])
|
||||
baseMs = scaled[Math.min(attemptIdx, scaled.length - 1)] || 3000
|
||||
} catch {
|
||||
const retryDelays = [3000, 5000, 8000, 12000, 15000]
|
||||
baseMs = retryDelays[Math.min(attemptIdx, retryDelays.length - 1)] || 3000
|
||||
}
|
||||
|
||||
const health = this._guildSyncHealthByGuild.get(guildId)
|
||||
const lastContact = Math.max(
|
||||
Number(health?.lastPeerSeenAt) || 0,
|
||||
Number(health?.lastSuccessfulPeerContactAt) || 0
|
||||
)
|
||||
const now = Date.now()
|
||||
const minMs = Number(process.env.PEARCORD_GUILD_MESH_PEER_RETRY_MIN_MS) || 1500
|
||||
const maxMs = Number(process.env.PEARCORD_GUILD_MESH_PEER_RETRY_MAX_MS) || 60000
|
||||
|
||||
if (!lastContact) {
|
||||
return Math.min(maxMs, Math.round(baseMs * (attempt <= 2 ? 1.15 : 1.3)))
|
||||
}
|
||||
|
||||
const sinceContactMs = Math.max(0, now - lastContact)
|
||||
let delayMs = baseMs
|
||||
if (sinceContactMs < 30000) {
|
||||
delayMs = Math.max(minMs, Math.round(minMs + sinceContactMs * 0.08))
|
||||
} else if (sinceContactMs < 180000) {
|
||||
delayMs = Math.max(baseMs, Math.round(baseMs + sinceContactMs * 0.12))
|
||||
} else {
|
||||
const extra = Math.min(30000, Math.round((sinceContactMs - 180000) * 0.06))
|
||||
delayMs = Math.min(maxMs, Math.round(baseMs * 1.2 + extra))
|
||||
}
|
||||
const jitter = localScope.meshRejoinJitterMs(guildId, `retry-${attempt}`)
|
||||
const out = Math.min(maxMs, Math.max(minMs, delayMs + jitter))
|
||||
this.log.debug('guild.mesh.retry.adaptive', {
|
||||
spanKind: 'guild.mesh.retry.adaptive',
|
||||
guildId,
|
||||
attempt,
|
||||
meshRtt,
|
||||
sinceContactMs: lastContact ? now - lastContact : null,
|
||||
baseMs,
|
||||
delayMs: out,
|
||||
spacingSource: lastContact ? 'last-peer-contact' : 'cold-start'
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
_refreshGuildTopicPoolCap (guildId) {
|
||||
const gid = guildId || this.guild?.guild?.id
|
||||
if (!gid || !this._guildTopicPool) return null
|
||||
|
||||
@@ -7,7 +7,8 @@ const PLATFORM_RUNTIME_MIXIN_MODULES = [
|
||||
'./platform-guild-open-fallback-mixin',
|
||||
'./platform-partition-heal-state-mixin',
|
||||
'./platform-guild-mesh-bounded-mixin',
|
||||
'./platform-federation-view-mixin'
|
||||
'./platform-federation-view-mixin',
|
||||
'./platform-guild-mesh-peer-mixin'
|
||||
]
|
||||
|
||||
const PLATFORM_RUNTIME_MIXIN_COUNT = PLATFORM_RUNTIME_MIXIN_MODULES.length
|
||||
|
||||
Reference in New Issue
Block a user