-
Notifications
You must be signed in to change notification settings - Fork 714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CPU spikes in prod every 5 minutes with Smarty v5 #1007
Comments
I'm afraid I don't have any clue. There is no background processing taking place at all. The only way forward I see is to migrate forward in smaller steps to see what commit in Smarty triggers this. Or add some kind of profiling or strace to see what the CPU is doing. |
Some other thoughts:
In general: it would be very useful to have some way to replicate the traffic level in production, e.g. by using some kind of replay of access logs against a testing environment. That would allow you to use the steps I suggested above and see what happens without disrupting your production env. |
I'm thinking we'll take a shot at using https://github.com/reliforp/reli-prof to see what the heavy requests are doing. |
I checked the compiled template timestamps a couple times thinking maybe the issue was regular recompilation. The compiled template file timestamps weren't regularly updated once compiled by v5. |
That's interesting. We also have For the record, if it's useful at all, here's our opcache config:
|
@nikitanp do you think these constantly recompiled cache files are a symptom of an underlying error in Smarty? |
@wisskid Firstly thanks for the reply and for your maintaining such a huge library. I suspect some moments:
// runs in a factory method while creating a Template object
private function addModuleResource(Module $module): void
{
$this->registerResource(
$module->getName(),
new class ($module) extends CustomPlugin {
private Module $module;
public function __construct(Module $module)
{
$this->module = $module;
}
protected function fetch($name, &$source, &$mtime): void
{
$path = $this->module->getViewsPath().$name;
if (file_exists($path) && is_file($path)) {
$source = file_get_contents($path);
}
}
protected function fetchTimestamp($name): bool|int
{
$path = $this->module->getViewsPath().$name;
if (file_exists($path) && is_file($path)) {
return filemtime($path);
}
return false;
}
}
);
}
public function fetch($template = null, $cache_id = null, $compile_id = null, $parent = null)
{
if ($template && !containsSubstring($template, ':')) {
$pathData = $this->getLegacyTemplatePaths($template);
$template = $pathData['path'];
$this->addTemplateDir($pathData['folder']);
}
return parent::fetch($template);
} |
So, we have @nikitanp saying that "some of the compiled cache files (not all of them!) were constantly recompiled". This suggests that we are looking for a bug where a template is recompiled (i.e. new code written to a compiled file in the templates_c folder, or the timestamp of this file being renewed) while the source file has not changed. But @jdpanderson states "I checked the compiled template timestamps a couple times thinking maybe the issue was regular recompilation. The compiled template file timestamps weren't regularly updated once compiled by v5." So at least in his case this does not seem to be the issue. Or maybe we have two different bugs with similar symptoms. In any case, it would be very helpful if you could come up with reproduction scenarios. |
@jdpanderson @nikitanp @wisskid We upgraded from v4 to v5 and also have a problem. When we run performance test by jmeter, the app server is down after running with ~400 users (total of 600 users for the daily test scenario), and there're a lot of "Too Many Requests" error at that moment. Additional information: A major change when upgrading is that we wrapped all our custom plugins (we wrote a lot of it) into a wrapper Extension. But I don't think it's the case, because we checked if the cached plugin exists before calling it:
and then we add the extension when creating Smarty instance:
|
@tungps250991 Unfortunately I haven't had enough time for investigation. We also used an extension class... and we had a lot of stuff in that extension. I can't share everything, but here's the extension's code. <?php
declare(strict_types=1);
use Smarty\BlockHandler\BlockHandlerInterface;
use Smarty\Extension\Base;
use Smarty\FunctionHandler\FunctionHandlerInterface;
final class SmartyExtension extends Base
{
public function getModifierCallback(string $modifierName): ?callable
{
return match ($modifierName) {
// PHP native functions
'json_encode' => 'json_encode',
'htmlspecialchars' => 'htmlspecialchars',
'count' => 'count',
'is_null' => 'is_null',
'intval' => 'intval',
'date' => 'date',
'range' => 'range',
'strtotime' => 'strtotime',
'constant' => 'constant',
'floor' => 'floor',
'html_entity_decode' => 'html_entity_decode',
// Custom
'account_date_format' => new AccountDateFormatModifierCallback(),
'account_number_format' => new AccountNumberFormatModifierCallback(),
'date_format' => new DateFormatModifierCallback(),
'date_lang_format' => new DateLangFormatModifierCallback(),
'date_locale_format' => new DateLocaleFormatModifierCallback(),
'lower' => new LowerModifierCallback(),
'mb_truncate' => new MbTruncateModifierCallback(),
'number_reduction' => new NumberReductionModifierCallback(),
'pretty_url' => new PrettyUrlModifierCallback(),
'reduction' => new ReductionModifierCallback(),
'remove_http' => new RemoveHttpModifierCallback(),
'to_array' => new ToArrayModifierCallback(),
'ucfirst' => new UCFirstModifierCallback(),
'unescape' => new UnescapeModifierCallback(),
default => null
};
}
public function getFunctionHandler(string $functionName): ?FunctionHandlerInterface
{
return match ($functionName) {
'action' => new ActionFunctionHandler(),
'get_dictionary' => new GetDictionaryFunctionHandler(),
'get_front_assets' => new GetFrontAssetsFunctionHandler(),
'html_select_options' => new HtmlSelectOptionsFunctionHandler(),
'legacy_pager' => new LegacyPagerFunctionHandler(),
'message' => new MessageFunctionHandler(),
'money_format' => new MoneyFormatFunctionHandler(),
'pager' => new PagerFunctionHandler(),
't' => new TFunctionHandler(),
'url' => new UrlFunctionHandler(),
'hasAccess' => new HasAccessFunctionHandler(),
default => null
};
}
public function getBlockHandler(string $blockTagName): ?BlockHandlerInterface
{
return match ($blockTagName) {
'block_include' => new BlockIncludeBlockHandler(),
'has_access' => new HasAccessBlockHandler(),
'has_access_to_group' => new HasAccessToGroupBlockHandler(),
't_namespace' => new TNamespaceBlockHandler(),
default => null
};
}
} |
Since there is still no reproduction scenario, I can't tell if the following suggestion will make any difference, but I've implemented returning the handlers from the Extension as follows: See if (isset($this->functionHandlers[$functionName])) {
return $this->functionHandlers[$functionName];
}
switch ($functionName) {
case 'count': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Count(); break;
case 'counter': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Counter(); break;
case 'cycle': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Cycle(); break;
case 'fetch': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Fetch(); break;
case 'html_checkboxes': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlCheckboxes(); break;
case 'html_image': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlImage(); break;
case 'html_options': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlOptions(); break;
case 'html_radios': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlRadios(); break;
case 'html_select_date': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlSelectDate(); break;
case 'html_select_time': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlSelectTime(); break;
case 'html_table': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\HtmlTable(); break;
case 'mailto': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Mailto(); break;
case 'math': $this->functionHandlers[$functionName] = new \Smarty\FunctionHandler\Math(); break;
}
return $this->functionHandlers[$functionName] ?? null; This will limit PHP garbage collection for new objects. Maybe it is related. |
We don't use any extensions, but we do use "plugins" to declare some functions & modifiers. I listed those in the top comment of the issue. Did the implementation of |
No, that should still be very fast array lookups. |
@wisskid @nikitanp
And in CustomSmartyFunction:
I think this seems to make almost no difference with your code. Doesn't it? |
Same problem here: After upgrading 4.x to 5, Production Server startet to have OOM issues. I've seen that all included compiled templates are invalidated on every call. Since it is forced there, file is always removed and added again to the opcache. And as the memory of removed files in the opcache isn't reused, this wasted memory raises.
|
…in fast raising wasted memory smarty-php#1007
…in fast raising wasted memory smarty-php#1007
…in fast raising wasted memory smarty-php#1007
…in fast raising wasted memory smarty-php#1007
…in fast raising wasted memory smarty-php#1007
…in fast raising wasted memory smarty-php#1007
…s resulting in fast raising wasted OpCache memory #1007 (#1047) * Fixing forced OpCache Invalidation on every call, which is resulting in fast raising wasted memory * Fix undefined $path variable warning --------- Co-authored-by: Daniel Metzner <[email protected]>
Great news! Thank you @stephanlueckl, we'll try the fix sometime next week. Reading the PR, definitely sounds like it aligns with what we were experiencing too. |
Due to a myriad of factors, we never got around to trying the fix until today. We can confirm it's resolved for us! Thanks everyone! We're on v5 now. |
Last week, we tried deploying an update to our prod servers upgrading from Smarty v4.3 to v5.1. Soon after, we started seeing patterns in our CPU graphs showing 100% CPU spikes (for about 5-10 seconds at a time) about every 5 minutes.
We then reverted the change, confirmed that the Smarty version change was the problem. Then, we tried upgrading to v4.4.1 then v4.5.2, to see if those exhibited the behaviour, but they didn't.
Our setup for Smarty is very, very simple. We're not using much in terms of config. It just looks like this (we're running on PHP 8.1 currently):
We're not seeing anything in the release notes that would suggest why there would be somekind of scheduled processing. We of course cleared out our compile dir when deploying.
We're certain that the problem is somewhere between v4.5.2 and v5.1.0, because we tried switching between them a couple of times and were able to replicate seeing CPU spikes. Unfortunately we don't have any kind of production profiling setup at the moment and we can only replicate it in prod due to the traffic levels, so we're not sure how else to dig into it.
Trying to scan through v4.5.2...v5.1.0 is difficult because we're not familiar enough with the codebase (also it's written in a very legacy style so it's hard to follow 😬)
I'd appreciate if you could give us any kind of leads.
The text was updated successfully, but these errors were encountered: