Problem/Motivation
ViewsData::getData() sets $this->fullyLoaded = TRUE at the beginning of the method (line 221), before data is actually retrieved from cache or rebuilt. This causes a race condition when PHP Fibers are involved.
When a Fiber suspension occurs during hook_views_data() invocation (e.g., due to lazy loading, BigPipe, or other async operations), another Fiber can observe:
fullyLoaded = TRUEallStorage = [](empty, because the first Fiber hasn't completed yet)
This causes the second Fiber to write an empty cache entry for table-specific views data, which persists and breaks Views functionality for subsequent requests.
This is the same pattern that was fixed in #3553342 (LocalTaskManager Fiber race condition) for Drupal 11.3.2.
Steps to reproduce
- Set up a multilingual Drupal site with Views using Search API (or any module providing views data via hooks)
- Clear all caches:
drush cr - Load a page in language A (e.g., English) that triggers Views data loading
- Load a page in language B (e.g., German) that uses a View with exposed filters
- The exposed filter block may disappear or show "broken" handlers
Cache state after reproduction:
views_data:en (main) → HAS data ✓ views_data:de (main) → HAS data ✓ views_data:search_api_index_[name]:en → HAS data ✓ views_data:search_api_index_[name]:de → EMPTY (0 keys) ✗
Debug evidence:
Adding debug logging to ViewsData::get() shows:
WRITING EMPTY CACHE for search_api_index_[name]:
serviceLang=de, currentLang=de, fullyLoaded=TRUE, allStorageKeys=(empty)
This "impossible" state (fullyLoaded=TRUE but allStorage empty) occurs because fullyLoaded is set before data is ready, and a Fiber suspension allows concurrent code to see this inconsistent state.
Proposed resolution
Move $this->fullyLoaded = TRUE to after data is obtained, in both code paths (cache hit and cache miss/rebuild).
Before (buggy):
protected function getData() {
$this->fullyLoaded = TRUE; // BUG: Set before data is ready
if ($data = $this->cacheGet($this->baseCid)) {
return $data->data;
}
else {
// ... rebuild data ...
return $data;
}
}
After (fixed):
protected function getData() {
// Do NOT set fullyLoaded here - causes Fiber race condition
if ($data = $this->cacheGet($this->baseCid)) {
$this->fullyLoaded = TRUE; // Set AFTER data is obtained
return $data->data;
}
else {
// ... rebuild data ...
$this->cacheSet($this->baseCid, $data);
$this->fullyLoaded = TRUE; // Set AFTER data is rebuilt
return $data;
}
}
This follows the same fix pattern applied in #3553342 for LocalTaskManager.
Remaining tasks
- Review the proposed fix
- Add test coverage for the Fiber race condition scenario
- Verify fix doesn't introduce performance regression
- Backport consideration for 10.4.x if applicable
User interface changes
None. This is a bug fix that restores expected behavior.
Introduced terminology
None.
API changes
None. The public API of ViewsData remains unchanged.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #2 | debug_traces_views.png | 203.12 KB | eduardo morales alberti |
Issue fork drupal-3569624
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
eduardo morales albertiCapture showing the bug:

When it tries to load the cid "views_data:search_api_index_global_search:de", it returns $data as false, but the property fullyLoaded is true, so it does not try to load the data again. Because allStorage is empty, it sets the cache related to the cid to empty.
Comment #4
eduardo morales albertiComment #5
eduardo morales albertiCreated MR
Comment #6
eduardo morales albertiAdded tests:
The fullyLoaded flag is used by two public methods (get() and getAll()) that can be called in any combination:
Scenario 1: get() + get() → Test 1
Scenario 2: getAll() + getAll() → Test 2
Scenario 3: get() + getAll() → Test 3
Comment #7
eduardo morales albertiComment #8
godotislateNice work!
Setting to NW for spellcheck job failures, see MR suggestions.
Also add comments on the MR to remove custom assert messages, which sometimes is asked for by committers.
Once tests are green, the test only job should also be run so that the failures can be seen.
Comment #9
eduardo morales albertiAll suggestions approved!
Let's wait for MR
Comment #10
eduardo morales albertiIt is failing, but should not be related
Comment #11
eduardo morales albertiAfter running the tests that failed, it stabilized the MR
Comment #12
godotislateCan you run the Test only job to confirm it fails?
Comment #13
eduardo morales albertiThis issue is duplicated @godotislate https://www.drupal.org/project/drupal/issues/3565020#comment-16442537