Skip to content

Commit c1a42f5

Browse files
committed
[BUGFIX] Don't cache TypoScript for pages with uncached content
Since change e84af96 ("[BUGFIX] Handle new TS-not-set limitation on v13 (#1928)") the "plugin.tx_vhs" section of the TypoScript setup is written into a cache. This is necessary as TYPO3 v13 PrepareTypoScriptFrontendRendering [1] no longer performs the full TypoScript setup when serving a fully-cached page. An "Setup array has not been initialized" error is thrown when trying to access the uninitialized TypoScript setup. For pages with uncached content, FrontendInterface::set() is called on each request. This leads to database deadlock errors if the Typo3DatabaseBackend is used for the "vhs_main" cache and many concurrent requests hit the same TYPO3 page. This issues is tracked in upstream TYPO3 bug tracker as #106593 [2]. A fully reproduction example is available on GitHub [3]. We don't need to store the TypoScript of those pages with uncached content in the cache, because they cannot be served from cache and thus forcing the full TypoScript setup. [1]: https://github.com/TYPO3/typo3/blob/v13.4.9/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php#L146 [2]: https://forge.typo3.org/issues/106593 [3]: https://github.com/adamkoppede/reproduce-deadlock-with-fluidtypo3-vhs-7-1
1 parent c4f6038 commit c1a42f5

1 file changed

Lines changed: 83 additions & 44 deletions

File tree

Classes/Service/AssetService.php

Lines changed: 83 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class AssetService implements SingletonInterface
5050

5151
protected static bool $typoScriptAssetsBuilt = false;
5252
protected static ?array $typoScriptCache = null;
53+
protected static array $pagesWithSavedTypoScript = [];
54+
protected static bool $currentlyBuildingCacheable = true;
5355
protected static array $cachedDependencies = [];
5456
protected static bool $cacheCleared = false;
5557

@@ -71,51 +73,59 @@ public function usePageCache(object $caller, bool $shouldUsePageCache): bool
7173

7274
public function buildAll(array $parameters, object $caller, bool $cached = true, ?string &$content = null): void
7375
{
74-
if ($content === null) {
75-
$content = &$caller->content;
76+
$wasBuildingCacheableBefore = static::$currentlyBuildingCacheable;
77+
if ($caller instanceof TypoScriptFrontendController && $caller->isINTincScript()) {
78+
static::$currentlyBuildingCacheable = false;
7679
}
80+
try {
81+
if ($content === null) {
82+
$content = &$caller->content;
83+
}
7784

78-
$settings = $this->getSettings();
79-
$buildTypoScriptAssets = (
80-
!static::$typoScriptAssetsBuilt
81-
&& ($cached || $this->readCacheDisabledInstructionFromContext())
82-
);
83-
if ($buildTypoScriptAssets && isset($settings['asset']) && is_array($settings['asset'])) {
84-
foreach ($settings['asset'] as $name => $typoScriptAsset) {
85-
if (!isset($GLOBALS['VhsAssets'][$name]) && is_array($typoScriptAsset)) {
86-
if (!isset($typoScriptAsset['name'])) {
87-
$typoScriptAsset['name'] = $name;
88-
}
89-
if (isset($typoScriptAsset['dependencies']) && !is_array($typoScriptAsset['dependencies'])) {
90-
$typoScriptAsset['dependencies'] = GeneralUtility::trimExplode(
91-
',',
92-
(string) $typoScriptAsset['dependencies'],
93-
true
94-
);
85+
$settings = $this->getSettings();
86+
$buildTypoScriptAssets = (
87+
!static::$typoScriptAssetsBuilt
88+
&& ($cached || $this->readCacheDisabledInstructionFromContext())
89+
);
90+
if ($buildTypoScriptAssets && isset($settings['asset']) && is_array($settings['asset'])) {
91+
foreach ($settings['asset'] as $name => $typoScriptAsset) {
92+
if (!isset($GLOBALS['VhsAssets'][$name]) && is_array($typoScriptAsset)) {
93+
if (!isset($typoScriptAsset['name'])) {
94+
$typoScriptAsset['name'] = $name;
95+
}
96+
if (isset($typoScriptAsset['dependencies']) && !is_array($typoScriptAsset['dependencies'])) {
97+
$typoScriptAsset['dependencies'] = GeneralUtility::trimExplode(
98+
',',
99+
(string) $typoScriptAsset['dependencies'],
100+
true
101+
);
102+
}
103+
Asset::createFromSettings($typoScriptAsset);
95104
}
96-
Asset::createFromSettings($typoScriptAsset);
97105
}
106+
static::$typoScriptAssetsBuilt = true;
98107
}
99-
static::$typoScriptAssetsBuilt = true;
100-
}
101-
if (empty($GLOBALS['VhsAssets']) || !is_array($GLOBALS['VhsAssets'])) {
102-
return;
103-
}
104-
$assets = $GLOBALS['VhsAssets'];
105-
$assets = $this->sortAssetsByDependency($assets);
106-
$assets = $this->manipulateAssetsByTypoScriptSettings($assets);
107-
$buildDebugRequested = (isset($settings['asset']['debugBuild']) && $settings['asset']['debugBuild'] > 0);
108-
$assetDebugRequested = (isset($settings['asset']['debug']) && $settings['asset']['debug'] > 0);
109-
$useDebugUtility = (isset($settings['asset']['useDebugUtility']) && $settings['asset']['useDebugUtility'] > 0)
110-
|| !isset($settings['asset']['useDebugUtility']);
111-
if ($buildDebugRequested || $assetDebugRequested) {
112-
if ($useDebugUtility) {
113-
DebuggerUtility::var_dump($assets);
114-
} else {
115-
echo var_export($assets, true);
108+
if (empty($GLOBALS['VhsAssets']) || !is_array($GLOBALS['VhsAssets'])) {
109+
return;
110+
}
111+
$assets = $GLOBALS['VhsAssets'];
112+
$assets = $this->sortAssetsByDependency($assets);
113+
$assets = $this->manipulateAssetsByTypoScriptSettings($assets);
114+
$buildDebugRequested = (isset($settings['asset']['debugBuild']) && $settings['asset']['debugBuild'] > 0);
115+
$assetDebugRequested = (isset($settings['asset']['debug']) && $settings['asset']['debug'] > 0);
116+
$useDebugUtility = (isset($settings['asset']['useDebugUtility']) && $settings['asset']['useDebugUtility'] > 0)
117+
|| !isset($settings['asset']['useDebugUtility']);
118+
if ($buildDebugRequested || $assetDebugRequested) {
119+
if ($useDebugUtility) {
120+
DebuggerUtility::var_dump($assets);
121+
} else {
122+
echo var_export($assets, true);
123+
}
116124
}
125+
$this->placeAssetsInHeaderAndFooter($assets, $cached, $content);
126+
} finally {
127+
static::$currentlyBuildingCacheable = $wasBuildingCacheableBefore;
117128
}
118-
$this->placeAssetsInHeaderAndFooter($assets, $cached, $content);
119129
}
120130

121131
public function buildAllUncached(array $parameters, object $caller, ?string &$content = null): void
@@ -151,26 +161,27 @@ public function getSettings(): array
151161
protected function getTypoScript(): array
152162
{
153163
if (static::$typoScriptCache !== null) {
164+
$this->saveTypoScriptIfNecessary(static::$typoScriptCache);
154165
return static::$typoScriptCache;
155166
}
156167

157-
$cache = $this->cacheManager->getCache('vhs_main');
158-
$pageUid = $this->readPageUidFromContext();
159-
$cacheId = 'vhs_asset_ts_' . $pageUid;
160-
$cacheTag = 'pageId_' . $pageUid;
161-
162168
try {
163169
$allTypoScript = $this->configurationManager->getConfiguration(
164170
ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT
165171
);
166172
$typoScript = GeneralUtility::removeDotsFromTS($allTypoScript['plugin.']['tx_vhs.'] ?? []);
167-
$cache->set($cacheId, $typoScript, [$cacheTag]);
173+
$this->saveTypoScriptIfNecessary($typoScript);
168174
} catch (\RuntimeException $exception) {
169175
if ($exception->getCode() !== 1666513645) {
170176
// Re-throw, but only if the exception is not the specific "Setup array has not been initialized" one.
171177
throw $exception;
172178
}
173179

180+
$pageUid = $this->readPageUidFromContext();
181+
$cache = $this->cacheManager->getCache('vhs_main');
182+
$cacheId = 'vhs_asset_ts_' . $pageUid;
183+
$cacheTag = 'pageId_' . $pageUid;
184+
174185
// Note: this case will only ever be entered on TYPO3v13 and above. Earlier versions will consistently
175186
// produce the necessary TS array from ConfigurationManager - and will not raise the specified exception.
176187

@@ -196,6 +207,34 @@ protected function getTypoScript(): array
196207
return $typoScript;
197208
}
198209

210+
/**
211+
* Save page TypoScript if required to prevent the
212+
* "Setup array has not been initialized" error when serving the next
213+
* request from cache.
214+
*/
215+
private function saveTypoScriptIfNecessary(array $typoScript): void
216+
{
217+
// We don't need to save the TypoScript if we're rendering uncached
218+
// content, because the next request won't be answered from cache as no
219+
// cache entry will be created.
220+
if (!static::$currentlyBuildingCacheable) {
221+
return;
222+
}
223+
224+
$pageUid = $this->readPageUidFromContext();
225+
226+
// We don't need to save the TypoScript if we already did.
227+
if (in_array($pageUid, static::$pagesWithSavedTypoScript, true)) {
228+
return;
229+
}
230+
231+
$cache = $this->cacheManager->getCache('vhs_main');
232+
$cacheId = 'vhs_asset_ts_' . $pageUid;
233+
$cacheTag = 'pageId_' . $pageUid;
234+
$cache->set($cacheId, $typoScript, [$cacheTag]);
235+
static::$pagesWithSavedTypoScript[] = $pageUid;
236+
}
237+
199238
/**
200239
* @param AssetInterface[]|array[] $assets
201240
*/

0 commit comments

Comments
 (0)