This commit is contained in:
Raven Scott
2025-12-20 15:41:48 -05:00
parent 0dbdcdd641
commit 6128abe902
3 changed files with 157 additions and 29 deletions
+3 -1
View File
@@ -92,5 +92,7 @@ module.exports = {
lastReconnectionAttempt: new Map(), // Map<peerId, timestamp>
// Invite diagnostics tracking
consecutiveInviteFailures: 0,
networkConsensusTimer: null
networkConsensusTimer: null,
// Pending invite requests queue for when master isn't ready
pendingInviteRequests: new Map() // Map<peerId, {timestamp, retryCount}>
};
+10 -3
View File
@@ -812,7 +812,10 @@ async function waitForBidirectionalOpen(peerChannel, timeout = 5000, checkInterv
}
const startTime = Date.now();
const peerId = peerChannel.peerId || 'unknown';
logDebug('ChannelManager', `Waiting for bidirectional open on channel for peer ${peerId.substring(0, 16)}..., timeout: ${timeout}ms`);
while (Date.now() - startTime < timeout) {
// Trust protomux's channel.opened as authoritative
// When channel.opened is true, the channel is fully bidirectional
@@ -820,13 +823,17 @@ async function waitForBidirectionalOpen(peerChannel, timeout = 5000, checkInterv
// Sync our tracked state with protomux state
peerChannel.localOpened = true;
peerChannel.remoteOpened = true;
const elapsed = Date.now() - startTime;
logDebug('ChannelManager', `Channel became bidirectional for peer ${peerId.substring(0, 16)}... after ${elapsed}ms`);
return true;
}
// Wait before checking again
await new Promise(resolve => setTimeout(resolve, checkInterval));
}
const elapsed = Date.now() - startTime;
logWarn('ChannelManager', `Channel failed to become bidirectional for peer ${peerId.substring(0, 16)}... within ${timeout}ms (waited ${elapsed}ms)`);
return false;
}
+144 -25
View File
@@ -348,6 +348,58 @@ async function main() {
if (newPass) {
core = newPass.base;
logDebug('Swarm', 'dnsPass and core updated consistently');
// Process any pending invite requests now that master is ready
if (isMaster && state.pendingInviteRequests.size > 0) {
logInfo('Swarm', `Master is now ready, processing ${state.pendingInviteRequests.size} pending invite requests`);
processPendingInviteRequests();
}
}
}
// Function to process pending invite requests when master becomes ready
async function processPendingInviteRequests() {
if (!isMaster || !state.dnsPass) {
return;
}
const pendingRequests = Array.from(state.pendingInviteRequests.entries());
state.pendingInviteRequests.clear(); // Clear queue immediately to avoid duplicate processing
for (const [peerId, requestInfo] of pendingRequests) {
// Check if peer is still connected
if (!connectedPeers.has(peerId)) {
logDebug('Swarm', `Skipping pending invite request for disconnected peer ${peerId}`);
continue;
}
// Check age of request (don't process very old requests)
const age = Date.now() - requestInfo.timestamp;
if (age > 30000) { // 30 seconds
logWarn('Swarm', `Skipping stale pending invite request from ${peerId} (age: ${Math.round(age/1000)}s)`);
continue;
}
logInfo('Swarm', `Processing pending invite request from ${peerId} (queued for ${Math.round(age/1000)}s)`);
try {
const inv = await state.dnsPass.createInvite();
logInfo('Swarm', `Created invite for pending request from ${peerId}: ${inv.toString('hex').substring(0, 20)}...`);
const sent = channelManager.sendToPeer(CORE_DOMAIN, 'invite', peerId, inv.toString('hex'));
if (sent) {
logInfo('Swarm', `Successfully sent pending invite to ${peerId}`);
} else {
logWarn('Swarm', `Failed to send pending invite to ${peerId} - channel not ready`);
}
} catch (err) {
logError('Swarm', `Error processing pending invite request for ${peerId}: ${err.message}`);
// Send error response so peer knows what happened
try {
channelManager.sendToPeer(CORE_DOMAIN, 'request', peerId, 'invite_unavailable:creation_failed');
} catch (sendErr) {
// Channel might be closed, ignore
}
}
}
}
@@ -517,10 +569,25 @@ async function main() {
// Reset failure counter on successful invite processing
state.consecutiveInviteFailures = 0;
// Send invite acknowledgment back to sender
// Send invite acknowledgment back to sender (wait for channel readiness)
try {
channelManager.sendToPeer(CORE_DOMAIN, 'request', peerId, 'invite_ack');
logInfo('Swarm', `Sent invite_ack to peer ${peerId}`);
const requestChannelInfo = channelManager.getChannelInfo(CORE_DOMAIN, 'request');
const requestPeerChannel = requestChannelInfo?.peerChannels?.get(peerId);
if (requestPeerChannel) {
// Wait for request channel to be ready before sending acknowledgment
const channelReady = await channelManager.waitForBidirectionalOpen(requestPeerChannel, 3000);
if (!channelReady) {
logWarn('Swarm', `Request channel not ready for invite_ack to ${peerId}, but proceeding (protomux will queue)`);
}
}
const sent = channelManager.sendToPeer(CORE_DOMAIN, 'request', peerId, 'invite_ack');
if (sent) {
logInfo('Swarm', `Sent invite_ack to peer ${peerId}`);
} else {
logWarn('Swarm', `Failed to send invite_ack to ${peerId} - channel not ready`);
}
} catch (ackErr) {
logWarn('Swarm', `Failed to send invite_ack: ${ackErr.message}`);
}
@@ -617,10 +684,16 @@ async function main() {
if (!pass) {
// This is expected for joiners that haven't received an invite yet
if (isMaster) {
logWarn('Swarm', `Ignoring invite request from ${peerId} as dnsPass is not initialized (unexpected for master)`);
// Send error response so requester knows there's an issue
logWarn('Swarm', `Master not ready yet, queuing invite request from ${peerId}`);
// Queue the request to process when dnsPass is ready
state.pendingInviteRequests.set(peerId, {
timestamp: Date.now(),
retryCount: 0
});
// Send acknowledgment that request is queued
try {
channelManager.sendToPeer(CORE_DOMAIN, 'request', peerId, 'invite_unavailable:master_not_ready');
channelManager.sendToPeer(CORE_DOMAIN, 'request', peerId, 'invite_queued:master_initializing');
} catch (err) {
// Ignore send errors - channel might be closed
}
@@ -1624,28 +1697,46 @@ async function main() {
const invitePeerChannel = inviteChannelInfo?.peerChannels?.get(peerId);
const requestPeerChannel = requestChannelInfo?.peerChannels?.get(peerId);
// Wait for invite channel to be bidirectionally open before sending (with timeout)
// Wait for invite channel to be bidirectionally open before sending (with increased timeout)
let inviteChannelReady = false;
if (invitePeerChannel) {
inviteChannelReady = await channelManager.waitForBidirectionalOpen(invitePeerChannel, 3000);
inviteChannelReady = await channelManager.waitForBidirectionalOpen(invitePeerChannel, 5000);
}
// Also check request channel readiness for acknowledgment handling
// Also check request channel readiness for acknowledgment handling (with increased timeout)
let requestChannelReady = false;
if (requestPeerChannel) {
requestChannelReady = await channelManager.waitForBidirectionalOpen(requestPeerChannel, 1000);
requestChannelReady = await channelManager.waitForBidirectionalOpen(requestPeerChannel, 3000);
}
// If invite channel isn't ready but request channel is, we can still proceed
// Protomux will queue messages even if channels aren't fully open
// Don't proceed unless at least one channel is ready - don't rely on Protomux queuing
if (!inviteChannelReady && !requestChannelReady) {
logWarn('Swarm', `Neither invite nor request channels ready for peer ${peerId}, will retry`);
// Schedule retry
setTimeout(async () => {
if (isConnectionStable(peerId, conn)) {
await sendProactiveInviteWithRetry(pass, peerId, conn, isReconnection, 3);
logWarn('Swarm', `Neither invite nor request channels ready for peer ${peerId} after extended wait, will retry with exponential backoff`);
// Schedule retry with exponential backoff starting at 2s
const baseRetryDelay = 2000;
const maxRetryDelay = 10000;
let retryDelay = baseRetryDelay;
let retryCount = 0;
const maxRetries = 5;
const retryInvite = async () => {
retryCount++;
if (retryCount > maxRetries || !isConnectionStable(peerId, conn)) {
logWarn('Swarm', `Failed to send proactive invite to ${peerId} after ${maxRetries} retries`);
return;
}
}, 2000);
setTimeout(async () => {
if (isConnectionStable(peerId, conn)) {
await sendProactiveInviteWithRetry(pass, peerId, conn, isReconnection, 3);
}
}, retryDelay);
// Exponential backoff: double the delay, cap at maxRetryDelay
retryDelay = Math.min(retryDelay * 2, maxRetryDelay);
};
retryInvite();
return;
}
@@ -1679,12 +1770,40 @@ async function main() {
const requestChannelInfo = channelManager.getChannelInfo(CORE_DOMAIN, 'request');
const requestPeerChannel = requestChannelInfo?.peerChannels?.get(targetPeerId);
let channelReady = false;
if (requestPeerChannel) {
// Give channel a moment to be ready, but don't block too long
const channelReady = await channelManager.waitForBidirectionalOpen(requestPeerChannel, 2000);
if (!channelReady) {
logDebug('Swarm', `Request channel not ready for peer ${targetPeerId}, but proceeding (protomux will queue)`);
}
// Wait for channel to be bidirectional before sending (increased timeout)
channelReady = await channelManager.waitForBidirectionalOpen(requestPeerChannel, 5000);
}
if (!channelReady) {
logWarn('Swarm', `Request channel not ready for peer ${targetPeerId} after extended wait, will retry with exponential backoff`);
// Schedule retry with exponential backoff starting at 1s
const baseRetryDelay = 1000;
const maxRetryDelay = 8000;
let retryDelay = baseRetryDelay;
let retryCount = 0;
const maxRetries = 4;
const retryRequest = async () => {
retryCount++;
if (retryCount > maxRetries || !connectedPeers.has(targetPeerId) || targetConn.destroyed) {
logWarn('Swarm', `Failed to send invite request to ${targetPeerId} after ${maxRetries} retries`);
return;
}
setTimeout(async () => {
if (connectedPeers.has(targetPeerId) && !targetConn.destroyed) {
await sendInviteRequestToPeer(targetPeerId, targetConn);
}
}, retryDelay);
// Exponential backoff: double the delay, cap at maxRetryDelay
retryDelay = Math.min(retryDelay * 2, maxRetryDelay);
};
retryRequest();
return;
}
try {
@@ -1699,10 +1818,10 @@ async function main() {
}
};
// Give channels a moment to initialize before sending request
// Give channels more time to initialize before sending request
setTimeout(async () => {
await sendInviteRequestToPeer(peerId, conn);
}, 100);
}, 500);
// Also request from all other connected peers (parallel requests)
setTimeout(async () => {