From f3d5f35d9aa456806a785a6b388c61f0339cc4d1 Mon Sep 17 00:00:00 2001 From: Jan Keith Darunday Date: Wed, 18 Mar 2026 02:14:05 +0800 Subject: [PATCH 1/6] Add test case for write after close corruption --- test.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test.js b/test.js index 97fd7dc6..b362d80a 100644 --- a/test.js +++ b/test.js @@ -1845,6 +1845,57 @@ test('dedup mode', async (t) => { t.is(drive.blobs.core.length, len + 1) }) +test('write after close should not corrupt drive', async (t) => { + const platformCorestore = new Corestore(await t.tmp() , { + manifestVersion: 1, + compat: false, + wait: true + }) + await platformCorestore.ready() + + { + const corestore = platformCorestore.session({ writable: true }) + await corestore.ready() + t.teardown(() => corestore.close()) + + const drive = new Hyperdrive(corestore) + await drive.ready() + t.teardown(() => drive.close()) + + await drive.db.put('manifest', 'hello world') + + const batch = drive.batch() + try { + for (let i = 0; i < 14; i++) { + const stream = batch.createWriteStream('/file' + i + '.txt') + const close = new Promise(resolve => stream.on('close', resolve)) + stream.end('hello world' + i) + await close + + if (i === 2) await drive.close() + } + await batch.flush() + t.fail('batch should have errored when drive is closed') + } catch (err) { + t.pass('batch should error when drive is closed') + } + await corestore.close() + } + + { + const corestore = platformCorestore.session({ writable: false }) + await corestore.ready() + t.teardown(() => corestore.close()) + + const drive = new Hyperdrive(corestore) + await drive.ready() + t.teardown(() => drive.close()) + + const manifest = await drive.db.get('manifest') + t.is(manifest.value, 'hello world', 'should correctly read manifest') + } +}) + async function testenv(t) { const { teardown } = t From c25f3095d62ea69fb291b7fc71aeb0665fda8580 Mon Sep 17 00:00:00 2001 From: Jan Keith Darunday Date: Wed, 18 Mar 2026 02:20:00 +0800 Subject: [PATCH 2/6] Apply npm run format --- test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test.js b/test.js index b362d80a..0b3bc1d2 100644 --- a/test.js +++ b/test.js @@ -1846,7 +1846,7 @@ test('dedup mode', async (t) => { }) test('write after close should not corrupt drive', async (t) => { - const platformCorestore = new Corestore(await t.tmp() , { + const platformCorestore = new Corestore(await t.tmp(), { manifestVersion: 1, compat: false, wait: true @@ -1868,7 +1868,7 @@ test('write after close should not corrupt drive', async (t) => { try { for (let i = 0; i < 14; i++) { const stream = batch.createWriteStream('/file' + i + '.txt') - const close = new Promise(resolve => stream.on('close', resolve)) + const close = new Promise((resolve) => stream.on('close', resolve)) stream.end('hello world' + i) await close From 095a103943564f8f360bb96837eefbf730a9c036 Mon Sep 17 00:00:00 2001 From: Jan Keith Darunday Date: Thu, 19 Mar 2026 11:59:40 +0800 Subject: [PATCH 3/6] Track batches --- index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index debeb97c..465fda15 100644 --- a/index.js +++ b/index.js @@ -33,6 +33,7 @@ module.exports = class Hyperdrive extends ReadyResource { this.supportsMetadata = true this.encryptionKey = opts.encryptionKey || null this.monitors = new Set() + this.batches = new Set() this._active = opts.active !== false this._openingBlobs = null @@ -164,12 +165,16 @@ module.exports = class Hyperdrive extends ReadyResource { } batch() { - return new Hyperdrive(this.corestore, this.key, { + const batch = new Hyperdrive(this.corestore, this.key, { onwait: this._onwait, encryptionKey: this.encryptionKey, _checkout: null, _db: this.db.batch() }) + batch.on('close', () => this.batches.delete(batch)) + this.batches.add(batch) + + return batch } setActive(bool) { From 2cef25ec5ab400f2b6cff970c2c268c91a21d0b7 Mon Sep 17 00:00:00 2001 From: Jan Keith Darunday Date: Thu, 19 Mar 2026 12:00:37 +0800 Subject: [PATCH 4/6] Close batch when closing --- index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.js b/index.js index 465fda15..a2882c6a 100644 --- a/index.js +++ b/index.js @@ -195,6 +195,8 @@ module.exports = class Hyperdrive extends ReadyResource { await this.blobs.core.close() } + for (const batch of this.batches) await batch.close() + await this.db.close() if (!this._checkout && !this._batching) { From 2f5075b74a57cb729ce9de525c1182345edebb8b Mon Sep 17 00:00:00 2001 From: Jan Keith Darunday Date: Thu, 19 Mar 2026 12:01:12 +0800 Subject: [PATCH 5/6] Prevent creation of write streams when closing --- index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.js b/index.js index a2882c6a..ad3b737d 100644 --- a/index.js +++ b/index.js @@ -569,6 +569,8 @@ module.exports = class Hyperdrive extends ReadyResource { } createWriteStream(name, { executable = false, metadata = null, dedup = false } = {}) { + if (this.closing) throw new Error('Closed') + const self = this let destroyed = false From f6a27ebb92157b320cf555ba03e364ebb22ae75a Mon Sep 17 00:00:00 2001 From: Jan Keith Darunday Date: Sat, 21 Mar 2026 11:45:58 +0800 Subject: [PATCH 6/6] Add missing platform corestore close --- test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test.js b/test.js index 0b3bc1d2..d6e94168 100644 --- a/test.js +++ b/test.js @@ -1852,6 +1852,7 @@ test('write after close should not corrupt drive', async (t) => { wait: true }) await platformCorestore.ready() + t.teardown(() => platformCorestore.close()) { const corestore = platformCorestore.session({ writable: true })