Repository navigation
Give cloud requests a 2 second timeout, and cloud engines their properties when they join the Pipeline - #136
Merged
Merged
Conversation
…erties The Cloud Request Engine specification gives a TimeoutSeconds option with a default of 2 seconds, and requests here have no timeout. A local server that answers only after 8 seconds stands in for a slow cloud service, and the tests check that a request stops close to its timeout, with the default and with the engine's timeoutSeconds setting, and that a timeout of zero or less is refused. The Cloud Aspect Engine specification says a cloud engine built after the Cloud Request Engine can read the accessible Properties at build time, and the engines here take them only when they first process. The tests check that a cloud engine has its Properties once the Pipeline is built, and still takes them later when there are none yet.
The Cloud Request Engine specification gives the TimeoutSeconds option, the timeout of requests to the cloud service, with a default of 2 seconds. Requests here had none, so curl waited as long as the cloud service took, and the fallback without curl waited for PHP's default_socket_timeout. HttpClient now has setTimeoutSeconds and getTimeoutSeconds, 2 seconds unless set, and applies the timeout to the whole request with curl and to the stream context without it. The engine's new timeoutSeconds setting sets it on the client the engine uses, and a value that is not a number greater than 0 is refused when the engine is built. A client given to the engine keeps its own timeout unless the setting is given. HttpClient::setTimeoutSeconds refuses a timeout of 0 or less too, which curl would read as no timeout at all.
The Cloud Aspect Engine specification says a Cloud Aspect Engine built after the Cloud Request Engine can read the accessible Properties and populate its Property metadata at build time. The cloud engines here took them only when they first processed. They now take them in onRegistration when the Cloud Request Engine has them, and still take them on first use when it has none yet, so an empty result is never kept.
This was referenced Nov 14, 2026
testAGivenHttpClientTakesTheSetting builds engines with the test's made-up resource key. Once the engine asks for discovery when it is built, as #128 makes it, a build against the real cloud service fails on that key. Against an address where nothing listens the request fails at once, which does not fail the build, so the test checks the client's timeout either way.
The timeout tests fail a request that stops more than EARLY_SECONDS before its timeout or LATE_SECONDS after it, and the local server that stands in for a slow cloud service has SERVER_START_SECONDS to start. Each test now writes to standard error the time each request took and the server's start time beside those limits, so every runner's figures are in its log.
jwrosewell
marked this pull request as ready for review
November 15, 2026 08:01
jwrosewell
added a commit
that referenced
this pull request
Nov 15, 2026
… product when the Pipeline is built (#164) Two parts of the specifications that a cloud engine follows were left for later while [#128](#128) and [#136](#136) changed `CloudEngine.php`. Both are now on `rc`. Every specification link below is to [51Degrees/specifications at 6570727d](https://github.com/51Degrees/specifications/tree/6570727d2af6967390d3c4fbec77b9cf0540d3f3), the head of its `main` branch today.
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.
This draft closes two gaps in the cloud packages. Requests to the cloud service get the timeout the specification gives, 2 seconds unless the new
timeoutSecondssetting sets another, where today they have none. And a cloud engine takes its Properties from the Cloud Request Engine when it joins the Pipeline, where today it takes them only when it first processes.The problem
File and line at
rc36b71fde.HttpClient::makeCloudRequestsets no timeout. With curl (pipeline/cloudrequestengine/src/HttpClient.phplines 85 to 100) a request waits as long as the cloud service takes, and without curl (lines 50 to 59) it waits for PHP'sdefault_socket_timeout. The Cloud Request Engine has no setting for it (CloudRequestEngine.phplines 81 to 136). The red run below shows a request to a server that answers after 8 seconds waiting the full 8 seconds.CloudEngine::onRegistration(CloudEngine.phplines 62 to 69) only checks that the Pipeline has a Cloud Request Engine, andprocessInternaltakes the Properties (lines 82 to 85). So until the first request is processed, a cloud engine reports no Properties, even when the Cloud Request Engine already has them.What the specification says
All at specifications 706de1f0.
The timeout. The Configuration options table of
pipeline-elements/cloud-request-engine.md, line 506, which lines 492 and 493 introduce as "the configuration options that are unique to this Engine", has this row.The setting here is named
timeoutSeconds, in the camelCase of the engine's other settings (resourceKey,licenseKey,cloudEndPoint,cloudRequestOrigin). A timeout of 0 or less is refused, under James Rosewell's rule of 12 November, "Let's make it really hard for people to configure us badly", because curl reads 0 as no timeout at all.The Properties.
pipeline-elements/cloud-aspect-engine.mdlines 40 to 44, under Start-up activity.Lines 46 to 51 cover the case where it has not.
What changed
HttpClienthassetTimeoutSeconds(float)andgetTimeoutSeconds(), 2 seconds (Constants::TIMEOUT_SECONDS_DEFAULT) unless set. With curl it setsCURLOPT_TIMEOUT_MS, which covers the whole request, connecting included, andCURLOPT_NOSIGNAL, so that a timeout below one second works too. Without curl it sets the stream context'stimeout, which PHP applies to reading the answer. The Cloud Request Engine reads a newtimeoutSecondssetting and sets it on the client it uses. A value that is not a number greater than 0 is refused when the engine is built, andsetTimeoutSecondsrefuses one of 0 or less, both withConstants::MESSAGE_TIMEOUT_NOT_POSITIVE. A client passed ashttpClientkeeps its own timeout unless the setting is given. A request that times out fails as one that could not be made, with theCloudRequestExceptionthatHttpClientalready raises for a failed connection.CloudEngine::onRegistrationtakes the engine's Properties from the Cloud Request Engine when it has a product for the engine's Data Key, and leaves them as they are when it has none.processInternalstill takes them on first use, as before, so an engine that joined before discovery completed gets them then, and an empty result is never kept. Every cloud engine here, Device Detection, hardware profile, location and IP Intelligence, inherits it.On
rcthe Cloud Request Engine asks the cloud service for its Properties only when it first processes, so the second change has an effect when the engine already has them, for example aftergetEngineProperties(). #128 makes the engine ask when it is built, and with it this becomes the normal case. The two merge cleanly, and the discovery requests #128 makes at build time go through the same client, so they take the timeout too.Before you upgrade
default_socket_timeout, 60 seconds unless changed. A request that takes longer than the timeout now fails withCloudRequestException, "The request to the cloud service at '...' could not be made", followed by the reason curl gives, and the error is recorded against the Cloud Request Engine as any other failed request is. SettimeoutSecondsin the Cloud Request Engine's settings to wait longer.timeoutto reading the answer, so the time to connect still followsdefault_socket_timeout.timeoutSecondsof 0 or less, or one that is not a number, is refused withInvalidArgumentExceptionwhen the engine is built, andHttpClient::setTimeoutSecondsrefuses 0 or less in the same way. An empty value of the setting counts as absent.getProperties()before processing and expected nothing will now find them.timeoutSecondssetting,HttpClient::setTimeoutSeconds(),HttpClient::getTimeoutSeconds(),Constants::TIMEOUT_SECONDS_DEFAULTandConstants::MESSAGE_TIMEOUT_NOT_POSITIVE. Nothing is removed or renamed.Tests
Two new test files in the cloud request engine's Unit suite, both written from the sentences above.
pipeline/cloudrequestengine/tests/TimeoutTests.php(6 tests) starts a local PHP server that answers each request only after 8 seconds and checks that a request fails with "could not be made" no earlier than 0.25 seconds before its timeout and less than 4 seconds after it, with the default, withsetTimeoutSeconds, and with the engine'stimeoutSecondssetting. It also checks that a given client takes the setting, that the engine refuses 0, -1 and'two', and thatsetTimeoutSecondsrefuses 0 and -1.pipeline/cloudrequestengine/tests/AspectEnginePropertiesTests.php(3 tests) checks that a cloud engine has its Properties once the Pipeline is built, that it builds without them and takes them on first use when the Cloud Request Engine has none yet, and that a missing product does not fail the build. The last two pass onrctoo, and check that those cases keep working.The red and green runs use a throwaway workflow that installs the packages from the checkout as CI does (
Install-RootComposerandSet-RootAutoloadfromci/packages.ps1) and runs PHPUnit 9.6.34 with--fail-on-warning, as CI does.tmp/fixer-dx-red4cb3e2e, which isrcwith the test commit and the workflow, Ubuntu with PHP 8.5. It failed, as it should, with "Tests: 8, Assertions: 7, Errors: 2, Failures: 4".timeoutSeconds.getTimeoutSeconds().testACloudEngineHasItsPropertiesWhenItJoinsThePipelinewith "Failed asserting that an array has the key 'value'."testSetTimeoutSecondsRefusesZeroOrLesscame after the red run, with the refusal insetTimeoutSecondsit tests. Onrcthe method does not exist, as the red run shows for the other tests that call it.tmp/fixer-dx-green70def13, which is this head with the workflow. It ran on Ubuntu with PHP 7.4, the lowest the package supports, and 8.5, the highest, and on Windows with PHP 8.3, because the timeout tests start a local PHP server as a process of its own. All three jobs passed.onRegistration, "OK (29 tests, 203 assertions)" on each.tests/IpIntelligenceCloudTests.php, "Tests: 8, Assertions: 25, Skipped: 1" on PHP 8.5. IP Intelligence needs PHP 8.0 and builds an extension, so it is not installed on PHP 7.4 or on Windows, as in CI.setTimeoutSecondsrefused 0 or less. The cloud request engine suite gave "OK (98 tests, 432 assertions)" on all three jobs, and the Windows job failed only because the workflow ran the IP Intelligence tests there, where the package is not installed.The timing limits and what each runner measured
TimeoutTestsfails a request that stops more thanEARLY_SECONDS, 0.25 seconds, before its timeout, orLATE_SECONDS, 4 seconds, after it, and gives the local server that stands in for a slow cloud serviceSERVER_START_SECONDS, 10 seconds, to start (TimeoutTests.phplines 41 to 53). Commit 8a37ddfb makes each test write to standard error the time each timed-out request and the server took, beside those limits, so every run records them. Each figure below is from the job's own log. Each job has three timed requests, of a 2 second and two 1 second timeouts.The latest any request stopped was 0.014 seconds after its timeout, on macOS, against a limit of 4 seconds, no request stopped early, and the slowest server start was 0.314 seconds, on Windows, against 10 seconds. The macOS figures come from the throwaway run because a draft's full build leaves macOS out, and the rc head's release build, which has macOS, prints them too.
The tests that passed on
rcThe last two
AspectEnginePropertiesTests,testACloudEngineTakesItsPropertiesLaterWhenThereAreNoneYetandtestAMissingProductDoesNotFailTheBuild, pass onrctoo. Run 43688673516, job 132734460487, at 76701449, which is this draft's head 8a37ddf with a temporary commit holding a workflow and two patch files, ran the file unchanged, where all three tests passed, then under each change.processInternalno longer takes the PropertiestestACloudEngineTakesItsPropertiesLaterWhenThereAreNoneYetonlyonRegistrationthrows when the Cloud Request Engine has Properties but none for this enginetestAMissingProductDoesNotFailTheBuildonlyOther drafts that change the same files
git merge-treeof this head with each open draft finds one conflict.CloudEngine::onRegistration, where it finds the Cloud Request Engine by its new Data Key,cloud-response, or bycloud. Whichever merges second keeps that lookup and takes the Properties from the engine it finds.HttpClient.php,CloudRequestEngine.php,Constants.php,phpunit.xml), #127, #121 (phpunit.xml) and #113 merge with it cleanly.testAGivenHttpClientTakesTheSettingbuilt its engines with the test's made-up resource key against the real cloud service, so in a build ofrcwith every open PHP draft merged, run 43683733486, job 132719933541 (Ubuntu, PHP 7.4), the build of the engine failed with "'resource_key' could not be read as a valid resource key". Commit bd68e437 points those engines athttp://127.0.0.1:1/, where nothing listens, so a request made while an engine is built fails at once, which Ask the cloud service for discovery when the Cloud Request Engine is built, build it from saved results, and log the cloud service's warnings #128 does not let fail the build, and the test checks the client's timeout with or without Ask the cloud service for discovery when the Cloud Request Engine is built, build it from saved results, and log the cloud service's warnings #128. Run 43687155017, on a composite ofrc44dbd4e with every open PHP draft merged, these changes included (85871de5 on a throwaway branch), passed the Unit suites of pipeline/core, pipeline/engines and pipeline/cloudrequestengine on Ubuntu 24.04 with PHP 8.1 and 8.5, this test among them.Release
This draft changes package code in
fiftyone.pipeline.cloudrequestengineonly, so that package takes a new version. Under the release plan of #37, the packages that depend on it are released with it.Build
Full build run 43688679819 passed on this draft's head 8a37ddfb. The build ran from
rcat bee9ddd8. The run printed 9 of 9Setting 'PullRequestSha'lines on 8a37ddf, and Configure printed "Left out 5 macOS entries". All eight Build and Test jobs passed, Ubuntu with PHP 7.4 and 8.0 to 8.5 and Windows with PHP 8.3. Attempt 1's Ubuntu PHP 8.5 job failed in Fetch Assets, before any test ran, when the download of the free ASN file stalled (the data repository's script reported its stall as a formatting error, data#13), and that job was run again as attempt 2.macOS was left out of this draft's full build under the 9 November decision. The rc head is built with macOS before it merges into main.
What remains