dnspass fixes

This commit is contained in:
Raven Scott
2026-05-28 01:20:52 -04:00
parent d95baaed1c
commit 9057b9661d
4 changed files with 129 additions and 52 deletions
+65 -12
View File
@@ -13,6 +13,32 @@ const { getVersionedCore } = require('./db-core-keys');
const MANIFEST_FILE = '.p2ns-db-version.json'; const MANIFEST_FILE = '.p2ns-db-version.json';
const VERSION_HISTORY_FILE = '.p2ns-version-history.json'; const VERSION_HISTORY_FILE = '.p2ns-version-history.json';
/** Plugins whose migration finished (or was skipped) this process — avoids duplicate Corestore opens. */
const completedMigrations = new Set();
/**
* @param {string} pluginDomain
* @param {string} pluginDir
* @returns {string}
*/
function migrationKey(pluginDomain, pluginDir) {
return `${pluginDomain}\0${pluginDir}`;
}
/**
* Yield the event loop after closing a migration Corestore so db-manager can reopen the path.
* @param {import('corestore')} store
* @returns {Promise<void>}
*/
async function closeMigrationStore(store) {
try {
await store.close();
} catch (err) {
logDebug('DBVersionMigration', `Error closing migration store: ${err.message}`);
}
await new Promise((resolve) => setImmediate(resolve));
}
/** /**
* @param {string} pluginDir * @param {string} pluginDir
* @returns {string} * @returns {string}
@@ -328,10 +354,26 @@ async function migrateBetweenVersions(pluginDomain, pluginDir, dbDir, fromVersio
logDebug('DBVersionMigration', `Error closing target db: ${err.message}`); logDebug('DBVersionMigration', `Error closing target db: ${err.message}`);
} }
} }
await closeMigrationStore(store);
}
}
/**
* Run pending HyperDB version migrations for loaded plugins before opening live databases.
* @param {Array<{ domain: string, pluginDir: string, dbConfig?: { dbDir: string }|null }>} plugins
* @returns {Promise<void>}
*/
async function runPendingPluginDatabaseMigrations(plugins) {
for (const plugin of plugins) {
if (!plugin?.dbConfig?.dbDir) continue;
try { try {
await store.close(); await ensurePluginDatabaseVersionMigration(plugin.domain, plugin.pluginDir, plugin.dbConfig.dbDir);
} catch (err) { } catch (err) {
logDebug('DBVersionMigration', `Error closing migration store: ${err.message}`); logError(
'DBVersionMigration',
`Pre-init migration failed for ${plugin.domain}: ${err.message} — database init may retry migration`
);
completedMigrations.delete(migrationKey(plugin.domain, plugin.pluginDir));
} }
} }
} }
@@ -429,11 +471,7 @@ async function versionCoreHasRecordsAt(pluginDir, pluginDomain, version, dbDir,
try { try {
return await versionCoreHasRecords(store, pluginDomain, version, dbDir, collectionIds); return await versionCoreHasRecords(store, pluginDomain, version, dbDir, collectionIds);
} finally { } finally {
try { await closeMigrationStore(store);
await store.close();
} catch (err) {
logDebug('DBVersionMigration', `Error closing version probe store: ${err.message}`);
}
} }
} }
@@ -486,11 +524,7 @@ async function discoverLegacySourceVersion(pluginDomain, pluginDir, dbDir, targe
} }
} }
} finally { } finally {
try { await closeMigrationStore(store);
await store.close();
} catch (err) {
logDebug('DBVersionMigration', `Error closing discovery store: ${err.message}`);
}
} }
return null; return null;
@@ -504,6 +538,15 @@ async function discoverLegacySourceVersion(pluginDomain, pluginDir, dbDir, targe
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async function ensurePluginDatabaseVersionMigration(pluginDomain, pluginDir, dbDir) { async function ensurePluginDatabaseVersionMigration(pluginDomain, pluginDir, dbDir) {
const key = migrationKey(pluginDomain, pluginDir);
if (completedMigrations.has(key)) {
return;
}
const markDone = () => {
completedMigrations.add(key);
};
const targetVersion = await readPluginConfigVersion(pluginDir); const targetVersion = await readPluginConfigVersion(pluginDir);
const hyperdbConfig = await readPluginHyperdbConfig(pluginDir); const hyperdbConfig = await readPluginHyperdbConfig(pluginDir);
const migrationPending = !isMigrationMarkedComplete(hyperdbConfig); const migrationPending = !isMigrationMarkedComplete(hyperdbConfig);
@@ -519,6 +562,7 @@ async function ensurePluginDatabaseVersionMigration(pluginDomain, pluginDir, dbD
logWarn('DBVersionMigration', `Marking migration complete for ${pluginDomain} to avoid retry loop`); logWarn('DBVersionMigration', `Marking migration complete for ${pluginDomain} to avoid retry loop`);
await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, hyperdbConfig?.previousVersion || null); await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, hyperdbConfig?.previousVersion || null);
} }
markDone();
return; return;
} }
} }
@@ -535,11 +579,13 @@ async function ensurePluginDatabaseVersionMigration(pluginDomain, pluginDir, dbD
} }
await appendVersionHistory(pluginDir, targetVersion); await appendVersionHistory(pluginDir, targetVersion);
logDebug('DBVersionMigration', `Skipping migration for ${pluginDomain} (hyperdb.migrated=true)`); logDebug('DBVersionMigration', `Skipping migration for ${pluginDomain} (hyperdb.migrated=true)`);
markDone();
return; return;
} }
if (manifest && manifest.version === targetVersion && !hyperdbConfig?.previousVersion) { if (manifest && manifest.version === targetVersion && !hyperdbConfig?.previousVersion) {
await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, null); await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, null);
markDone();
return; return;
} }
@@ -563,6 +609,7 @@ async function ensurePluginDatabaseVersionMigration(pluginDomain, pluginDir, dbD
`No manifest for ${pluginDomain}; data already on v${targetVersion} — migration skipped` `No manifest for ${pluginDomain}; data already on v${targetVersion} — migration skipped`
); );
await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, null); await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, null);
markDone();
return; return;
} }
@@ -581,6 +628,7 @@ async function ensurePluginDatabaseVersionMigration(pluginDomain, pluginDir, dbD
`No prior database version with data for ${pluginDomain} (target v${targetVersion}) — migration skipped` `No prior database version with data for ${pluginDomain} (target v${targetVersion}) — migration skipped`
); );
await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, hyperdbConfig?.previousVersion || null); await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, hyperdbConfig?.previousVersion || null);
markDone();
return; return;
} }
} }
@@ -588,6 +636,7 @@ async function ensurePluginDatabaseVersionMigration(pluginDomain, pluginDir, dbD
if (sourceVersion === targetVersion) { if (sourceVersion === targetVersion) {
logInfo('DBVersionMigration', `Source and target are both v${targetVersion} for ${pluginDomain} — migration skipped`); logInfo('DBVersionMigration', `Source and target are both v${targetVersion} for ${pluginDomain} — migration skipped`);
await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, sourceVersion); await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, sourceVersion);
markDone();
return; return;
} }
@@ -597,6 +646,7 @@ async function ensurePluginDatabaseVersionMigration(pluginDomain, pluginDir, dbD
`Target v${targetVersion} for ${pluginDomain} already has data — migration skipped` `Target v${targetVersion} for ${pluginDomain} already has data — migration skipped`
); );
await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, sourceVersion); await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, sourceVersion);
markDone();
return; return;
} }
@@ -613,6 +663,7 @@ async function ensurePluginDatabaseVersionMigration(pluginDomain, pluginDir, dbD
`No data on v${sourceVersion} for ${pluginDomain} — migration skipped (nothing to copy)` `No data on v${sourceVersion} for ${pluginDomain} — migration skipped (nothing to copy)`
); );
await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, sourceVersion); await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, sourceVersion);
markDone();
return; return;
} }
@@ -679,10 +730,12 @@ async function ensurePluginDatabaseVersionMigration(pluginDomain, pluginDir, dbD
} }
await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, sourceVersion); await finalizeVersionMigration(pluginDir, pluginDomain, targetVersion, sourceVersion);
markDone();
} }
module.exports = { module.exports = {
ensurePluginDatabaseVersionMigration, ensurePluginDatabaseVersionMigration,
runPendingPluginDatabaseMigrations,
readVersionManifest, readVersionManifest,
writeVersionManifest, writeVersionManifest,
getCollectionIds getCollectionIds
+16 -2
View File
@@ -548,7 +548,9 @@ async function loadAllPlugins() {
await fs.mkdir(pluginSitesDir, { recursive: true }); await fs.mkdir(pluginSitesDir, { recursive: true });
} }
// Load plugins for each discovered internal domain const pluginsToInit = [];
// Load plugin modules and schemas first (no live HyperDB yet)
for (const domain of internalDomains) { for (const domain of internalDomains) {
const pluginDir = path.join(pluginSitesDir, domain); const pluginDir = path.join(pluginSitesDir, domain);
const plugin = await loadPlugin(domain, pluginDir); const plugin = await loadPlugin(domain, pluginDir);
@@ -556,12 +558,24 @@ async function loadAllPlugins() {
if (plugin) { if (plugin) {
plugins.set(domain, plugin); plugins.set(domain, plugin);
loadedPlugins.set(domain, plugin); loadedPlugins.set(domain, plugin);
await initializePlugin(plugin); pluginsToInit.push(plugin);
} else { } else {
logDebug('PluginHandler', `No plugin found for ${domain}, will use fallback handler`); logDebug('PluginHandler', `No plugin found for ${domain}, will use fallback handler`);
} }
} }
// Run version migrations before opening plugin Corestores (avoids lock contention with db-manager)
try {
const { runPendingPluginDatabaseMigrations } = require('./db-version-migration');
await runPendingPluginDatabaseMigrations(pluginsToInit);
} catch (err) {
logWarn('PluginHandler', `Plugin database migration phase failed: ${err.message}`);
}
for (const plugin of pluginsToInit) {
await initializePlugin(plugin);
}
logInfo('PluginHandler', `Loaded ${loadedPlugins.size} plugin(s) out of ${internalDomains.length} internal domain(s)`); logInfo('PluginHandler', `Loaded ${loadedPlugins.size} plugin(s) out of ${internalDomains.length} internal domain(s)`);
return loadedPlugins; return loadedPlugins;
} }
+47 -37
View File
@@ -614,6 +614,37 @@ async function main() {
} }
state.sendConflictClaimRemoval = sendConflictClaimRemoval; state.sendConflictClaimRemoval = sendConflictClaimRemoval;
const inviteChannelWaitMs = parseInt(process.env.INVITE_CHANNEL_WAIT_MS || '5000', 10);
/** Joiner: request Autopass invite once the request channel is ready. */
async function requestInviteFromPeer(targetPeerId) {
if (isMaster || getDnsPass()) {
return false;
}
if (failedInvitePeers.has(targetPeerId)) {
logDebug('Swarm', `Skipping invite request for peer ${targetPeerId} (cannot provide invite)`);
return false;
}
if (!connectedPeers.has(targetPeerId)) {
return false;
}
const sent = await channelManager.sendToPeerAsync(
CORE_DOMAIN,
'request',
targetPeerId,
'request_invite',
inviteChannelWaitMs
);
if (sent) {
logInfo('Swarm', `Invite request sent to peer ${targetPeerId}`);
} else {
logWarn('Swarm', `Request channel not ready for peer ${targetPeerId}`);
}
return sent;
}
state.requestInviteFromPeer = requestInviteFromPeer;
// Register invite channel for sending/receiving invites // Register invite channel for sending/receiving invites
channelManager.registerPluginChannel(CORE_DOMAIN, 'invite', { channelManager.registerPluginChannel(CORE_DOMAIN, 'invite', {
encoding: 'string', encoding: 'string',
@@ -786,6 +817,15 @@ async function main() {
channelManager.registerPluginChannel(CORE_DOMAIN, 'request', { channelManager.registerPluginChannel(CORE_DOMAIN, 'request', {
encoding: 'string', encoding: 'string',
autoReconnect: true, autoReconnect: true,
onOpen: (peerId) => {
if (isMaster || getDnsPass()) {
return;
}
logDebug('Swarm', `Request channel open for peer ${peerId.substring(0, 16)}..., requesting invite`);
requestInviteFromPeer(peerId).catch((err) => {
logDebug('Swarm', `Invite request on channel open failed for ${peerId}: ${err.message}`);
});
},
onMessage: async (message, peerId) => { onMessage: async (message, peerId) => {
logInfo('Swarm', `Received message from peer ${peerId}: ${message}`); logInfo('Swarm', `Received message from peer ${peerId}: ${message}`);
@@ -1383,6 +1423,7 @@ async function main() {
logInfo('Swarm', `Starting persistent invite retry loop (interval: ${retryIntervalMs}ms, relay after ${relayAfterAttempts} attempts, max attempts: ${maxRetryAttempts})`); logInfo('Swarm', `Starting persistent invite retry loop (interval: ${retryIntervalMs}ms, relay after ${relayAfterAttempts} attempts, max attempts: ${maxRetryAttempts})`);
persistentInviteRetryInterval = setInterval(() => { persistentInviteRetryInterval = setInterval(() => {
void (async () => {
const pass = getDnsPass(); const pass = getDnsPass();
if (pass) { if (pass) {
// Got invite, stop the loop // Got invite, stop the loop
@@ -1405,14 +1446,14 @@ async function main() {
// Broadcast invite request to all connected peers // Broadcast invite request to all connected peers
if (state.broadcastInviteRequest) { if (state.broadcastInviteRequest) {
logDebug('Swarm', `Persistent retry (attempt ${retryAttemptCount}): broadcasting invite request to all peers`); logDebug('Swarm', `Persistent retry (attempt ${retryAttemptCount}): broadcasting invite request to all peers`);
state.broadcastInviteRequest(); await state.broadcastInviteRequest();
} else if (connectedPeers.size > 0) { } else if (connectedPeers.size > 0) {
// Fallback: manually send to all peers // Fallback: manually send to all peers
logDebug('Swarm', `Persistent retry (attempt ${retryAttemptCount}): sending invite requests (fallback mode)`); logDebug('Swarm', `Persistent retry (attempt ${retryAttemptCount}): sending invite requests (fallback mode)`);
for (const otherPeerId of connectedPeers) { for (const otherPeerId of connectedPeers) {
if (failedInvitePeers.has(otherPeerId)) continue; if (failedInvitePeers.has(otherPeerId)) continue;
try { try {
channelManager.sendToPeer(CORE_DOMAIN, 'request', otherPeerId, 'request_invite'); await requestInviteFromPeer(otherPeerId);
} catch (err) { } catch (err) {
logError('Swarm', `Error in persistent retry to ${otherPeerId}: ${err.message}`); logError('Swarm', `Error in persistent retry to ${otherPeerId}: ${err.message}`);
} }
@@ -1437,6 +1478,7 @@ async function main() {
} }
} }
} }
})();
}, retryIntervalMs); }, retryIntervalMs);
// Store reference for cleanup // Store reference for cleanup
@@ -2193,8 +2235,7 @@ async function main() {
// Send request to this peer // Send request to this peer
const sendInviteRequestToPeer = async (targetPeerId, targetConn) => { const sendInviteRequestToPeer = async (targetPeerId, targetConn) => {
const pass = getDnsPass(); if (getDnsPass()) {
if (pass) {
logDebug('Swarm', 'dnsPass already initialized, skipping invite request'); logDebug('Swarm', 'dnsPass already initialized, skipping invite request');
return; return;
} }
@@ -2204,27 +2245,8 @@ async function main() {
return; return;
} }
// Check if request channel is available and ready
const requestChannelInfo = channelManager.getChannelInfo(CORE_DOMAIN, 'request');
const requestPeerChannel = requestChannelInfo?.peerChannels?.get(targetPeerId);
let channelReady = false;
if (requestPeerChannel) {
// Wait for channel to be bidirectional before sending
channelReady = await channelManager.waitForBidirectionalOpen(requestPeerChannel, 2000);
}
if (!channelReady) {
logDebug('Swarm', `Request channel not ready for peer ${targetPeerId}, proceeding anyway (protomux will queue)`);
}
try { try {
const sent = channelManager.sendToPeer(CORE_DOMAIN, 'request', targetPeerId, 'request_invite'); await requestInviteFromPeer(targetPeerId);
if (sent) {
logInfo('Swarm', `Invite request sent to peer ${targetPeerId}`);
} else {
logWarn('Swarm', `Request channel not ready for peer ${targetPeerId}`);
}
} catch (err) { } catch (err) {
logError('Swarm', `Error sending invite request to peer ${targetPeerId}: ${err.message}`); logError('Swarm', `Error sending invite request to peer ${targetPeerId}: ${err.message}`);
} }
@@ -2269,21 +2291,9 @@ async function main() {
const otherConn = channels.conn; const otherConn = channels.conn;
if (!otherConn || otherConn.destroyed) continue; if (!otherConn || otherConn.destroyed) continue;
// Check channel readiness before sending
const requestChannelInfo = channelManager.getChannelInfo(CORE_DOMAIN, 'request');
const requestPeerChannel = requestChannelInfo?.peerChannels?.get(otherPeerId);
const sendPromise = (async () => { const sendPromise = (async () => {
if (requestPeerChannel) {
// Give channel a moment to be ready, but don't block too long
const channelReady = await channelManager.waitForBidirectionalOpen(requestPeerChannel, 1000);
if (!channelReady) {
logDebug('Swarm', `Request channel not ready for broadcast to peer ${otherPeerId}, but proceeding`);
}
}
try { try {
const sent = channelManager.sendToPeer(CORE_DOMAIN, 'request', otherPeerId, 'request_invite'); const sent = await requestInviteFromPeer(otherPeerId);
if (sent) sentCount++; if (sent) sentCount++;
return sent; return sent;
} catch (err) { } catch (err) {
+1 -1
View File
@@ -11,7 +11,7 @@
"www": "www", "www": "www",
"hyperdb": { "hyperdb": {
"previousVersion": "1.4.2", "previousVersion": "1.4.2",
"migrated": false, "migrated": true,
"schemas": { "schemas": {
"namespace": "peerpaste", "namespace": "peerpaste",
"structs": [ "structs": [