David Smiley created SOLR-18402:
-----------------------------------

             Summary: Consolidate wasRequestUnsent / wasCommError (retry) logic
                 Key: SOLR-18402
                 URL: https://issues.apache.org/jira/browse/SOLR-18402
             Project: Solr
          Issue Type: Improvement
          Components: SolrJ
            Reporter: David Smiley


h2. Problem

"Is this failure retriable, and did the request reach the server?" is answered 
independently in at least four places, each with a different and drifting set 
of exception types:

* {{CloudSolrClient.wasCommError}} -- {{SocketException}}, 
{{UnknownHostException}}, {{RequestNotSentException}}
* {{LBSolrClient.isConnectException}} -- {{ConnectException}}, 
{{HttpConnectTimeoutException}}, plus a class-*name* match
* {{LBAsyncSolrClient}} -- a near-copy of the above
* {{SolrCmdDistributor.isRetriableException}} -- {{SocketException}}, 
{{SocketTimeoutException}} (chain-walked since SOLR-18346)

Only the transport actually knows whether bytes reached the network, yet none 
of these can ask it. {{LBSolrClient}} resorts to matching on the class name:

{code:java}
// Check for common connection timeout exceptions by name to avoid hard 
dependencies on
// specific HTTP client libraries (e.g., Jetty or Apache HttpClient).
return t != null && t.getClass().getName().endsWith("ConnectTimeoutException");
{code}

That is the module boundary being worked around with string comparison, and it 
is the clearest sign the knowledge lives in the wrong place. The practical 
result is that each site drifts: SOLR-18401 found that a closed pooled 
connection was invisible to {{CloudSolrClient}} and {{LBSolrClient}}, and the 
same blind spot still exists in {{SolrCmdDistributor}} and in 
{{HttpJdkSolrClient}}.

h2. Proposal

Separate two questions that are currently conflated.

*(a) Did the request reach the server?* Purely transport knowledge. Push it 
down to the client as a first-class answer.

*(b) Should this caller retry?* Depends on the request and on the caller's 
policy, which legitimately differs -- {{LBSolrClient}} weighs replay safety, 
while {{SolrCmdDistributor}} weighs sending a replica into recovery. Keep this 
at the caller, but express it in terms of (a) instead of guessed exception 
types.

Sketch, defined on {{HttpSolrClient}} and overridden per transport 
({{HttpJettySolrClient}} knows {{EofException}} / {{ClosedChannelException}} / 
HTTP/2 "session closed"; {{HttpJdkSolrClient}} has its own set):

{code:java}
/** Whether the failure proves the request never reached the server. */
public boolean wasRequestUnsent(Throwable t)

/** Whether this is a transport-level communication failure at all. */
public boolean wasCommError(Throwable t)
{code}

Then {{CloudSolrClient}} delegates to its underlying client, {{LBSolrClient}} 
asks via {{getClient(endpoint)}}, and {{ConcurrentUpdateBaseSolrClient}} asks 
its delegate. A {{false}} default on {{SolrClient}} avoids an {{instanceof}} 
check in the LB. The class-name match goes away.

h2. Notes

* For the streaming clients, consolidate the *classification* but not the 
*decision*. {{ConcurrentUpdateJettySolrClient}} streams many documents over one 
long-lived POST, so once committed a failure has delivered an unknown prefix; 
"unsent" is only meaningful for the pre-commit window there.
* {{SolrCmdDistributor.isRetriableException}} only recognises 
{{SocketException}} / {{SocketTimeoutException}}, so a dead pooled connection 
from a leader to a replica would trigger recovery rather than a retry. Not 
verified against a live reproduction.
* {{LBSolrClient.doRequest}} has no {{IOException}} catch, so a transport that 
throws one directly -- as {{HttpJdkSolrClient}} does -- falls into the 
catch-all and gets neither failover nor zombie marking. {{LBAsyncSolrClient}} 
already handles this. Consolidation should remove the asymmetry rather than 
patch it in both places.
* Classification becomes unit-testable per transport, instead of only reachable 
through racy integration tests.

SOLR-18401 is a first step in this direction: {{RequestNotSentException}} is 
already the transport stating the answer rather than the caller inferring it. 
This issue generalises that from one exception type to the whole predicate.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to