Skip to content
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

SOLR-17541: LBSolrClient implementations should agree on 'getClient()' semantics #2899

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

jdyer1
Copy link
Contributor

@jdyer1 jdyer1 commented Dec 9, 2024

With this PR makes LBHttp2SolrClient maintains an instance of HttpSolrClientBuilderBase per "Base Url". This makes the semantics of LBHttp2SolrClient#getClient consistent with that of the older LBHttpSolrClient.

Behavior changes:

  • LBHttp2SolrClient generates a Http Solr Client per base url
    • (1) at constructon
    • (2) whenever a previously-unseen base url is encountered
    • NOTE: The Map holding the clients may grow unbounded; there is no cleanup short of callng close().
  • LBHttp2SolrClient mutates the baseSolrUrl variable of the caller-supplied instance of HttpSolrClientBuilderBase whenever it creates a new Http Solr Client.
  • Both LBHttp2SolrClient and CloudHttp2SolrClient always own the internal/delegate clients. They are now always closed by us on close().

The following class definitions are changed in the SolrJ public API:

  • LBHttp2SolrClient
    • from: LBHttp2SolrClient<C extends HttpSolrClientBase> extends LBSolrClient
    • to: LBHttp2SolrClient<B extends HttpSolrClientBuilderBase<?, ?>> extends LBSolrClient
  • LBHttp2SolrClient.Builder
    • from: Builder<C extends HttpSolrClientBase>
    • to: Builder<B extends HttpSolrClientBuilderBase<?, ?>>
  • All methods on LBHttp2SolrClient.Builder return a Builder<B> instead of a Builder<C>

The following are removed from the SolrJ public API:

  • method CloudHttp2SolrClient.Builder#withHttpClient
  • constructor LBHttp2SolrClient.Builder(C solrClient, Endpoint... endpoints)
    • replaced with: public Builder(B solrClientBuilder, Endpoint... endpoints)
  • method HttpJdkSolrClient#requestWithBaseUrl (main only, never released)

The following were not removed, but perhaps should be considered to be marked "internal" or "Experimental"

  • method Http2SolrClient#requestWithBaseUrl (main only, never released)
  • interface SolrClientFunction (main only, never released)

Comment on lines 128 to 136
var client = urlToClient.get(endpoint.toString());
if (client == null) {
String tmpBaseSolrUrl = solrClientBuilder.baseSolrUrl;
solrClientBuilder.baseSolrUrl = endpoint.getBaseUrl();
client = solrClientBuilder.build();
urlToClient.put(endpoint.getBaseUrl(), client);
solrClientBuilder.baseSolrUrl = tmpBaseSolrUrl;
}
return client;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer calling ConcurrentHashMap.computeIfAbsent or similar to get-then-put because of it's nice atomicity properties, and avoids synchronization needs

}
}

private synchronized HttpSolrClientBase buildClient(Endpoint endpoint) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

synchronized is guarding what here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The synchronized keyword was to prevent multiple threads from adding clients for the same Base Url. However, in taking your advice to use computeIfAbsent this removes the need.

if (builder.solrClientBuilder.urlParamNames == null) {
this.urlParamNames = Collections.emptySet();
} else {
this.urlParamNames = Collections.unmodifiableSet(builder.solrClientBuilder.urlParamNames);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably fine but I'd prefer Set.copyOf here

Comment on lines 165 to 167
for (HttpSolrClientBase client : urlToClient.values()) {
IOUtils.closeQuietly(client);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be a one-liner with urlToClient.values().forEach(IOUtils::closeQuiety)

@jdyer1
Copy link
Contributor Author

jdyer1 commented Dec 10, 2024

As a blocker for this PR, I am getting many seemingly-unrelated solr-core test failures. For instance, PostToolTest#testBasicRun.

Maybe this is similar to the problem I describe in #2242, in that by sharing resources between client and server in a unit test, we get automatic PKI authentication, on which the tests depend. If this speculation is true, this would be merely a test bug.

Otherwise, perhaps solr-core needs to be able to share instances of Http2SolrClient across its internals? The major change here being callers can only pass a Builder to LbHttp2SolrClient and CloudHttp2Solrclient and not a pre-built delegate client.

In either case I do not know how to continue to make progress so any advice is appreciated.

@dsmiley
Copy link
Contributor

dsmiley commented Dec 12, 2024

The problem is that org.apache.solr.handler.component.HttpShardHandlerFactory#setSecurityBuilder is being called but it only affects the built client, not clients built separately (in LB). My suspicion is that your conversion of passing the Builder as a template instead of an existing client should switch back to the client as a template and then remember to call org.apache.solr.client.solrj.impl.Http2SolrClient.Builder#withHttpClient from the template.

@jdyer1
Copy link
Contributor Author

jdyer1 commented Dec 12, 2024

your conversion of passing the Builder as a template instead of an existing client should switch back to the client as a template

I am not sure this ticket/PR has much value if we continue having the caller pass in a pre-built delegate client instead of a Builder. LBHttp2solrClient needs to create a client per base url, but the caller only supplies one. Yet LBHttp2SolrClientmight not know how to correctly clone that client, keeping in mind LBHttp2solrClient is now generic. It seems to me that passing in the pre-built client is more of a relic of the days before we used Bulders in solrJ, and not a really great API decision. I was hoping to improve that here.

@dsmiley
Copy link
Contributor

dsmiley commented Dec 12, 2024

Okay with me to stay with the builder. Probably add a new method to org.apache.solr.security.HttpClientBuilderPlugin for the builder, overloading setup.

As an aside, I'm totally confused about our authentication plugins, especially PKIAuthenticationPlugin as it apparently has a pivotal role, even when there's know "PKI" going on! CC @janhoy I suspect you might know

@gerlowskija
Copy link
Contributor

This LGTM, pending figuring out the test-failure issue and adding a CHANGES.txt entry that highlights the deprecations/removals.

The following were not removed, but perhaps should be considered to be marked "internal" or "Experimental"

+1 - Both seems like good candidates for a "lucene.experimental" annotation. (Presumably there should be a "solr.experimental", but I don't think that exists...)

@jdyer1
Copy link
Contributor Author

jdyer1 commented Dec 13, 2024

Probably add a new method to org.apache.solr.security.HttpClientBuilderPlugin for the builder,

Thank you for the insight on this. I went ahead and did this, but its a bit ugly. I'd rather not have both, but then again, I want to minimize changes to solr-core with this PR.

Besides, @dsmiley, I appreciate the change to synchronize on the Builder. That was a pretty bad miss on my part!

I also added @lucene.experimental to the two places discussed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT @iamsanjay ?

@@ -305,16 +306,16 @@ public void init(PluginInfo info) {
sb);
int soTimeout =
getParameter(args, HttpClientUtil.PROP_SO_TIMEOUT, HttpClientUtil.DEFAULT_SO_TIMEOUT, sb);

this.defaultClient =
this.httpClientBuilder =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please name httpSolrClientBuilder -- so as to clarify this isn't an Apache/Jetty HttpClient; it's a SolrClient.

.build();
this.defaultClient.addListenerFactory(this.httpListenerFactory);
this.loadbalancer = new LBHttp2SolrClient.Builder<Http2SolrClient>(defaultClient).build();
.withListenerFactory(List.of(this.httpListenerFactory));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed an issue. This overwrites the list of listeners that may already be present. Shouldn't we be augmenting, not replacing?

@janhoy
Copy link
Contributor

janhoy commented Dec 15, 2024

PKIAuthenticationPlugin as it apparently has a pivotal role, even when there's know "PKI" going on!

PKI plugin handles incoming internal requests from other nodes in the cluster. Then there is indeed PKI going on 😉 It is registered whenever any auth plugin is active.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants