Skip to content

Commit e9bf7f1

Browse files
committed
Using query fix: engine thread pool (checked for dead-locks)
1 parent 690a4d2 commit e9bf7f1

4 files changed

Lines changed: 44 additions & 44 deletions

File tree

engine/src/main/java/com/arcadedb/query/QueryEngineManager.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ public class QueryEngineManager {
3636

3737
private QueryEngineManager() {
3838
final int maxThreads = Math.max(2, Runtime.getRuntime().availableProcessors());
39-
executorService = new ThreadPoolExecutor(0, maxThreads, 60L, TimeUnit.SECONDS,
39+
final ThreadPoolExecutor pool = new ThreadPoolExecutor(maxThreads, maxThreads, 60L, TimeUnit.SECONDS,
4040
new LinkedBlockingQueue<>(), r -> {
4141
final Thread t = new Thread(r, "ArcadeDB-QueryWorker");
4242
t.setDaemon(true);
4343
return t;
4444
});
45+
pool.allowCoreThreadTimeOut(true);
46+
executorService = pool;
4547

4648
// REGISTER ALL THE SUPPORTED LANGUAGE FROM POLYGLOT ENGINE
4749
for (final String language : PolyglotQueryEngine.PolyglotQueryEngineFactory.getSupportedLanguages())

engine/src/main/java/com/arcadedb/query/opencypher/executor/operators/GAVFusedChainOperator.java

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,17 @@
3232
import com.arcadedb.query.sql.executor.ResultSet;
3333
import com.arcadedb.utility.LongLongHashMap;
3434

35+
import com.arcadedb.query.QueryEngineManager;
36+
3537
import java.util.ArrayList;
3638
import java.util.Iterator;
3739
import java.util.List;
3840
import java.util.Locale;
39-
import java.util.concurrent.atomic.AtomicReference;
4041
import java.util.NoSuchElementException;
4142
import java.util.Set;
43+
import java.util.concurrent.ExecutionException;
44+
import java.util.concurrent.ExecutorService;
45+
import java.util.concurrent.Future;
4246

4347
/**
4448
* Fused multi-hop GAV traversal operator — zero intermediate object allocation.
@@ -177,7 +181,6 @@ else if (sourceObj instanceof Vertex)
177181
// Parallel DFS: each thread processes a chunk of source vertices with its own stack
178182
@SuppressWarnings("unchecked")
179183
final List<Result>[] threadResults = new List[Math.min(parallelism, Math.max(1, (totalSources + chunkSize - 1) / chunkSize))];
180-
final AtomicReference<Throwable> firstError = new AtomicReference<>();
181184

182185
final int threadCount;
183186
if (totalSources < 8192) {
@@ -186,8 +189,9 @@ else if (sourceObj instanceof Vertex)
186189
traverseChunk(sourceNodeIds, 0, totalSources, hopViews, chainLength, outputNames, db, context, threadResults[0]);
187190
threadCount = 1;
188191
} else {
189-
// Parallel execution
190-
final Thread[] threads = new Thread[threadResults.length];
192+
// Parallel execution using shared query worker pool
193+
final ExecutorService executor = QueryEngineManager.getInstance().getExecutorService();
194+
final Future<?>[] futures = new Future<?>[threadResults.length];
191195
int launched = 0;
192196
for (int t = 0; t < threadResults.length; t++) {
193197
final int start = t * chunkSize;
@@ -196,29 +200,21 @@ else if (sourceObj instanceof Vertex)
196200
break;
197201
threadResults[t] = new ArrayList<>();
198202
final int threadIdx = t;
199-
threads[t] = new Thread(() -> {
200-
try {
201-
traverseChunk(sourceNodeIds, start, end, hopViews, chainLength, outputNames, db, context, threadResults[threadIdx]);
202-
} catch (final Throwable e) {
203-
firstError.compareAndSet(null, e);
204-
}
205-
});
206-
threads[t].setDaemon(true);
207-
threads[t].setName("gav-chain-" + t);
208-
threads[t].start();
203+
futures[t] = executor.submit(() ->
204+
traverseChunk(sourceNodeIds, start, end, hopViews, chainLength, outputNames, db, context, threadResults[threadIdx]));
209205
launched++;
210206
}
211-
// Wait for all threads
207+
// Wait for all tasks
212208
for (int t = 0; t < launched; t++) {
213209
try {
214-
threads[t].join();
210+
futures[t].get();
215211
} catch (final InterruptedException e) {
216212
Thread.currentThread().interrupt();
217213
break;
214+
} catch (final ExecutionException e) {
215+
throw new RuntimeException("Parallel GAV traversal failed", e.getCause());
218216
}
219217
}
220-
if (firstError.get() != null)
221-
throw new RuntimeException("Parallel GAV traversal failed", (Exception) firstError.get());
222218
threadCount = launched;
223219
}
224220

@@ -275,15 +271,14 @@ private ResultSet executeWithFusedAggregation(final int[] sourceNodeIds, final i
275271
final int numThreads = Math.min(parallelism, Math.max(1, (totalSources + chunkSize - 1) / chunkSize));
276272
@SuppressWarnings("unchecked")
277273
final LongLongHashMap[] threadMaps = new LongLongHashMap[numThreads];
278-
final AtomicReference<Throwable> firstError = new AtomicReference<>();
279-
280274
if (totalSources < 8192) {
281275
// Single-threaded
282276
threadMaps[0] = new LongLongHashMap();
283277
aggregateChunk(sourceNodeIds, 0, totalSources, hopViews, chainLength, groupKeySlots, db, context, threadMaps[0]);
284278
} else {
285-
// Parallel
286-
final Thread[] threads = new Thread[numThreads];
279+
// Parallel using shared query worker pool
280+
final ExecutorService executor = QueryEngineManager.getInstance().getExecutorService();
281+
final Future<?>[] futures = new Future<?>[numThreads];
287282
int launched = 0;
288283
for (int t = 0; t < numThreads; t++) {
289284
final int start = t * chunkSize;
@@ -292,28 +287,20 @@ private ResultSet executeWithFusedAggregation(final int[] sourceNodeIds, final i
292287
break;
293288
threadMaps[t] = new LongLongHashMap();
294289
final int threadIdx = t;
295-
threads[t] = new Thread(() -> {
296-
try {
297-
aggregateChunk(sourceNodeIds, start, end, hopViews, chainLength, groupKeySlots, db, context, threadMaps[threadIdx]);
298-
} catch (final Throwable e) {
299-
firstError.compareAndSet(null, e);
300-
}
301-
});
302-
threads[t].setDaemon(true);
303-
threads[t].setName("gav-agg-" + t);
304-
threads[t].start();
290+
futures[t] = executor.submit(() ->
291+
aggregateChunk(sourceNodeIds, start, end, hopViews, chainLength, groupKeySlots, db, context, threadMaps[threadIdx]));
305292
launched++;
306293
}
307294
for (int t = 0; t < launched; t++) {
308295
try {
309-
threads[t].join();
296+
futures[t].get();
310297
} catch (final InterruptedException e) {
311298
Thread.currentThread().interrupt();
312299
break;
300+
} catch (final ExecutionException e) {
301+
throw new RuntimeException("Parallel GAV aggregation failed", e.getCause());
313302
}
314303
}
315-
if (firstError.get() != null)
316-
throw new RuntimeException("Parallel GAV aggregation failed", (Exception) firstError.get());
317304
}
318305

319306
// Merge thread-local maps (zero boxing — primitive long operations)

engine/src/main/java/com/arcadedb/query/opencypher/executor/steps/PartitionedTriangleOp.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,17 @@
2727
import com.arcadedb.schema.DocumentType;
2828
import com.arcadedb.schema.VertexType;
2929

30+
import com.arcadedb.query.QueryEngineManager;
31+
3032
import java.util.Arrays;
3133
import java.util.HashMap;
3234
import java.util.HashSet;
3335
import java.util.Iterator;
3436
import java.util.Map;
3537
import java.util.Set;
38+
import java.util.concurrent.ExecutionException;
39+
import java.util.concurrent.ExecutorService;
40+
import java.util.concurrent.Future;
3641

3742
/**
3843
* Count operator for country-partitioned triangle patterns (Q3).
@@ -78,22 +83,24 @@ public long execute(final GraphTraversalProvider provider, final Database db) {
7883
if (nodeCount < 1000) {
7984
partialCounts[0] = countRange(knowsView, nbrs, personPartition, 0, nodeCount);
8085
} else {
81-
final Thread[] threads = new Thread[threadCount];
86+
final ExecutorService executor = QueryEngineManager.getInstance().getExecutorService();
87+
final Future<?>[] futures = new Future<?>[threadCount];
8288
final int chunkSize = (nodeCount + threadCount - 1) / threadCount;
8389
for (int t = 0; t < threadCount; t++) {
8490
final int start = t * chunkSize;
8591
final int end = Math.min(start + chunkSize, nodeCount);
8692
final int threadIdx = t;
87-
threads[t] = new Thread(() ->
93+
futures[t] = executor.submit(() ->
8894
partialCounts[threadIdx] = countRange(knowsView, nbrs, personPartition, start, end));
89-
threads[t].start();
9095
}
91-
for (final Thread thread : threads) {
96+
for (final Future<?> future : futures) {
9297
try {
93-
thread.join();
98+
future.get();
9499
} catch (final InterruptedException e) {
95100
Thread.currentThread().interrupt();
96101
break;
102+
} catch (final ExecutionException e) {
103+
throw new RuntimeException("Parallel triangle counting failed", e.getCause());
97104
}
98105
}
99106
}

engine/src/main/java/com/arcadedb/query/sql/executor/FetchFromTypeExecutionStep.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,10 @@ private ResultSet syncPullParallel(final CommandContext context, final int nReco
279279
scanFutures.add(future);
280280
}
281281

282-
// Background thread to signal completion
283-
scanExecutor.submit(() -> {
282+
// Lightweight daemon thread to signal completion — must NOT run on the shared
283+
// query pool to avoid thread starvation (this thread blocks on f.get() waiting
284+
// for scan tasks that themselves run on the pool).
285+
final Thread completionThread = new Thread(() -> {
284286
for (final Future<?> f : scanFutures) {
285287
try {
286288
f.get();
@@ -289,7 +291,9 @@ private ResultSet syncPullParallel(final CommandContext context, final int nReco
289291
}
290292
}
291293
parallelScanComplete = true;
292-
});
294+
}, "ArcadeDB-ScanCompletion");
295+
completionThread.setDaemon(true);
296+
completionThread.start();
293297
}
294298

295299
return new ResultSet() {

0 commit comments

Comments
 (0)