jexidb 2.1.7 → 2.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Database.cjs +33 -1
- package/package.json +1 -1
- package/src/FileHandler.mjs +38 -1
package/dist/Database.cjs
CHANGED
|
@@ -4126,6 +4126,37 @@ class FileHandler {
|
|
|
4126
4126
|
// Global I/O limiter to prevent file descriptor exhaustion in concurrent operations
|
|
4127
4127
|
this.readLimiter = pLimit(opts.maxConcurrentReads || 4);
|
|
4128
4128
|
}
|
|
4129
|
+
_getIoTimeoutMs(override) {
|
|
4130
|
+
if (typeof override === 'number') return override;
|
|
4131
|
+
if (typeof this.opts.ioTimeoutMs === 'number') return this.opts.ioTimeoutMs;
|
|
4132
|
+
return 0;
|
|
4133
|
+
}
|
|
4134
|
+
async _withIoTimeout(fn, timeoutMs, onTimeout) {
|
|
4135
|
+
if (!timeoutMs || timeoutMs <= 0) {
|
|
4136
|
+
return fn();
|
|
4137
|
+
}
|
|
4138
|
+
let timeoutId;
|
|
4139
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
4140
|
+
timeoutId = setTimeout(() => {
|
|
4141
|
+
if (onTimeout) {
|
|
4142
|
+
try {
|
|
4143
|
+
onTimeout();
|
|
4144
|
+
} catch {}
|
|
4145
|
+
}
|
|
4146
|
+
const err = new Error(`I/O timeout after ${timeoutMs}ms`);
|
|
4147
|
+
err.code = 'ETIMEDOUT';
|
|
4148
|
+
reject(err);
|
|
4149
|
+
}, timeoutMs);
|
|
4150
|
+
});
|
|
4151
|
+
try {
|
|
4152
|
+
return await Promise.race([fn(), timeoutPromise]);
|
|
4153
|
+
} finally {
|
|
4154
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
async _readWithTimeout(fd, buffer, offset, length, position, timeoutMs) {
|
|
4158
|
+
return this._withIoTimeout(() => fd.read(buffer, offset, length, position), timeoutMs, () => fd.close().catch(() => {}));
|
|
4159
|
+
}
|
|
4129
4160
|
async truncate(offset) {
|
|
4130
4161
|
try {
|
|
4131
4162
|
await fs.promises.access(this.file, fs.constants.F_OK);
|
|
@@ -4230,6 +4261,7 @@ class FileHandler {
|
|
|
4230
4261
|
if (!(await this.exists())) {
|
|
4231
4262
|
return Buffer.alloc(0); // Return empty buffer if file doesn't exist
|
|
4232
4263
|
}
|
|
4264
|
+
const timeoutMs = this._getIoTimeoutMs();
|
|
4233
4265
|
let fd = await fs.promises.open(this.file, 'r');
|
|
4234
4266
|
try {
|
|
4235
4267
|
// CRITICAL FIX: Check file size before attempting to read
|
|
@@ -4254,7 +4286,7 @@ class FileHandler {
|
|
|
4254
4286
|
let buffer = Buffer.alloc(length);
|
|
4255
4287
|
const {
|
|
4256
4288
|
bytesRead
|
|
4257
|
-
} = await
|
|
4289
|
+
} = await this._readWithTimeout(fd, buffer, 0, length, start, timeoutMs);
|
|
4258
4290
|
await fd.close();
|
|
4259
4291
|
|
|
4260
4292
|
// CRITICAL FIX: Ensure we read the expected amount of data
|
package/package.json
CHANGED
package/src/FileHandler.mjs
CHANGED
|
@@ -15,6 +15,42 @@ export default class FileHandler {
|
|
|
15
15
|
this.readLimiter = pLimit(opts.maxConcurrentReads || 4)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
_getIoTimeoutMs(override) {
|
|
19
|
+
if (typeof override === 'number') return override
|
|
20
|
+
if (typeof this.opts.ioTimeoutMs === 'number') return this.opts.ioTimeoutMs
|
|
21
|
+
return 0
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async _withIoTimeout(fn, timeoutMs, onTimeout) {
|
|
25
|
+
if (!timeoutMs || timeoutMs <= 0) {
|
|
26
|
+
return fn()
|
|
27
|
+
}
|
|
28
|
+
let timeoutId
|
|
29
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
30
|
+
timeoutId = setTimeout(() => {
|
|
31
|
+
if (onTimeout) {
|
|
32
|
+
try { onTimeout() } catch {}
|
|
33
|
+
}
|
|
34
|
+
const err = new Error(`I/O timeout after ${timeoutMs}ms`)
|
|
35
|
+
err.code = 'ETIMEDOUT'
|
|
36
|
+
reject(err)
|
|
37
|
+
}, timeoutMs)
|
|
38
|
+
})
|
|
39
|
+
try {
|
|
40
|
+
return await Promise.race([fn(), timeoutPromise])
|
|
41
|
+
} finally {
|
|
42
|
+
if (timeoutId) clearTimeout(timeoutId)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async _readWithTimeout(fd, buffer, offset, length, position, timeoutMs) {
|
|
47
|
+
return this._withIoTimeout(
|
|
48
|
+
() => fd.read(buffer, offset, length, position),
|
|
49
|
+
timeoutMs,
|
|
50
|
+
() => fd.close().catch(() => {})
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
18
54
|
async truncate(offset) {
|
|
19
55
|
try {
|
|
20
56
|
await fs.promises.access(this.file, fs.constants.F_OK)
|
|
@@ -128,6 +164,7 @@ export default class FileHandler {
|
|
|
128
164
|
return Buffer.alloc(0) // Return empty buffer if file doesn't exist
|
|
129
165
|
}
|
|
130
166
|
|
|
167
|
+
const timeoutMs = this._getIoTimeoutMs()
|
|
131
168
|
let fd = await fs.promises.open(this.file, 'r')
|
|
132
169
|
try {
|
|
133
170
|
// CRITICAL FIX: Check file size before attempting to read
|
|
@@ -151,7 +188,7 @@ export default class FileHandler {
|
|
|
151
188
|
}
|
|
152
189
|
|
|
153
190
|
let buffer = Buffer.alloc(length)
|
|
154
|
-
const { bytesRead } = await
|
|
191
|
+
const { bytesRead } = await this._readWithTimeout(fd, buffer, 0, length, start, timeoutMs)
|
|
155
192
|
await fd.close()
|
|
156
193
|
|
|
157
194
|
// CRITICAL FIX: Ensure we read the expected amount of data
|