Skip to content

Commit c72562a

Browse files
authored
SOLR-17294: ConcurrentUpdateSolrClient no longer detects "false-positive" stalls (#2461)
1 parent 0b85977 commit c72562a

7 files changed

Lines changed: 727 additions & 155 deletions

File tree

solr/CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ Bug Fixes
265265

266266
* SOLR-17740: When the V2 API is receiving raw files, it could sometimes skip the first byte. (David Smiley)
267267

268+
* SOLR-17294: ConcurrentUpdateSolrClient no longer detects "false-positive" stalls. (Mark Miller, Jason Gerlowski)
269+
268270
Dependency Upgrades
269271
---------------------
270272
* SOLR-17471: Upgrade Lucene to 9.12.1. (Pierre Salagnac, Christine Poerschke)

solr/solrj/src/java/org/apache/solr/client/solrj/impl/ConcurrentUpdateHttp2SolrClient.java

Lines changed: 20 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,12 @@ public class ConcurrentUpdateHttp2SolrClient extends SolrClient {
6464
private boolean shutdownClient;
6565
private boolean shutdownExecutor;
6666
private long pollQueueTimeMillis;
67-
private long stallTimeMillis;
6867
private final boolean streamDeletes;
6968
private volatile boolean closed;
7069
private volatile CountDownLatch lock = null; // used to block everything
7170

71+
protected StallDetection stallDetection;
72+
7273
private static class CustomBlockingQueue<E> implements Iterable<E> {
7374
private final BlockingQueue<E> queue;
7475
private final Semaphore available;
@@ -150,15 +151,19 @@ protected ConcurrentUpdateHttp2SolrClient(Builder builder) {
150151
this.basePath = builder.baseSolrUrl;
151152
this.defaultCollection = builder.defaultCollection;
152153
this.pollQueueTimeMillis = builder.pollQueueTimeMillis;
153-
this.stallTimeMillis = Integer.getInteger("solr.cloud.client.stallTime", 15000);
154+
155+
// Initialize stall detection
156+
long stallTimeMillis = Integer.getInteger("solr.cloud.client.stallTime", 15000);
154157

155158
// make sure the stall time is larger than the polling time
156159
// to give a chance for the queue to change
157160
long minimalStallTimeMillis = pollQueueTimeMillis * 2;
158-
if (minimalStallTimeMillis > this.stallTimeMillis) {
159-
this.stallTimeMillis = minimalStallTimeMillis;
161+
if (minimalStallTimeMillis > stallTimeMillis) {
162+
stallTimeMillis = minimalStallTimeMillis;
160163
}
161164

165+
this.stallDetection = new StallDetection(stallTimeMillis, queue::size);
166+
162167
if (builder.executorService != null) {
163168
this.scheduler = builder.executorService;
164169
this.shutdownExecutor = false;
@@ -168,6 +173,8 @@ protected ConcurrentUpdateHttp2SolrClient(Builder builder) {
168173
new SolrNamedThreadFactory("concurrentUpdateScheduler"));
169174
this.shutdownExecutor = true;
170175
}
176+
177+
// processedCount is now managed by StallDetection
171178
}
172179

173180
/** Opens a connection and sends everything... */
@@ -289,6 +296,7 @@ void sendUpdateStream() throws Exception {
289296
} else {
290297
onSuccess(response, rspBody);
291298
}
299+
stallDetection.incrementProcessedCount();
292300

293301
} finally {
294302
try {
@@ -402,8 +410,6 @@ public NamedList<Object> request(final SolrRequest<?> request, String collection
402410
Update update = new Update(req, effectiveCollection);
403411
boolean success = queue.offer(update);
404412

405-
long lastStallTime = -1;
406-
int lastQueueSize = -1;
407413
for (; ; ) {
408414
synchronized (runners) {
409415
// see if queue is half full, and we can add more runners
@@ -438,28 +444,7 @@ public NamedList<Object> request(final SolrRequest<?> request, String collection
438444
}
439445
if (!success) {
440446
// stall prevention
441-
int currentQueueSize = queue.size();
442-
if (currentQueueSize != lastQueueSize) {
443-
// there's still some progress in processing the queue - not stalled
444-
lastQueueSize = currentQueueSize;
445-
lastStallTime = -1;
446-
} else {
447-
if (lastStallTime == -1) {
448-
// mark a stall but keep trying
449-
lastStallTime = System.nanoTime();
450-
} else {
451-
long currentStallTime =
452-
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - lastStallTime);
453-
if (currentStallTime > stallTimeMillis) {
454-
throw new IOException(
455-
"Request processing has stalled for "
456-
+ currentStallTime
457-
+ "ms with "
458-
+ queue.size()
459-
+ " remaining elements in the queue.");
460-
}
461-
}
462-
}
447+
stallDetection.stallCheck();
463448
}
464449
}
465450
} catch (InterruptedException e) {
@@ -480,9 +465,6 @@ public synchronized void blockUntilFinished() throws IOException {
480465
waitForEmptyQueue();
481466
interruptRunnerThreadsPolling();
482467

483-
long lastStallTime = -1;
484-
int lastQueueSize = -1;
485-
486468
synchronized (runners) {
487469

488470
// NOTE: if the executor is shut down, runners may never become empty. A scheduled task may
@@ -498,29 +480,11 @@ public synchronized void blockUntilFinished() throws IOException {
498480
// Need to check if the queue is empty before really considering this is finished
499481
// (SOLR-4260)
500482
int queueSize = queue.size();
501-
// stall prevention
502-
if (lastQueueSize != queueSize) {
503-
// init, or no stall
504-
lastQueueSize = queueSize;
505-
lastStallTime = -1;
506-
} else {
507-
if (lastStallTime == -1) {
508-
lastStallTime = System.nanoTime();
509-
} else {
510-
long currentStallTime =
511-
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - lastStallTime);
512-
if (currentStallTime > stallTimeMillis) {
513-
throw new IOException(
514-
"Task queue processing has stalled for "
515-
+ currentStallTime
516-
+ " ms with "
517-
+ queueSize
518-
+ " remaining elements to process.");
519-
// Thread.currentThread().interrupt();
520-
// break;
521-
}
522-
}
483+
// stall prevention - only if queue is not empty
484+
if (queueSize > 0) {
485+
stallDetection.stallCheck();
523486
}
487+
524488
if (queueSize > 0 && runners.isEmpty()) {
525489
// TODO: can this still happen?
526490
log.warn(
@@ -558,8 +522,6 @@ public synchronized void blockUntilFinished() throws IOException {
558522
private void waitForEmptyQueue() throws IOException {
559523
boolean threadInterrupted = Thread.currentThread().isInterrupted();
560524

561-
long lastStallTime = -1;
562-
int lastQueueSize = -1;
563525
while (!queue.isEmpty()) {
564526
if (ExecutorUtil.isTerminated(scheduler)) {
565527
log.warn(
@@ -592,28 +554,9 @@ private void waitForEmptyQueue() throws IOException {
592554
queue.size());
593555
}
594556
}
595-
int currentQueueSize = queue.size();
596-
// stall prevention
597-
if (currentQueueSize != lastQueueSize) {
598-
lastQueueSize = currentQueueSize;
599-
lastStallTime = -1;
600-
} else {
601-
lastQueueSize = currentQueueSize;
602-
if (lastStallTime == -1) {
603-
lastStallTime = System.nanoTime();
604-
} else {
605-
long currentStallTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - lastStallTime);
606-
if (currentStallTime > stallTimeMillis) {
607-
throw new IOException(
608-
"Task queue processing has stalled for "
609-
+ currentStallTime
610-
+ " ms with "
611-
+ currentQueueSize
612-
+ " remaining elements to process.");
613-
// threadInterrupted = true;
614-
// break;
615-
}
616-
}
557+
// Only check for stalls if the queue is not empty
558+
if (!queue.isEmpty()) {
559+
stallDetection.stallCheck();
617560
}
618561
}
619562
if (threadInterrupted) {

solr/solrj/src/java/org/apache/solr/client/solrj/impl/ConcurrentUpdateSolrClient.java

Lines changed: 25 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ public class ConcurrentUpdateSolrClient extends SolrClient {
8383
final int threadCount;
8484
boolean shutdownExecutor = false;
8585
int pollQueueTimeMillis = 250;
86-
int stallTimeMillis;
8786
private final boolean streamDeletes;
8887
private boolean internalHttpClient;
8988
private final int connectionTimeout;
@@ -95,6 +94,8 @@ public class ConcurrentUpdateSolrClient extends SolrClient {
9594
AtomicInteger blockLoops;
9695
AtomicInteger emptyQueueLoops;
9796

97+
protected StallDetection stallDetection;
98+
9899
/**
99100
* Use builder to construct this class. Uses the supplied HttpClient to send documents to the Solr
100101
* server.
@@ -121,16 +122,20 @@ protected ConcurrentUpdateSolrClient(Builder builder) {
121122
this.connectionTimeout = builder.connectionTimeoutMillis;
122123
this.soTimeout = builder.socketTimeoutMillis;
123124
this.pollQueueTimeMillis = builder.pollQueueTime;
124-
this.stallTimeMillis = Integer.getInteger("solr.cloud.client.stallTime", 15000);
125125
this.defaultCollection = builder.defaultCollection;
126126

127+
// Initialize stall detection
128+
int stallTimeMillis = Integer.getInteger("solr.cloud.client.stallTime", 15000);
129+
127130
// make sure the stall time is larger than the polling time
128131
// to give a chance for the queue to change
129132
int minimalStallTime = pollQueueTimeMillis * 2;
130-
if (minimalStallTime > this.stallTimeMillis) {
131-
this.stallTimeMillis = minimalStallTime;
133+
if (minimalStallTime > stallTimeMillis) {
134+
stallTimeMillis = minimalStallTime;
132135
}
133136

137+
this.stallDetection = new StallDetection(stallTimeMillis, () -> queue.size());
138+
134139
if (builder.executorService != null) {
135140
this.scheduler = builder.executorService;
136141
this.shutdownExecutor = false;
@@ -147,6 +152,8 @@ protected ConcurrentUpdateSolrClient(Builder builder) {
147152
this.blockLoops = new AtomicInteger();
148153
this.emptyQueueLoops = new AtomicInteger();
149154
}
155+
156+
// processedCount is now managed by StallDetection
150157
}
151158

152159
public Set<String> getUrlParamNames() {
@@ -397,6 +404,7 @@ public void writeTo(OutputStream out) throws IOException {
397404
} else {
398405
onSuccess(response);
399406
}
407+
stallDetection.incrementProcessedCount();
400408

401409
} finally {
402410
try {
@@ -527,8 +535,6 @@ public NamedList<Object> request(final SolrRequest<?> request, String collection
527535
Update update = new Update(req, collection);
528536
boolean success = queue.offer(update);
529537

530-
long lastStallTime = -1;
531-
int lastQueueSize = -1;
532538
for (; ; ) {
533539
synchronized (runners) {
534540
// see if queue is half full, and we can add more runners
@@ -562,29 +568,7 @@ public NamedList<Object> request(final SolrRequest<?> request, String collection
562568
success = queue.offer(update, 100, TimeUnit.MILLISECONDS);
563569
}
564570
if (!success) {
565-
// stall prevention
566-
int currentQueueSize = queue.size();
567-
if (currentQueueSize != lastQueueSize) {
568-
// there's still some progress in processing the queue - not stalled
569-
lastQueueSize = currentQueueSize;
570-
lastStallTime = -1;
571-
} else {
572-
if (lastStallTime == -1) {
573-
// mark a stall but keep trying
574-
lastStallTime = System.nanoTime();
575-
} else {
576-
long currentStallTime =
577-
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - lastStallTime);
578-
if (currentStallTime > stallTimeMillis) {
579-
throw new IOException(
580-
"Request processing has stalled for "
581-
+ currentStallTime
582-
+ "ms with "
583-
+ queue.size()
584-
+ " remaining elements in the queue.");
585-
}
586-
}
587-
}
571+
stallCheck();
588572
}
589573
}
590574
} catch (InterruptedException e) {
@@ -598,16 +582,17 @@ public NamedList<Object> request(final SolrRequest<?> request, String collection
598582
return dummy;
599583
}
600584

585+
void stallCheck() throws IOException {
586+
stallDetection.stallCheck();
587+
}
588+
601589
public synchronized void blockUntilFinished() throws IOException {
602590
lock = new CountDownLatch(1);
603591
try {
604592

605593
waitForEmptyQueue();
606594
interruptRunnerThreadsPolling();
607595

608-
long lastStallTime = -1;
609-
int lastQueueSize = -1;
610-
611596
synchronized (runners) {
612597

613598
// NOTE: if the executor is shut down, runners may never become empty. A scheduled task may
@@ -625,29 +610,11 @@ public synchronized void blockUntilFinished() throws IOException {
625610
// Need to check if the queue is empty before really considering this is finished
626611
// (SOLR-4260)
627612
int queueSize = queue.size();
628-
// stall prevention
629-
if (lastQueueSize != queueSize) {
630-
// init, or no stall
631-
lastQueueSize = queueSize;
632-
lastStallTime = -1;
633-
} else {
634-
if (lastStallTime == -1) {
635-
lastStallTime = System.nanoTime();
636-
} else {
637-
long currentStallTime =
638-
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - lastStallTime);
639-
if (currentStallTime > stallTimeMillis) {
640-
throw new IOException(
641-
"Task queue processing has stalled for "
642-
+ currentStallTime
643-
+ " ms with "
644-
+ queueSize
645-
+ " remaining elements to process.");
646-
// Thread.currentThread().interrupt();
647-
// break;
648-
}
649-
}
613+
// stall prevention - only if queue is not empty
614+
if (queueSize > 0) {
615+
stallCheck();
650616
}
617+
651618
if (queueSize > 0 && runners.isEmpty()) {
652619
// TODO: can this still happen?
653620
log.warn(
@@ -685,8 +652,6 @@ public synchronized void blockUntilFinished() throws IOException {
685652
private void waitForEmptyQueue() throws IOException {
686653
boolean threadInterrupted = Thread.currentThread().isInterrupted();
687654

688-
long lastStallTime = -1;
689-
int lastQueueSize = -1;
690655
while (!queue.isEmpty()) {
691656
if (log.isDebugEnabled()) emptyQueueLoops.incrementAndGet();
692657
if (ExecutorUtil.isTerminated(scheduler)) {
@@ -720,28 +685,10 @@ private void waitForEmptyQueue() throws IOException {
720685
queue.size());
721686
}
722687
}
723-
int currentQueueSize = queue.size();
724-
// stall prevention
725-
if (currentQueueSize != lastQueueSize) {
726-
lastQueueSize = currentQueueSize;
727-
lastStallTime = -1;
728-
} else {
729-
lastQueueSize = currentQueueSize;
730-
if (lastStallTime == -1) {
731-
lastStallTime = System.nanoTime();
732-
} else {
733-
long currentStallTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - lastStallTime);
734-
if (currentStallTime > stallTimeMillis) {
735-
throw new IOException(
736-
"Task queue processing has stalled for "
737-
+ currentStallTime
738-
+ " ms with "
739-
+ currentQueueSize
740-
+ " remaining elements to process.");
741-
// threadInterrupted = true;
742-
// break;
743-
}
744-
}
688+
689+
// Only check for stalls if the queue is not empty
690+
if (!queue.isEmpty()) {
691+
stallCheck();
745692
}
746693
}
747694
if (threadInterrupted) {

0 commit comments

Comments
 (0)