Skip to content

Commit f34f936

Browse files
authored
fix(core): fix potential File Descriptor leak on table drop (#6053)
1 parent 7454770 commit f34f936

3 files changed

Lines changed: 58 additions & 28 deletions

File tree

core/src/main/java/io/questdb/cairo/TableWriter.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7652,8 +7652,12 @@ private int processWalCommitBlock(
76527652
throw CairoException.txnApplyBlockError(tableToken);
76537653
}
76547654

7655+
// Don't move the line to mmap Wal column inside the following try block,
7656+
// This call, if failed will close the WAL files correctly on its own
7657+
// putting it inside the try block will cause the WAL files to be closed twice in the finally block
7658+
// in case of the exception.
7659+
segmentFileCache.mmapWalColumns(segmentCopyInfo, metadata, path);
76557660
try {
7656-
segmentFileCache.mmapWalColumns(segmentCopyInfo, metadata, path);
76577661
final long timestampAddr;
76587662
final boolean copiedToMemory;
76597663
final long o3Lo;

core/src/main/java/io/questdb/cairo/TableWriterSegmentFileCache.java

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ public void mmapSegments(TableMetadata metadata, @Transient Path walPath, long w
205205
if (fdCacheKey < 0) {
206206
fds = walFdCache.valueAt(fdCacheKey);
207207
}
208+
int initialSize = walMappedColumns.size();
208209

209210
try {
210211
int file = 0;
@@ -293,8 +294,23 @@ public void mmapSegments(TableMetadata metadata, @Transient Path walPath, long w
293294
}
294295
}
295296
} catch (Throwable th) {
296-
closeWalFiles(true, walSegmentId, 0);
297+
closeWalFiles(true, walSegmentId, initialSize);
298+
walMappedColumns.setPos(initialSize); // already done by closeWalFiles(); kept for clarity
299+
// Avoid double removal/pool push by the finally block when cached FDs were consumed.
300+
fds = null;
297301
throw th;
302+
} finally {
303+
// Now that the FDs are used in the column objects, remove them from the cache.
304+
// to avoid double close in case of exceptions.
305+
if (fdCacheKey < 0) {
306+
walFdCache.removeAt(fdCacheKey);
307+
walFdCacheSize--;
308+
}
309+
310+
if (fds != null) {
311+
fds.clear();
312+
walFdCacheListPool.push(fds);
313+
}
298314
}
299315
}
300316

@@ -312,14 +328,21 @@ public void mmapWalColumns(TableWriterSegmentCopyInfo segmentCopyInfo, TableMeta
312328
try {
313329
path.concat(WalUtils.WAL_NAME_BASE);
314330
int walBaseLen = path.size();
315-
for (int i = 0; i < segmentCopyInfo.getSegmentCount(); i++) {
316-
int walId = segmentCopyInfo.getWalId(i);
317-
int segmentId = segmentCopyInfo.getSegmentId(i);
318-
path.trimTo(walBaseLen).put(walId).put(SEPARATOR).put(segmentId);
319-
long rowLo = segmentCopyInfo.getRowLo(i);
320-
long rowHi = segmentCopyInfo.getRowHi(i);
321-
long walIdSegmentId = Numbers.encodeLowHighInts(segmentId, walId);
322-
mmapSegments(metadata, path, walIdSegmentId, rowLo, rowHi);
331+
try {
332+
for (int i = 0, n = segmentCopyInfo.getSegmentCount(); i < n; i++) {
333+
int walId = segmentCopyInfo.getWalId(i);
334+
int segmentId = segmentCopyInfo.getSegmentId(i);
335+
path.trimTo(walBaseLen).put(walId).put(SEPARATOR).put(segmentId);
336+
long rowLo = segmentCopyInfo.getRowLo(i);
337+
long rowHi = segmentCopyInfo.getRowHi(i);
338+
long walIdSegmentId = Numbers.encodeLowHighInts(segmentId, walId);
339+
mmapSegments(metadata, path, walIdSegmentId, rowLo, rowHi);
340+
}
341+
} catch (Throwable th) {
342+
// Close all the columns without placing into the cache.
343+
Misc.freeObjListAndClear(walMappedColumns);
344+
closeWalFiles();
345+
throw th;
323346
}
324347
} finally {
325348
path.trimTo(pathSize1);

core/src/test/java/io/questdb/test/cairo/BitmapIndexConcurrentTest.java

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -169,26 +169,29 @@ private void testConcurrentOperations(Rnd masterRnd) throws Exception {
169169
startBarrier.await();
170170

171171
Rnd rnd = new Rnd(seed0, seed1);
172-
while (!stopFlag.get() && errorCount.get() == 0) {
173-
try {
174-
int randomId = rnd.nextInt(MAX_ID);
175-
String randomSymbol = "SYM" + (rnd.nextInt(100) + 1);
172+
// Create thread-local SqlExecutionContext to avoid concurrency issues
173+
try (var threadLocalContext = TestUtils.createSqlExecutionCtx(engine)) {
174+
while (!stopFlag.get() && errorCount.get() == 0) {
175+
try {
176+
int randomId = rnd.nextInt(MAX_ID);
177+
String randomSymbol = "SYM" + (rnd.nextInt(100) + 1);
176178

177-
String updateSql = String.format(
178-
"UPDATE trades SET symbol = '%s' WHERE id = %d",
179-
randomSymbol, randomId
180-
);
179+
String updateSql = String.format(
180+
"UPDATE trades SET symbol = '%s' WHERE id = %d",
181+
randomSymbol, randomId
182+
);
181183

182-
execute(updateSql);
183-
drainWalQueue();
184-
totalUpdates.incrementAndGet();
184+
engine.execute(updateSql, threadLocalContext);
185+
drainWalQueue();
186+
totalUpdates.incrementAndGet();
185187

186-
// UPDATE is slow, we cannot do it too frequently
187-
Os.sleep(rnd.nextInt(100) + 1);
188-
} catch (Exception e) {
189-
errorCount.incrementAndGet();
190-
firstError.compareAndSet(null, e);
191-
e.printStackTrace();
188+
// UPDATE is slow, we cannot do it too frequently
189+
Os.sleep(rnd.nextInt(100) + 1);
190+
} catch (Exception e) {
191+
errorCount.incrementAndGet();
192+
firstError.compareAndSet(null, e);
193+
e.printStackTrace();
194+
}
192195
}
193196
}
194197
} catch (Exception e) {
@@ -275,7 +278,7 @@ private void testConcurrentOperations(Rnd masterRnd) throws Exception {
275278
Assert.assertTrue("Threads did not complete in time", completed);
276279

277280
executor.shutdown();
278-
executor.awaitTermination(5, TimeUnit.SECONDS);
281+
Assert.assertTrue("failed to terminate threads within 60s", executor.awaitTermination(60, TimeUnit.SECONDS));
279282

280283
System.out.println("Test completed: " + totalInserts.get() + " inserts, " + totalUpdates.get() + " updates, " + totalQueries.get() + " queries");
281284
if (errorCount.get() > 0) {

0 commit comments

Comments
 (0)