OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git commits / a2b7e81

Fix RefSync retry storms, update udx-native to fix double-free, graceful teardown
RefSync: _watchLinkedRepoRefs fired _syncRepo as fire-and-forget — the syncing flag went false immediately, causing retry storms on every Autobase update. Now awaits _syncRepo and backs off 60s after failure. udx-native: updated from 1.19.2 to 1.20.3 (@next) which includes libudx fixes for use-after-free (fb414d0), double-free on interface close (6f762f8), and stream-closed-on-reentry (0420f62). These 14 commits fix the root cause of the "double free detected in tcache" crash during Hyperswarm teardown. git-remote-punch: replaced process.exit() workaround with graceful connector.destroy() teardown, now that udx-native no longer crashes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
a2b7e8146075e33187f83b4e45e4ea9cb0bdc11a
by plankatron <fleeky@gmail.com> · 2026-04-09T19:49:01.000Z

Changed files (4) +21 -15

bin/git-remote-punch +6 -8
……@@ -172,10 +172,10 @@ async function handleConnect(service, serverKey, repoName, httpsUrl, stdout, std
172172 function cleanup (code) {
173173 if (cleaningUp) return
174174 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))
179179 }
180180
181181 // Manual bidirectional forwarding — NOT pipe()!
……@@ -221,10 +221,8 @@ async function handleConnect(service, serverKey, repoName, httpsUrl, stdout, std
221221 } catch (err) {
222222 stderr.write(`[punch] P2P connection failed: ${err.message}\n`)
223223
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.
228226 const hardKill = setTimeout(() => process.exit(128), 5000)
229227 hardKill.unref()
230228 try { await connector.destroy() } catch {}
package-lock.json +5 -4
……@@ -32,7 +32,8 @@
3232 "pear-ipc": "^6.10.0",
3333 "pear-runtime": "^0.4.2",
3434 "secure-prompt": "github:holepunchto/secure-prompt",
35 "sodium-native": "^4.3.1"
35 "sodium-native": "^4.3.1",
36 "udx-native": "^1.20.3"
3637 },
3738 "bin": {
3839 "git-remote-pear": "bin/git-remote-pear",
……@@ -2402,9 +2403,9 @@
24022403 }
24032404 },
24042405 "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==",
24082409 "license": "Apache-2.0",
24092410 "dependencies": {
24102411 "b4a": "^1.5.0",
package.json +2 -1
……@@ -57,7 +57,8 @@
5757 "pear-ipc": "^6.10.0",
5858 "pear-runtime": "^0.4.2",
5959 "secure-prompt": "github:holepunchto/secure-prompt",
60 "sodium-native": "^4.3.1"
60 "sodium-native": "^4.3.1",
61 "udx-native": "^1.20.3"
6162 },
6263 "devDependencies": {
6364 "@noble/ed25519": "^3.0.1",
pear-git-server.js +8 -2
……@@ -699,8 +699,10 @@ class PearGitServer {
699699 */
700700 _watchLinkedRepoRefs (repoName, linked) {
701701 let syncing = false
702 let cooldownUntil = 0
702703 linked._base.on('update', async () => {
703704 if (syncing) return
705 if (Date.now() < cooldownUntil) return
704706 syncing = true
705707 try {
706708 const linkedRefs = await linked.listRefs()
……@@ -734,9 +736,13 @@ class PearGitServer {
734736 }
735737 if (fedPeers.length > 0) {
736738 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) {
738742 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 }
740746 }
741747 }
742748 } catch (err) {