Phase 871: protomux-rpc MEMBER_PAGE_REQUEST pull (v0.8.838)
Opt-in member page RPC pull mirrors guild sync request wire: _buildMemberPagePayload, _handleMemberPageRequestRpc, _tryMemberPageRequestRpc, RPC-first requestGuildMemberPage with gossip fanout fallback. Non-breaking; default event path unchanged.
This commit is contained in:
@@ -92,6 +92,8 @@ Application facade: one `PearcordPlatform` class that wires identity, database,
|
|||||||
|
|
||||||
**Phase 822 (v0.8.789):** Triple-peer topology via `guild-mesh-topology` + `getMeshStabilityStats`. Bundle: `npm run test:ci-phase822`.
|
**Phase 822 (v0.8.789):** Triple-peer topology via `guild-mesh-topology` + `getMeshStabilityStats`. Bundle: `npm run test:ci-phase822`.
|
||||||
|
|
||||||
|
**Phase 871 (v0.8.838):** Opt-in protomux-rpc `MEMBER_PAGE_REQUEST` pull via `_tryMemberPageRequestRpc` (`PEARCORD_RPC_REQUEST_WIRE=1`). Bundle: `npm run test:ci-phase871`.
|
||||||
|
|
||||||
**Phase 869 (v0.8.836):** Opt-in protomux-rpc `GUILD_SYNC_REQUEST` pull via `_tryGuildSyncRequestRpc` (`PEARCORD_RPC_REQUEST_WIRE=1`). Bundle: `npm run test:ci-phase869`.
|
**Phase 869 (v0.8.836):** Opt-in protomux-rpc `GUILD_SYNC_REQUEST` pull via `_tryGuildSyncRequestRpc` (`PEARCORD_RPC_REQUEST_WIRE=1`). Bundle: `npm run test:ci-phase869`.
|
||||||
|
|
||||||
**Phase 821 (v0.8.788):** Triple-peer ACK watermark via `_fanoutGuildSyncRequestWithAckWatermarks`. Bundle: `npm run test:ci-phase821`.
|
**Phase 821 (v0.8.788):** Triple-peer ACK watermark via `_fanoutGuildSyncRequestWithAckWatermarks`. Bundle: `npm run test:ci-phase821`.
|
||||||
|
|||||||
@@ -295,13 +295,30 @@ requestGuildMemberPage (offset = 0, opts = {}) {
|
|||||||
}
|
}
|
||||||
const off = Math.max(0, Number(offset) || 0)
|
const off = Math.max(0, Number(offset) || 0)
|
||||||
if (!opts.debounced) this._recordMemberPageBurst()
|
if (!opts.debounced) this._recordMemberPageBurst()
|
||||||
this.guild
|
const fanoutMemberPageRequest = () => {
|
||||||
.gossipMemberPageRequest({
|
this.guild
|
||||||
|
.gossipMemberPageRequest({
|
||||||
|
guildId,
|
||||||
|
requesterId: userId,
|
||||||
|
offset: off
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
if (sharedScope.isRpcGossipRequestEnabled?.()) {
|
||||||
|
void this._tryMemberPageRequestRpc(off)
|
||||||
|
.then((rpc) => {
|
||||||
|
if (!rpc?.viaRpc) fanoutMemberPageRequest()
|
||||||
|
})
|
||||||
|
.catch(() => fanoutMemberPageRequest())
|
||||||
|
return {
|
||||||
guildId,
|
guildId,
|
||||||
requesterId: userId,
|
offset: off,
|
||||||
offset: off
|
requested: true,
|
||||||
})
|
pendingRpc: true,
|
||||||
.catch(() => {})
|
burstRemaining: this._memberPageBurstRemaining()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fanoutMemberPageRequest()
|
||||||
return {
|
return {
|
||||||
guildId,
|
guildId,
|
||||||
offset: off,
|
offset: off,
|
||||||
@@ -329,23 +346,12 @@ async _handleGuildSyncRequestRpc (payload) {
|
|||||||
return bundle
|
return bundle
|
||||||
},
|
},
|
||||||
|
|
||||||
async _handleMemberPageRequest (payload) {
|
async _buildMemberPagePayload (payload) {
|
||||||
const span = this.log.time('guild.sync.member-page', {
|
|
||||||
spanKind: 'guild.sync.member-page',
|
|
||||||
guildId: payload?.guildId,
|
|
||||||
offset: payload?.offset
|
|
||||||
})
|
|
||||||
const guildId = this.guild?.guild?.id
|
const guildId = this.guild?.guild?.id
|
||||||
if (!guildId || payload?.guildId !== guildId) {
|
if (!guildId || payload?.guildId !== guildId) return null
|
||||||
span.end({ sent: 0, reason: 'guild-mismatch' })
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
const isOwner = this.guild.guild.ownerId === this.identity?.user?.id
|
const isOwner = this.guild.guild.ownerId === this.identity?.user?.id
|
||||||
const canMod = await this._canModeratorPushGuildSync()
|
const canMod = await this._canModeratorPushGuildSync()
|
||||||
if (!isOwner && !canMod) {
|
if (!isOwner && !canMod) return null
|
||||||
span.end({ sent: 0, reason: 'forbidden' })
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
const allMembers = await this.guild.listMembers()
|
const allMembers = await this.guild.listMembers()
|
||||||
const offset = Math.max(0, Number(payload.offset) || 0)
|
const offset = Math.max(0, Number(payload.offset) || 0)
|
||||||
const pageSize = this._getGuildSyncMemberPageSize()
|
const pageSize = this._getGuildSyncMemberPageSize()
|
||||||
@@ -354,16 +360,40 @@ async _handleMemberPageRequest (payload) {
|
|||||||
)
|
)
|
||||||
const page = sorted.slice(offset, offset + pageSize)
|
const page = sorted.slice(offset, offset + pageSize)
|
||||||
const users = await this._collectUsersForSync(page)
|
const users = await this._collectUsersForSync(page)
|
||||||
await this.guild.gossipMemberPage({
|
return {
|
||||||
guildId,
|
guildId,
|
||||||
offset,
|
offset,
|
||||||
members: page.map((m) => hyperdbScope.sanitizeMemberForHyperDb(m)),
|
members: page.map((m) => hyperdbScope.sanitizeMemberForHyperDb(m)),
|
||||||
users,
|
users,
|
||||||
memberTotal: sorted.length,
|
memberTotal: sorted.length,
|
||||||
requesterId: payload.requesterId || null
|
requesterId: payload.requesterId || null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async _handleMemberPageRequestRpc (payload) {
|
||||||
|
const page = await this._buildMemberPagePayload(payload)
|
||||||
|
if (page) page.viaRpc = true
|
||||||
|
return page
|
||||||
|
},
|
||||||
|
|
||||||
|
async _handleMemberPageRequest (payload) {
|
||||||
|
const span = this.log.time('guild.sync.member-page', {
|
||||||
|
spanKind: 'guild.sync.member-page',
|
||||||
|
guildId: payload?.guildId,
|
||||||
|
offset: payload?.offset
|
||||||
})
|
})
|
||||||
span.end({ sent: page.length, offset, memberTotal: sorted.length })
|
const page = await this._buildMemberPagePayload(payload)
|
||||||
return page.length
|
if (!page) {
|
||||||
|
span.end({ sent: 0, reason: 'forbidden-or-mismatch' })
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
await this.guild.gossipMemberPage(page)
|
||||||
|
span.end({
|
||||||
|
sent: page.members.length,
|
||||||
|
offset: page.offset,
|
||||||
|
memberTotal: page.memberTotal
|
||||||
|
})
|
||||||
|
return page.members.length
|
||||||
},
|
},
|
||||||
|
|
||||||
async _handleMemberJoinIdentifyRequest (payload) {
|
async _handleMemberJoinIdentifyRequest (payload) {
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ const platformGuildWireMixin = {
|
|||||||
_bindGuildGossipRpcHandlers (guildInstance) {
|
_bindGuildGossipRpcHandlers (guildInstance) {
|
||||||
const enabled = !!sharedScope.isRpcGossipRequestEnabled?.()
|
const enabled = !!sharedScope.isRpcGossipRequestEnabled?.()
|
||||||
const next = enabled
|
const next = enabled
|
||||||
? { GUILD_SYNC_REQUEST: (payload) => this._handleGuildSyncRequestRpc(payload) }
|
? {
|
||||||
|
GUILD_SYNC_REQUEST: (payload) => this._handleGuildSyncRequestRpc(payload),
|
||||||
|
MEMBER_PAGE_REQUEST: (payload) => this._handleMemberPageRequestRpc(payload)
|
||||||
|
}
|
||||||
: null
|
: null
|
||||||
const hadHandlers = !!guildInstance._gossipRpcHandlers
|
const hadHandlers = !!guildInstance._gossipRpcHandlers
|
||||||
guildInstance._gossipRpcHandlers = next
|
guildInstance._gossipRpcHandlers = next
|
||||||
|
|||||||
@@ -17,6 +17,49 @@ function meshPeerIdHex (pk) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const platformMeshViewMixin = {
|
const platformMeshViewMixin = {
|
||||||
|
async _tryMemberPageRequestRpc (offset = 0) {
|
||||||
|
if (!sharedScope.isRpcGossipRequestEnabled?.()) return null
|
||||||
|
const guildId = this.guild?.guild?.id
|
||||||
|
const userId = this.identity?.user?.id
|
||||||
|
if (!guildId || !userId || !this.guild?.requestGossipFromPeer) return null
|
||||||
|
let hostPk = meshPeerIdHex(await this._lookupGuildHostPublicKey(this.guild.guild))
|
||||||
|
const peerKeys = [...(this.guild.peers?.keys() || [])]
|
||||||
|
if (hostPk && !this.guild.peers?.has(hostPk) && peerKeys.length === 1) {
|
||||||
|
hostPk = peerKeys[0]
|
||||||
|
}
|
||||||
|
if (!hostPk || !(this.guild.peers?.has(hostPk))) return null
|
||||||
|
await this._flushGuildGossipOutbox().catch(() => {})
|
||||||
|
try {
|
||||||
|
const { ensureGuildWireChannels } = require('pearcord-guild/mesh')
|
||||||
|
ensureGuildWireChannels(this.guild)
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
const page = await this.guild.requestGossipFromPeer(
|
||||||
|
hostPk,
|
||||||
|
sharedScope.RPC.MEMBER_PAGE_REQUEST,
|
||||||
|
{
|
||||||
|
guildId,
|
||||||
|
requesterId: userId,
|
||||||
|
offset: Math.max(0, Number(offset) || 0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: Number(process.env.PEARCORD_GUILD_MEMBER_PAGE_RPC_MS) || 25000,
|
||||||
|
wireReadyMs:
|
||||||
|
Number(process.env.PEARCORD_GUILD_MEMBER_PAGE_RPC_WIRE_READY_MS) || 12000
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!page?.guildId) return null
|
||||||
|
const added = await this._ingestMemberPage(page)
|
||||||
|
return {
|
||||||
|
requested: true,
|
||||||
|
viaRpc: true,
|
||||||
|
offset: page.offset,
|
||||||
|
memberCount: page.members?.length || 0,
|
||||||
|
added
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async _tryGuildSyncRequestRpc (channelId) {
|
async _tryGuildSyncRequestRpc (channelId) {
|
||||||
if (!sharedScope.isRpcGossipRequestEnabled?.()) return null
|
if (!sharedScope.isRpcGossipRequestEnabled?.()) return null
|
||||||
const guildId = this.guild?.guild?.id
|
const guildId = this.guild?.guild?.id
|
||||||
|
|||||||
Reference in New Issue
Block a user