Repository navigation
Limit the cloud host lookup to the timeout and log why processing failed - #123
Draft
jwrosewell wants to merge 7 commits into
Draft
jwrosewell wants to merge 7 commits into
jwrosewell wants to merge 7 commits into
Conversation
The Cloud Request Engine sets its timeout, 2 seconds by default, as the connect and read timeouts of each HttpURLConnection. Neither covers the lookup of the host, which waits as long as the system resolver does, so a resolver that stops answering held a request for 30 seconds. HttpClientDefault now looks up the host within the connection's connect timeout before connecting. Callers waiting on the same host share one lookup, and a request through a proxy is not looked up. The error FlowData logs for a processing failure now carries the exception. The location packages took SLF4J 1.7.36 with Logback 1.3.16, which binds only through the SLF4J 2 API, so their tests logged nothing. They now take SLF4J 2.0.20, as the other packages do.
A failed cloud request leaves no response. GeoLocationCloudEngine read it anyway, so each failure added a NullPointerException from the JSON parser after the Cloud Request Engine's own error. It now logs a warning and processes nothing, as CloudAspectEngineBase does.
GeoLocationCloudEngine, DeviceDetectionCloudEngine, IPIntelligenceCloudEngine and the developer example's SimpleCloudEngine override processEngine and read the Cloud Request Engine's response without the check that CloudAspectEngineBase makes. A failed cloud request leaves no response, so each failure added a NullPointerException from the JSON parser after the Cloud Request Engine's own error. Each now logs one warning and processes nothing. The message is shared as Constants.Messages.NoCloudResponse.
HostLookup relies on the JVM's address cache, so its comment now says the two limits that follow. With networkaddress.cache.ttl at 0 the connection looks the host up again with no limit, and a request the connection would send on a kept-alive socket is looked up too, so it fails once the cached address expires and the resolver does not answer. HttpClientDefault says that a subclass connecting through a proxy of its own is still looked up. CloudAspectEngineBase logs Constants.Messages.NoCloudResponse, as the four engines that override processEngine do, and the geo-location test checks the warning as the Device Detection and IP Intelligence tests do.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In full build 43382362370 (head
b96922b1of #91), the job MacOS_X64_Java_17 failed in the geo-location cloud value tests. The first test waited 30.63 seconds, then all three threw, and the log does not say why.The Cloud Request Engine's timeout is 2 seconds by default (
Constants.javaline 41).Which wait was 30 seconds, and whose
The engine applies its timeout as the connect and read timeouts of each
HttpURLConnection(CloudRequestEngineDefault.javalines 830 to 835). Neither covers the lookup of the host name. The connection looks the host up before its connect timeout starts, and the lookup waits for as long as the operating system's resolver does.A probe on a
macos-15-intelrunner (run 43435285991), with Java 17.0.20.1 on macOS 15.7.9 as in the failing job, sent lookups for51degrees.comto an address that never answers and timed them.cloud.51degrees.com, resolver answeringUnknownHostExceptionSo the 30 seconds is the macOS resolver giving up on a DNS server that does not answer. The JVM then keeps the failed lookup for 10 seconds, which is why the second and third tests failed in 3 ms and 19 ms. That is the shape of the failure.
The test class takes these times when the cloud answers, over the 14 full build attempts between 11 and 13 November (13 runs, one of them run twice). Only some of them ran the macOS jobs.
No runner is near 30 seconds, so no limit needs to change. The wait was not the test's or the engine's, and it was longer than the engine's timeout says a request can take.
Why the cause was not in the log
PipelineDefault.processthrows anAggregateExceptionthat holds the element's exception only as a suppressed exception (PipelineDefault.javalines 495 to 504,AggregateException.javaline 48). Surefire 2.22.2 trims a stack trace to the test's own frames, so the cause is not printed.FlowDataDefault.addErrorloggedError occurred during processing of ...without the exception (FlowDataDefault.javaline 124).location/pom.xmlline 66) with Logback 1.3.16, which binds only through the SLF4J 2 API. Their tests logged nothing at all, as the failing job printed.CloudRequestEngineDefault.javaline 364).What changed
HttpClientDefaultlooks up the host within the connection's connect timeout before it connects, through the newHostLookupinpipeline.engines. A lookup that takes longer throwsSocketTimeoutException: Looking up the host 'cloud.51degrees.com' took longer than the timeout of 2000 ms.A lookup that succeeds leaves the address in the JVM's cache, where the connection then finds it. Callers waiting on the same host share one lookup, so a resolver that does not answer holds one thread, not one for each request. Nothing is looked up when the connect timeout is 0, which means no limit, or when a proxy makes the request, because the proxy looks up the host. The Cloud Request Engine usesHttpClientDefaultunless it is given another client, so its timeout now covers the lookup.Relying on the JVM's cache has two limits, which
HostLookup's comment also says. With the security propertynetworkaddress.cache.ttlset to 0 nothing is cached, so the connection looks the host up again itself, with no limit, as before this change. And every request is looked up first, including one the connection would have sent on a kept-alive socket with no lookup at all. So once the cached address expires, 30 seconds after it was found by default, a resolver that does not answer now fails such a request within the timeout, where before the open socket would have served it. Avoiding that would need either a thread for each request's whole connect, which a resolver that does not answer would hold for 30 seconds each, or a way to hand the connection an address, whichHttpURLConnectionon Java 8 does not have.The default
ProxySelectordecides whether a request goes through a proxy. A subclass ofHttpClientDefaultwhoseconnectopens the connection through a proxy of its own still has the host looked up here, whichHttpClientDefault's comment says.FlowDataDefault.addErrorlogs the exception with the error.The location packages take SLF4J 2.0.20, as the other five package trees do.
GeoLocationCloudEngine,DeviceDetectionCloudEngine,IPIntelligenceCloudEngineand the developer example'sSimpleCloudEngineoverrideprocessEngineand read the cloud response without the check thatCloudAspectEngineBase.processEnginemakes (CloudAspectEngineBase.javalines 425 to 439). A failed cloud request leaves no response, so each one addedNullPointerException: Cannot invoke "String.length()" because "s" is nullafter the real error. The fault run 43437302629 on this branch showed it, once logging worked and before this check was added. Each now logs one warning,Constants.Messages.NoCloudResponse, and processes nothing.CloudAspectEngineBase.processEnginelogs the sameNoCloudResponsewarning in place of its own copy of the text.How it was verified
New tests:
HostLookupTests, 8 tests. A resolver that does not answer times out within the limit with the host in the message, an unknown host keeps its cause, a timeout of 0 looks nothing up, a lookup after a failure looks up again, two callers at once share one lookup, a request through a proxy looks nothing up, and both request methods ofHttpClientDefaultfail within the connect timeout.FlowDataTests.FlowData_AddError_LogsTheException.NoResponse_ProcessesNothinginGeoLocationCloudEngineNullValueTestsand in the Device Detection and IP Intelligence cloudProductMissingTests, each checking that one warning is logged for each response.Run against
rcwith only the tests and the new message constant added (run 43518934300),FlowData_AddError_LogsTheExceptionfails withArgument(s) are different!, because the error is logged without the exception, and the threeNoResponse_ProcessesNothingtests fail withNullPointerException: Cannot invoke "String.length()" because "s" is null. On this head (run 43518954543) all four pass, with the 8HostLookupTests.Fault runs on
ubuntu-22.04build the packages, run the geo-location cloud value tests with the cloud answering, then send every name lookup to an address that never answers, with a resolver timeout of 30 seconds as on macOS, and run them again.rc(run 43437315713)SocketTimeoutException: Looking up the host 'cloud.51degrees.com' took longer than the timeout of 2000 ms.in the engine's warning and in the processing errorEach test now takes 4 seconds under the fault, being the 2 second limit on the lookup when the engine is built and again when it processes. Both fault runs show as cancelled, because once the runner could not look up names it could not report back, but every step had finished and the job pages show their results.
Full build: none on this head,
35ac923f, which hasrc30e4886 merged in. The full build 43441956524 of the earlier headdd8d7356passed in 14 of its 15 build and test jobs across its two attempts, everyPullRequestShaequal to that head, the Intel macOS Java 11 job among them withFiftyoneValueTestsin 3.21 s andHostLookupTestsin 1.03 s. MacOS_X64_Java_21 failed in both attempts in the Selenium contract test that #124 fixes, which does not touch this change. This draft is built again with macOS once #124 is inrcandrcis merged in here.What remains
DataUploaderHttp,ShareUsageElementandDidClientopen their own connections, so their lookups still wait as long as the resolver does. None of them is the Cloud Request Engine.DataUpdateServiceDefaultconnects throughHttpClient.connectand then callsgetResponseCodeitself (lines 821 and 838), not throughpostDataorgetResponseString, so the lookups of data file downloads have no limit either.