Skip to content

Commit e79bb4f

Browse files
rom1504claude
andcommitted
Fix zlib crashes on Node 24: use sync zlib to catch all errors
Node 24's stricter zlib can throw asynchronously via the internal Zlib C++ binding's onerror callback, bypassing try/catch around the async zlib.unzip()/zlib.deflate() calls. These become uncaught exceptions that crash the process. Fix: switch to zlib.unzipSync/deflateSync wrapped in try/catch. Synchronous calls ensure all errors are catchable. The Transform stream's _transform callback handles backpressure, so sync zlib inside _transform is safe and doesn't block the event loop (each call processes one packet, not large data). Also wrap gunzipSync in minecraft.js for NBT parsing. Also suppress compressor/decompressor errors during client shutdown. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dfecf1e commit e79bb4f

3 files changed

Lines changed: 29 additions & 18 deletions

File tree

src/client.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,15 @@ class Client extends EventEmitter {
224224
setCompressionThreshold (threshold) {
225225
if (this.compressor == null) {
226226
this.compressor = compression.createCompressor(threshold)
227-
this.compressor.on('error', (err) => this.emit('error', err))
227+
this.compressor.on('error', (err) => {
228+
if (!this.ended) this.emit('error', err)
229+
})
228230
this.serializer.unpipe(this.framer)
229231
this.serializer.pipe(this.compressor).pipe(this.framer)
230232
this.decompressor = compression.createDecompressor(threshold, this.hideErrors)
231-
this.decompressor.on('error', (err) => this.emit('error', err))
233+
this.decompressor.on('error', (err) => {
234+
if (!this.ended) this.emit('error', err)
235+
})
232236
this.splitter.unpipe(this.deserializer)
233237
this.splitter.pipe(this.decompressor).pipe(this.deserializer)
234238
} else {

src/datatypes/minecraft.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ function readCompressedNbt (buffer, offset) {
5656

5757
const compressedNbt = buffer.slice(offset + 2, offset + 2 + length)
5858

59-
const nbtBuffer = zlib.gunzipSync(compressedNbt) // TODO: async
59+
let nbtBuffer
60+
try {
61+
nbtBuffer = zlib.gunzipSync(compressedNbt) // TODO: async
62+
} catch (err) {
63+
throw new PartialReadError('zlib decompress failed: ' + err.message)
64+
}
6065

6166
const results = nbt.proto.read(nbtBuffer, 0, 'nbt')
6267
return {

src/transforms/compression.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ class Compressor extends Transform {
2020

2121
_transform (chunk, enc, cb) {
2222
if (chunk.length >= this.compressionThreshold) {
23-
zlib.deflate(chunk, (err, newChunk) => {
24-
if (err) { return cb(err) }
23+
try {
24+
const newChunk = zlib.deflateSync(chunk)
2525
const buf = Buffer.alloc(sizeOfVarInt(chunk.length) + newChunk.length)
2626
const offset = writeVarInt(chunk.length, buf, 0)
2727
newChunk.copy(buf, offset)
2828
this.push(buf)
2929
return cb()
30-
})
30+
} catch (err) {
31+
return cb(err)
32+
}
3133
} else {
3234
const buf = Buffer.alloc(sizeOfVarInt(0) + chunk.length)
3335
const offset = writeVarInt(0, buf, 0)
@@ -52,23 +54,23 @@ class Decompressor extends Transform {
5254
this.push(chunk.slice(size))
5355
return cb()
5456
} else {
55-
zlib.unzip(chunk.slice(size), { finishFlush: 2 /* Z_SYNC_FLUSH = 2, but when using Browserify/Webpack it doesn't exist */ }, (err, newBuf) => { /** Fix by lefela4. */
56-
if (err) {
57-
if (!this.hideErrors) {
58-
console.error('problem inflating chunk')
59-
console.error('uncompressed length ' + value)
60-
console.error('compressed length ' + chunk.length)
61-
console.error('hex ' + chunk.toString('hex'))
62-
console.log(err)
63-
}
64-
return cb()
65-
}
57+
try {
58+
const newBuf = zlib.unzipSync(chunk.slice(size), { finishFlush: 2 })
6659
if (newBuf.length !== value && !this.hideErrors) {
6760
console.error('uncompressed length should be ' + value + ' but is ' + newBuf.length)
6861
}
6962
this.push(newBuf)
7063
return cb()
71-
})
64+
} catch (err) {
65+
if (!this.hideErrors) {
66+
console.error('problem inflating chunk')
67+
console.error('uncompressed length ' + value)
68+
console.error('compressed length ' + chunk.length)
69+
console.error('hex ' + chunk.toString('hex'))
70+
console.log(err)
71+
}
72+
return cb()
73+
}
7274
}
7375
}
7476
}

0 commit comments

Comments
 (0)