Skip to content

Commit

Permalink
feat(flags): Add specific timeout for feature flags
Browse files Browse the repository at this point in the history
  • Loading branch information
neilkakkar committed Mar 8, 2024
1 parent 258caba commit 2c6f466
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 8 deletions.
10 changes: 10 additions & 0 deletions lib/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ class Client
*/
private $personalAPIKey;

/**
* @var integer
*/
private $featureFlagsRequestTimeout;

/**
* Consumer object handles queueing and bundling requests to PostHog.
*
Expand Down Expand Up @@ -84,6 +89,7 @@ public function __construct(
null,
(int) ($options['timeout'] ?? 10000)
);
$this->featureFlagsRequestTimeout = (int) ($options['feature_flags_request_timeout_ms'] ?? 3000);
$this->featureFlags = [];
$this->groupTypeMapping = [];
$this->distinctIdsFeatureFlagsReported = new SizeLimitedHash(SIZE_LIMIT);
Expand Down Expand Up @@ -453,6 +459,10 @@ public function decide(
[
// Send user agent in the form of {library_name}/{library_version} as per RFC 7231.
"User-Agent: posthog-php/" . PostHog::VERSION,
],
[
"shouldRetry" => false,
"timeout" => $this->featureFlagsRequestTimeout
]
)->getResponse();
}
Expand Down
21 changes: 16 additions & 5 deletions lib/HttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,17 @@ public function __construct(
* @param string $path
* @param string|null $payload
* @param array $extraHeaders
* @param array $requestOptions
* @return HttpResponse
*/
public function sendRequest(string $path, ?string $payload, array $extraHeaders = []): HttpResponse
public function sendRequest(string $path, ?string $payload, array $extraHeaders = [], array $requestOptions = []): HttpResponse
{
$protocol = $this->useSsl ? "https://" : "http://";

$backoff = 100; // Set initial waiting time to 100ms

$shouldRetry = $requestOptions['shouldRetry'] ?? true;

do {
// open connection
$ch = curl_init();
Expand All @@ -84,11 +87,17 @@ public function sendRequest(string $path, ?string $payload, array $extraHeaders
$headers[] = 'Content-Encoding: gzip';
}

// check if timeout exists in request options, if not use default
$timeout = $this->curlTimeoutMilliseconds;
if (isset($requestOptions['timeout'])) {
$timeout = $requestOptions['timeout'];
}

curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($headers, $extraHeaders));
curl_setopt($ch, CURLOPT_URL, $protocol . $this->host . $path);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->curlTimeoutMilliseconds);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->curlTimeoutMilliseconds);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, $timeout);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $timeout);

// retry failed requests just once to diminish impact on performance
$httpResponse = $this->executePost($ch);
Expand All @@ -101,7 +110,9 @@ public function sendRequest(string $path, ?string $payload, array $extraHeaders
// log error
$this->handleError($ch, $responseCode);

if (($responseCode >= 500 && $responseCode <= 600) || 429 == $responseCode) {
if ($shouldRetry === false) {
break;
} elseif (($responseCode >= 500 && $responseCode <= 600) || 429 == $responseCode) {
// If status code is greater than 500 and less than 600, it indicates server error
// Error code 429 indicates rate limited.
// Retry uploading in these cases.
Expand All @@ -115,7 +126,7 @@ public function sendRequest(string $path, ?string $payload, array $extraHeaders
} else {
break; // no error
}
} while ($backoff < $this->maximumBackoffDuration);
} while ($shouldRetry && $backoff < $this->maximumBackoffDuration);

return $httpResponse;
}
Expand Down
6 changes: 3 additions & 3 deletions test/MockedHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public function __construct(
$this->flagEndpointResponse = $flagEndpointResponse;
}

public function sendRequest(string $path, ?string $payload, array $extraHeaders = []): HttpResponse
public function sendRequest(string $path, ?string $payload, array $extraHeaders = [], array $requestOptions = []): HttpResponse
{
if (!isset($this->calls)) {
$this->calls = [];
}
array_push($this->calls, array("path" => $path, "payload" => $payload));
array_push($this->calls, array("path" => $path, "payload" => $payload, "extraHeaders" => $extraHeaders, "requestOptions" => $requestOptions));

if (str_starts_with($path, "/decide/")) {
return new HttpResponse(json_encode(MockedResponses::DECIDE_REQUEST), 200);
Expand All @@ -49,6 +49,6 @@ public function sendRequest(string $path, ?string $payload, array $extraHeaders
return new HttpResponse(json_encode($this->flagEndpointResponse), 200);
}

return parent::sendRequest($path, $payload, $extraHeaders);
return parent::sendRequest($path, $payload, $extraHeaders, $requestOptions);
}
}
42 changes: 42 additions & 0 deletions test/PostHogTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public function testCaptureWithSendFeatureFlagsOption(): void
self::FAKE_API_KEY,
[
"debug" => true,
"feature_flags_request_timeout_ms" => 1234,
],
$this->http_client,
"test"
Expand All @@ -112,14 +113,20 @@ public function testCaptureWithSendFeatureFlagsOption(): void
0 => array(
"path" => "/api/feature_flag/local_evaluation?token=random_key",
"payload" => null,
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3', 1 => 'Authorization: Bearer test'),
"requestOptions" => array(),
),
1 => array(
"path" => "/decide/?v=2",
"payload" => sprintf('{"api_key":"%s","distinct_id":"john"}', self::FAKE_API_KEY),
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array("timeout" => 1234, "shouldRetry" => false),
),
2 => array(
"path" => "/batch/",
"payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/simpleFlag":true,"$feature\/having_fun":false,"$feature\/enabled-flag":true,"$feature\/disabled-flag":false,"$feature\/multivariate-simple-test":"variant-simple-value","$feature\/simple-test":true,"$feature\/multivariate-test":"variant-value","$feature\/group-flag":"decide-fallback-value","$feature\/complex-flag":"decide-fallback-value","$feature\/beta-feature":"decide-fallback-value","$feature\/beta-feature2":"alakazam","$feature\/feature-1":"decide-fallback-value","$feature\/feature-2":"decide-fallback-value","$feature\/variant-1":"variant-1","$feature\/variant-3":"variant-3","$active_feature_flags":["simpleFlag","enabled-flag","multivariate-simple-test","simple-test","multivariate-test","group-flag","complex-flag","beta-feature","beta-feature2","feature-1","feature-2","variant-1","variant-3"],"$lib":"posthog-php","$lib_version":"3.0.3","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"3.0.3","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}',
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array(),
),
)
);
Expand Down Expand Up @@ -167,10 +174,14 @@ public function testCaptureWithLocalSendFlags(): void
0 => array(
"path" => "/api/feature_flag/local_evaluation?token=random_key",
"payload" => null,
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3', 1 => 'Authorization: Bearer test'),
"requestOptions" => array(),
),
1 => array(
"path" => "/batch/",
"payload" => '{"batch":[{"event":"Module PHP Event","properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"3.0.3","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"3.0.3","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}',
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array(),
),
)
);
Expand Down Expand Up @@ -211,10 +222,15 @@ public function testCaptureWithLocalSendFlagsNoOverrides(): void
0 => array(
"path" => "/api/feature_flag/local_evaluation?token=random_key",
"payload" => null,
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3', 1 => 'Authorization: Bearer test'),
"requestOptions" => array(),

),
1 => array(
"path" => "/batch/",
"payload" => '{"batch":[{"event":"Module PHP Event","properties":{"$feature\/true-flag":"random-override","$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"3.0.3","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"3.0.3","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}',
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array(),
),
)
);
Expand Down Expand Up @@ -245,10 +261,14 @@ public function testIsFeatureEnabled()
0 => array(
"path" => "/api/feature_flag/local_evaluation?token=random_key",
"payload" => null,
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3', 1 => 'Authorization: Bearer test'),
"requestOptions" => array(),
),
1 => array(
"path" => "/decide/?v=2",
"payload" => sprintf('{"api_key":"%s","distinct_id":"user-id","person_properties":{"distinct_id":"user-id"}}', self::FAKE_API_KEY),
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array("timeout" => 3000, "shouldRetry" => false),
),
)
);
Expand All @@ -264,13 +284,17 @@ public function testIsFeatureEnabledGroups()
0 => array(
"path" => "/api/feature_flag/local_evaluation?token=random_key",
"payload" => null,
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3', 1 => 'Authorization: Bearer test'),
"requestOptions" => array(),
),
1 => array(
"path" => "/decide/?v=2",
"payload" => sprintf(
'{"api_key":"%s","distinct_id":"user-id","groups":{"company":"id:5"},"person_properties":{"distinct_id":"user-id"},"group_properties":{"company":{"$group_key":"id:5"}}}',
self::FAKE_API_KEY
),
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array("timeout" => 3000, "shouldRetry" => false),
),
)
);
Expand All @@ -285,10 +309,14 @@ public function testGetFeatureFlag()
0 => array(
"path" => "/api/feature_flag/local_evaluation?token=random_key",
"payload" => null,
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3', 1 => 'Authorization: Bearer test'),
"requestOptions" => array(),
),
1 => array(
"path" => "/decide/?v=2",
"payload" => sprintf('{"api_key":"%s","distinct_id":"user-id","person_properties":{"distinct_id":"user-id"}}', self::FAKE_API_KEY),
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array("timeout" => 3000, "shouldRetry" => false),
),
)
);
Expand All @@ -314,13 +342,17 @@ public function testGetFeatureFlagGroups()
0 => array(
"path" => "/api/feature_flag/local_evaluation?token=random_key",
"payload" => null,
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3', 1 => 'Authorization: Bearer test'),
"requestOptions" => array(),
),
1 => array(
"path" => "/decide/?v=2",
"payload" => sprintf(
'{"api_key":"%s","distinct_id":"user-id","groups":{"company":"id:5"},"person_properties":{"distinct_id":"user-id"},"group_properties":{"company":{"$group_key":"id:5"}}}',
self::FAKE_API_KEY
),
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array("timeout" => 3000, "shouldRetry" => false),
),
)
);
Expand Down Expand Up @@ -480,10 +512,14 @@ public function testDefaultPropertiesGetAddedProperly(): void
0 => array(
"path" => "/api/feature_flag/local_evaluation?token=random_key",
"payload" => null,
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3', 1 => 'Authorization: Bearer test'),
"requestOptions" => array(),
),
1 => array(
"path" => "/decide/?v=2",
"payload" => sprintf('{"api_key":"%s","distinct_id":"some_id","groups":{"company":"id:5","instance":"app.posthog.com"},"person_properties":{"distinct_id":"some_id","x1":"y1"},"group_properties":{"company":{"$group_key":"id:5","x":"y"},"instance":{"$group_key":"app.posthog.com"}}}', self::FAKE_API_KEY),
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array("timeout" => 3000, "shouldRetry" => false),
),
)
);
Expand All @@ -504,6 +540,8 @@ public function testDefaultPropertiesGetAddedProperly(): void
0 => array(
"path" => "/decide/?v=2",
"payload" => sprintf('{"api_key":"%s","distinct_id":"some_id","groups":{"company":"id:5","instance":"app.posthog.com"},"person_properties":{"distinct_id":"override"},"group_properties":{"company":{"$group_key":"group_override"},"instance":{"$group_key":"app.posthog.com"}}}', self::FAKE_API_KEY),
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array("timeout" => 3000, "shouldRetry" => false),
),
)
);
Expand All @@ -518,6 +556,8 @@ public function testDefaultPropertiesGetAddedProperly(): void
0 => array(
"path" => "/decide/?v=2",
"payload" => sprintf('{"api_key":"%s","distinct_id":"some_id","groups":{"company":"id:5"},"person_properties":{"distinct_id":"some_id"},"group_properties":{"company":{"$group_key":"id:5"}}}', self::FAKE_API_KEY),
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array("timeout" => 3000, "shouldRetry" => false),
),
)
);
Expand All @@ -532,6 +572,8 @@ public function testDefaultPropertiesGetAddedProperly(): void
0 => array(
"path" => "/decide/?v=2",
"payload" => sprintf('{"api_key":"%s","distinct_id":"some_id","groups":{"company":"id:5","instance":"app.posthog.com"},"person_properties":{"distinct_id":"some_id","x1":"y1"},"group_properties":{"company":{"$group_key":"id:5","x":"y"},"instance":{"$group_key":"app.posthog.com"}}}', self::FAKE_API_KEY),
"extraHeaders" => array(0 => 'User-Agent: posthog-php/3.0.3'),
"requestOptions" => array("timeout" => 3000, "shouldRetry" => false),
),
)
);
Expand Down

0 comments on commit 2c6f466

Please sign in to comment.