[1.34.1] Maximum call stack size of 8339456 bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
Environment details
- OS: Debian Bullseye
- PHP version: 8.3
- Package name and version: google/gax:v1.34.1 (1.34.0 was working without errors)
Steps to reproduce
- Error seems a little random, but occurs always at the same place. I couldn't reproduce it myself, because for many thousands requests, there's like 5-10 errors per day.
Code example
Exception trace
{
"class": "Error",
"message": "Maximum call stack size of 8339456 bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?",
"code": 0,
"file": "/app/vendor/google/gax/src/CredentialsWrapper.php:247",
"trace": [
"/app/vendor/grpc/grpc/src/lib/UnaryCall.php:43",
"/app/vendor/grpc/grpc/src/lib/BaseStub.php:295",
"/app/vendor/grpc/grpc/src/lib/BaseStub.php:545",
"/app/vendor/google/gax/src/Transport/GrpcTransport.php:228",
"/app/vendor/google/gax/src/GapicClientTrait.php:641",
"/app/vendor/google/gax/src/Middleware/CredentialsWrapperMiddleware.php:58",
"/app/vendor/google/gax/src/Middleware/FixedHeaderMiddleware.php:68",
"/app/vendor/google/gax/src/Middleware/RetryMiddleware.php:89",
"/app/vendor/google/gax/src/Middleware/RequestAutoPopulationMiddleware.php:73",
"/app/vendor/google/gax/src/Middleware/OptionsFilterMiddleware.php:61",
"/app/vendor/google/gax/src/GapicClientTrait.php:606",
"/app/vendor/google/cloud-pubsub/src/V1/Gapic/PublisherGapicClient.php:909",
"/app/vendor/google/cloud-core/src/ExponentialBackoff.php:97",
"/app/vendor/google/cloud-core/src/GrpcRequestWrapper.php:135",
"/app/vendor/google/cloud-core/src/GrpcTrait.php:81",
"/app/vendor/google/cloud-pubsub/src/Connection/Grpc.php:284",
"/app/vendor/google/cloud-pubsub/src/Topic.php:527",
"/app/vendor/google/cloud-pubsub/src/Topic.php:482",
... (app-specific trace)
]
}
We also encountered this problem. PHP 8.3 has new feature and tries to detect the recursion. This feature likely doesn't work correctly with the gRPC PHP extension.
The workaround is to disable stack size checks in the PHP INI settings:
zend.max_allowed_stack_size: -1.
We also encounter this issue, incidentally. The possibility of disabling the stack size check, and avoiding the occurrence of the Error, does not mean the code works properly/efficiently. The comment in CredentialsWrapper::getAuthorizationHeaderCallback, where our infinite recursion errors originate, also suggests awareness about error-prone logic.
Happens thousands of times per day on my side.
Same issue here, happens with gax 1.34.[01] on php 8.3 with swoole/grpc.
Still an issue with gax 1.35.0 on php 8.3
We worked around this by wrapping the call in
try {
}
catch (\Throwable $exception) {
}
This issue Segmentation Fault in 1.68.0 for PHP 8.3 states: Works with grpc 1.66 and that seems to be the case. So if you have the possibility to (downgrade to) using 1.66.0 it can also be used as a workaround
We worked around this by wrapping the call in
try { } catch (\Throwable $exception) { }
Which call
Which call
We have done something like this:
// Execute API query and return the result.
try {
$client = $this->googleQueryFactory->getClient();
$response = $client->runReport($report_parameters);
}
catch (\Throwable $exception) {
}
It doesn't actually fix the issue, but at least allows loading the rest of the page.
I ran into the Maximum call stack size exceeded error in getAuthorizationHeaderCallback, which seemed to be caused by recursion. I changed the code to execute the callback function directly instead of returning it as a closure, and it resolved the issue:
Original
return function () use ($audience) {
// Token fetching and validation logic
};
Modified
$callback_fn = function () use ($audience) {
// Token fetching and validation logic
};
return $callback_fn();
~This worked for my case and seems to have fixed the issue. I’m considering making a pull request, but I’m not entirely sure if this is the right approach. I haven’t done any performance benchmarking or tested it thoroughly in other scenarios.~
~Is there a reason the closure is necessary?~
$callback_fn = function () use ($audience) { // Token fetching and validation logic }; return $callback_fn();
This looks like you may just run the "Token fetching and validation logic" outside of the callback and return its result without using callback at all.
This looks like you may just run the "Token fetching and validation logic" outside of the callback and return its result without using callback at all.
You're absolutely right. Previously, I was working on a different project that didn’t require authentication, so it seemed fine at the time.
We've now fixed it by adding a retry mechanism in case of a Throwable error:
$maxRetries = 3;
$attempt = 0;
$delay = 100000; // Initial delay in microseconds (100ms)
while ($attempt < $maxRetries) {
try {
$attempt++;
// Execute the operation that may fail
} catch (\Throwable $e) {
if ($attempt >= $maxRetries) {
throw $e; // Re-throw the exception after max retries
}
var_dump('Retrying... Attempt: ' . $attempt);
usleep($delay); // Wait before retrying
$delay *= 2; // Exponential backoff
}
}
This ensures that if an error occurs, the request is retried up to three times with an exponential backoff before failing completely.
I found the solution Friends!!!!!!!! (google ads php)
return (new GoogleAdsClientBuilder()) ->withOAuth2Credential($oAuth2Credential) ->fromFile(storage_path('google/google_ads_php.ini')) ->withTransport('rest') ->build();
you need add "->withTransport('rest') "
haizadvnet Could you clarify where you added the retry mechanism you outlined?
haizadvnet Could you clarify where you added the retry mechanism you outlined?
You may try adding this to your function that interacts with gax-php, as it handles API calls for various Google Cloud services. In my case, it's Firestore using gRPC.
Example:
$maxRetries = 3;
$attempt = 0;
$delay = 100000;
while ($attempt < $maxRetries) {
try {
$attempt++;
return $this->firestore->collection($this->collectionName)->document($document)->set(...);
} catch (\Throwable $e) {
if ($attempt >= $maxRetries) {
throw $e; // Re-throw the exception after max retries
}
usleep($delay); // Wait before retrying
$delay *= 2; // Exponential backoff
}
}
Any plans to fix it for PHP 8.3 - 8.4?
@krystianlaubach I've posted the issue to the grpc repo here: https://github.com/grpc/grpc/issues/39509
Will try to take a look. In the meantime, has anyone tried this with the latest gRPC version (at this time, 1.72)?
I found the solution Friends!!!!!!!! (google ads php) you need add "->withTransport('rest') "
This is not a solution, but a workaround. You're using REST instead of gRPC. There's nothing wrong with doing that, but it isn't a proper solution.
We've now fixed it by adding a retry mechanism in case of a Throwable error
Again not a fix, but a workaround. You're now catching the error and retrying it, and as it's intermittent, it seems to work.
I'll keep investigating here.
grpc/grpc#39509
Yes I have on Acquia with php8.3 and I get this error.
Getting the same error with the Firebase SDK and Firestore using gRPC.
Fix would be great!
EDIT: workaround with setting the zend.max_allowed_stack_size to -1 works but I'd rather not do this
google can only add code with their AI and cant fix code, they are probably waiting for real people from the community fixing it
@bshaffer I know it's a workaround, but https://github.com/googleapis/gax-php/pull/632 would be helpful in this situation. gRPC is notoriously problematic in dev environments (usually after an update).
posted analysis on https://github.com/grpc/grpc/issues/39509 We might need to fix usage of the authhttphandler , see analysis for details