| … | … | @@ -172,10 +172,10 @@ async function handleConnect(service, serverKey, repoName, httpsUrl, stdout, std |
| 172 | 172 | function cleanup (code) { |
| 173 | 173 | if (cleaningUp) return |
| 174 | 174 | cleaningUp = true |
| 175 | // Skip async teardown — just exit. The OS reclaims all resources. | |
| 176 | // Hyperswarm/libudx teardown has a native double-free bug that | |
| 177 | // crashes the process during conn.destroy() or swarm.destroy(). | |
| 178 | process.exit(code) | |
| 175 | // Graceful teardown — destroy swarm properly. | |
| 176 | // Requires udx-native >= 1.20.3 (libudx 0420f62) which fixes | |
| 177 | // use-after-free and double-free bugs in stream teardown. | |
| 178 | connector.destroy().catch(() => {}).then(() => process.exit(code)) | |
| 179 | 179 | } |
| 180 | 180 | |
| 181 | 181 | // Manual bidirectional forwarding — NOT pipe()! |
| … | … | @@ -221,10 +221,8 @@ async function handleConnect(service, serverKey, repoName, httpsUrl, stdout, std |
| 221 | 221 | } catch (err) { |
| 222 | 222 | stderr.write(`[punch] P2P connection failed: ${err.message}\n`) |
| 223 | 223 | |
| 224 | // Destroy swarm fully before exiting — premature process.exit() | |
| 225 | // during async Hyperswarm teardown causes double-free crashes | |
| 226 | // (libudx/io_uring buffers freed while still in use) and zombie | |
| 227 | // processes that spin at 100% CPU. | |
| 224 | // Destroy swarm before HTTPS fallback. Hard kill timeout as | |
| 225 | // fallback — connector.destroy() can trigger the libudx double-free. | |
| 228 | 226 | const hardKill = setTimeout(() => process.exit(128), 5000) |
| 229 | 227 | hardKill.unref() |
| 230 | 228 | try { await connector.destroy() } catch {} |
| … | … | @@ -32,7 +32,8 @@ |
| 32 | 32 | "pear-ipc": "^6.10.0", |
| 33 | 33 | "pear-runtime": "^0.4.2", |
| 34 | 34 | "secure-prompt": "github:holepunchto/secure-prompt", |
| 35 | "sodium-native": "^4.3.1" | |
| 35 | "sodium-native": "^4.3.1", | |
| 36 | "udx-native": "^1.20.3" | |
| 36 | 37 | }, |
| 37 | 38 | "bin": { |
| 38 | 39 | "git-remote-pear": "bin/git-remote-pear", |
| … | … | @@ -2402,9 +2403,9 @@ |
| 2402 | 2403 | } |
| 2403 | 2404 | }, |
| 2404 | 2405 | "node_modules/udx-native": { |
| 2405 | "version": "1.19.2", | |
| 2406 | "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.19.2.tgz", | |
| 2407 | "integrity": "sha512-RNYh+UhfryCsF5hE2ZOuIqcZ+qdipXK3UsarwxWJwsUQZFE3ybwz0mPjwb5ev1PMBcjFahWiepS/q0wwL51c2g==", | |
| 2406 | "version": "1.20.3", | |
| 2407 | "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.20.3.tgz", | |
| 2408 | "integrity": "sha512-BI2bgW3GRzdiRkZ36SJMbF40djHmjdb679L8GwsMlX5k4LRtW4JuPuvSdVAIsH1mmLvQ+NYXGQ/jGuh7670I9w==", | |
| 2408 | 2409 | "license": "Apache-2.0", |
| 2409 | 2410 | "dependencies": { |
| 2410 | 2411 | "b4a": "^1.5.0", |
| … | … | @@ -57,7 +57,8 @@ |
| 57 | 57 | "pear-ipc": "^6.10.0", |
| 58 | 58 | "pear-runtime": "^0.4.2", |
| 59 | 59 | "secure-prompt": "github:holepunchto/secure-prompt", |
| 60 | "sodium-native": "^4.3.1" | |
| 60 | "sodium-native": "^4.3.1", | |
| 61 | "udx-native": "^1.20.3" | |
| 61 | 62 | }, |
| 62 | 63 | "devDependencies": { |
| 63 | 64 | "@noble/ed25519": "^3.0.1", |
| … | … | @@ -699,8 +699,10 @@ class PearGitServer { |
| 699 | 699 | */ |
| 700 | 700 | _watchLinkedRepoRefs (repoName, linked) { |
| 701 | 701 | let syncing = false |
| 702 | let cooldownUntil = 0 | |
| 702 | 703 | linked._base.on('update', async () => { |
| 703 | 704 | if (syncing) return |
| 705 | if (Date.now() < cooldownUntil) return | |
| 704 | 706 | syncing = true |
| 705 | 707 | try { |
| 706 | 708 | const linkedRefs = await linked.listRefs() |
| … | … | @@ -734,9 +736,13 @@ class PearGitServer { |
| 734 | 736 | } |
| 735 | 737 | if (fedPeers.length > 0) { |
| 736 | 738 | console.log(`[RefSync] ${repoName}: refs changed in LinkedRepo, fetching from ${fedPeers[0].slice(0, 8)}`) |
| 737 | this.gitSyncClientManager._syncRepo(repoName, repoPath, fedPeers[0]).catch(err => { | |
| 739 | try { | |
| 740 | await this.gitSyncClientManager._syncRepo(repoName, repoPath, fedPeers[0]) | |
| 741 | } catch (err) { | |
| 738 | 742 | console.error(`[RefSync] Fetch failed for ${repoName}: ${err.message}`) |
| 739 | }) | |
| 743 | // Back off 60s after failure to prevent retry storms | |
| 744 | cooldownUntil = Date.now() + 60_000 | |
| 745 | } | |
| 740 | 746 | } |
| 741 | 747 | } |
| 742 | 748 | } catch (err) { |