tests
This commit is contained in:
@@ -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}>
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user