From eb373eb82f0ae75c54408850e3e3974123829038 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Mon, 20 Oct 2025 15:15:18 -0400 Subject: [PATCH 1/2] fix: Handle all exceptions within Guzzle feature requester fixes #225 --- .../Impl/Integrations/GuzzleFeatureRequester.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/LaunchDarkly/Impl/Integrations/GuzzleFeatureRequester.php b/src/LaunchDarkly/Impl/Integrations/GuzzleFeatureRequester.php index 65f5d93d..41711b02 100644 --- a/src/LaunchDarkly/Impl/Integrations/GuzzleFeatureRequester.php +++ b/src/LaunchDarkly/Impl/Integrations/GuzzleFeatureRequester.php @@ -4,6 +4,7 @@ namespace LaunchDarkly\Impl\Integrations; +use Exception; use GuzzleHttp\Client; use GuzzleHttp\Exception\BadResponseException; use GuzzleHttp\HandlerStack; @@ -75,6 +76,9 @@ public function getFeature(string $key): ?FeatureFlag $this->handleUnexpectedStatus($code, "GuzzleFeatureRequester::get"); } return null; + } catch (Exception $e) { + $this->_logger->error("GuzzleFeatureRequester::get encountered an exception retrieving flag key {$key}: " . $e->getMessage()); + return null; } } @@ -99,6 +103,9 @@ public function getSegment(string $key): ?Segment $this->handleUnexpectedStatus($code, "GuzzleFeatureRequester::get"); } return null; + } catch (Exception $e) { + $this->_logger->error("GuzzleFeatureRequester::get encountered an exception retrieving segment key {$key}: " . $e->getMessage()); + return null; } } @@ -117,6 +124,9 @@ public function getAllFeatures(): ?array /** @psalm-suppress PossiblyNullReference (resolved in guzzle 7) */ $this->handleUnexpectedStatus($e->getResponse()->getStatusCode(), "GuzzleFeatureRequester::getAll"); return null; + } catch (Exception $e) { + $this->_logger->error("GuzzleFeatureRequester::getAll encountered an exception retrieving all flags: " . $e->getMessage()); + return null; } } From 97cc43b7178a2d17c5702adef61df4362e649dfd Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Mon, 20 Oct 2025 19:59:22 +0000 Subject: [PATCH 2/2] add test to ensure we don't throw an error when the connection times out --- .../GuzzleFeatureRequesterTest.php | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/Impl/Integrations/GuzzleFeatureRequesterTest.php b/tests/Impl/Integrations/GuzzleFeatureRequesterTest.php index 3dc323b3..b35e8001 100644 --- a/tests/Impl/Integrations/GuzzleFeatureRequesterTest.php +++ b/tests/Impl/Integrations/GuzzleFeatureRequesterTest.php @@ -144,4 +144,37 @@ public function testSendsCorrectWrapperNameHeaders(?string $wrapper_name, ?strin $this->assertNotContains('X-LaunchDarkly-Wrapper', $headers); } } + + public function testTimeoutReturnsDefaultValue(): void + { + /** @var LoggerInterface **/ + $logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); + + $config = [ + 'logger' => $logger, + 'timeout' => 1, // Set a very short timeout + 'connect_timeout' => 1, + ]; + + $client = new Client(); + // Configure the mock server to delay the response by 2 seconds + $delayRule = [ + 'request' => [ + 'url' => '/sdk/flags/delayed-flag', + ], + 'response' => [ + 'fixedDelayMilliseconds' => 2000, + 'status' => 200, + 'body' => '{"key": "delayed-flag", "version": 1}' + ] + ]; + + $client->request('POST', 'http://localhost:8080/__admin/mappings', ['json' => $delayRule]); + + $requester = new GuzzleFeatureRequester('http://localhost:8080', 'sdk-key', $config); + $result = $requester->getFeature("delayed-flag"); + + // The request should timeout and return null (default value) instead of throwing an exception + $this->assertNull($result); + } }