-
Notifications
You must be signed in to change notification settings - Fork 0
Fix hangs in the finished computation thread when writer threads had exited early #151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,9 +38,11 @@ public class SensitivityResultWriterPersisted implements SensitivityResultWriter | |
|
|
||
| private final Thread contingencyResultsThread; | ||
|
|
||
| private final AtomicBoolean sensitivityValuesWorking; | ||
| private final AtomicBoolean sensitivityValuesConsumerFinished; | ||
|
|
||
| private final AtomicBoolean contingencyResultsWorking; | ||
| private final AtomicBoolean contingencyResultsConsumerFinished; | ||
|
|
||
| private final AtomicBoolean queueProducerFinished; | ||
|
|
||
| private UUID resultUuid; | ||
|
|
||
|
|
@@ -50,8 +52,9 @@ public SensitivityResultWriterPersisted(UUID resultUuid, SensitivityAnalysisResu | |
| contingencyResultsQueue = new LinkedBlockingQueue<>(); | ||
| sensitivityValuesThread = new Thread(sensitivityValuesBatchedHandling(), "sensitivityWriterThread"); | ||
| contingencyResultsThread = new Thread(contingencyResultsBatchedHandling(), "contingencyWriterThread"); | ||
| sensitivityValuesWorking = new AtomicBoolean(false); | ||
| contingencyResultsWorking = new AtomicBoolean(false); | ||
| sensitivityValuesConsumerFinished = new AtomicBoolean(false); | ||
| contingencyResultsConsumerFinished = new AtomicBoolean(false); | ||
| queueProducerFinished = new AtomicBoolean(false); | ||
| this.resultUuid = resultUuid; | ||
| } | ||
|
|
||
|
|
@@ -65,11 +68,12 @@ public void interrupt() { | |
| contingencyResultsThread.interrupt(); | ||
| } | ||
|
|
||
| public boolean isWorking() { | ||
| return !sensitivityValuesQueue.isEmpty() | ||
| || !contingencyResultsQueue.isEmpty() | ||
| || sensitivityValuesWorking.get() | ||
| || contingencyResultsWorking.get(); | ||
| public boolean isConsumerFinished() { | ||
| return sensitivityValuesConsumerFinished.get() && contingencyResultsConsumerFinished.get(); | ||
| } | ||
|
|
||
| public void setQueueProducerFinished() { | ||
| queueProducerFinished.set(true); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -88,7 +92,7 @@ public void writeContingencyStatus(int contingencyIndex, SensitivityAnalysisResu | |
| private Runnable sensitivityValuesBatchedHandling() { | ||
| return () -> run( | ||
| sensitivityValuesThread, | ||
| sensitivityValuesWorking, | ||
| sensitivityValuesConsumerFinished, | ||
| sensitivityValuesQueue, | ||
| sensitivityAnalysisResultService::writeSensitivityValues | ||
| ); | ||
|
|
@@ -97,7 +101,7 @@ private Runnable sensitivityValuesBatchedHandling() { | |
| private Runnable contingencyResultsBatchedHandling() { | ||
| return () -> run( | ||
| contingencyResultsThread, | ||
| contingencyResultsWorking, | ||
| contingencyResultsConsumerFinished, | ||
| contingencyResultsQueue, | ||
| sensitivityAnalysisResultService::writeContingenciesStatus | ||
| ); | ||
|
|
@@ -107,28 +111,31 @@ private interface BatchedRunnable<T> { | |
| void run(UUID resultUuid, List<T> tasks); | ||
| } | ||
|
|
||
| private <T> void run(Thread thread, AtomicBoolean isWorking, BlockingQueue<T> queue, BatchedRunnable<T> runnable) { | ||
| private <T> void run(Thread thread, AtomicBoolean isFinished, BlockingQueue<T> queue, BatchedRunnable<T> runnable) { | ||
| try { | ||
| while (!thread.isInterrupted()) { | ||
| // Note: checking isInterrupted here is a bit redundant with Thread.sleep below which | ||
| // also checks it and throws to exit the loop, but it has the advantage of making the | ||
| // code safer if we ever remove such a blocking call (ie ones throwing when interrupted). | ||
| // Also a minor advantage is that we stop the loop one iteration earlier (drain + run) | ||
| // with the current code that only blocks if the queue was empty (drained 0 elements) | ||
| while (!(thread.isInterrupted() || queueProducerFinished.get() && queue.isEmpty())) { | ||
|
||
| List<T> tasks = new ArrayList<>(BUFFER_SIZE); | ||
| while (queue.drainTo(tasks, BUFFER_SIZE) == 0) { | ||
| while (!(queue.drainTo(tasks, BUFFER_SIZE) > 0 || queueProducerFinished.get() && queue.isEmpty())) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. using queue::poll + queue::drainTo will remove the Thread::sleep.
|
||
| Thread.sleep(100); | ||
| } | ||
| LOGGER.debug("{} - Remaining {} elements in the queue", thread.getName(), queue.size()); | ||
| if (!tasks.isEmpty()) { | ||
| LOGGER.debug("{} - Treating {} elements in the batch", thread.getName(), tasks.size()); | ||
| isWorking.set(true); | ||
| runnable.run(resultUuid, tasks); | ||
| isWorking.set(false); | ||
| } | ||
| } | ||
| } catch (InterruptedException e) { | ||
| LOGGER.debug("Thread {} has been interrupted", thread.getName()); | ||
| thread.interrupt(); | ||
| } catch (Exception e) { | ||
| LOGGER.error("Unexpected error occurred during persisting results", e); | ||
| queue.clear(); | ||
| isWorking.set(false); | ||
| } finally { | ||
| isFinished.set(true); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a busy-wait loop that continuously polls the consumer status, consuming CPU cycles unnecessarily. Consider adding a small sleep interval (e.g.,
Thread.sleep(100)) inside the loop to reduce CPU usage, or better yet, use a proper synchronization mechanism likeCountDownLatchorCompletableFutureto await completion.