Problem/Motivation
When entity is being saved, a well-timed load for given entity will cause old version of it to be cached.
This can (and likely will) cause data loss.
There seems to be several tickets made that describes the behavior how I met this issue, and more real-life like scenario might contain something like messages with tokens that will eventually cause this bug to happen.
Steps to reproduce
Complete version using user entity as example
- Create new user, should be ID 2 with vanilla standard installation
- Have one terminal open running
drush scr bugsaver.php
- Have few terminals open running
drush scr bugloader.php
- even one instance will eventually find the perfect timing but it's significantly faster to run 3 to 6 instances
- Once the saver script prints out the mismatch, feel free to close the loader scripts
- Open the edit form for the new user as admin - the user name is now old
- (Option A) To "fix" the entity, clear caches & refresh the page
drush cr
- (Option B) Add a role to the user and save - the new user name is now fully gone
Sample snippet for saving
// bugsaver.php
$entityId = 2;
$entityType = 'user';
$field = 'name';
$entityTypeManager = \Drupal::entityTypeManager();
$uuidGenerator = \Drupal::service('uuid');
$entityStorage = $entityTypeManager->getStorage($entityType);
$entity = $entityStorage->load($entityId);
$current = $match = $uuidGenerator->generate();
$entity->set($field, $current)->save();
$entity->save();
while (TRUE) {
$entityStorage = $entityTypeManager->getStorage($entityType);
$entity = $entityStorage->load($entityId);
$current = $entity->get($field)->value;
if ($current != $match) {
echo "uh oh. {$current} != {$match}" . PHP_EOL;
exit;
}
$match = $uuidGenerator->generate();
$entity->set($field, $match)->save();
}
Sample snippet for loading
$entityId = 2;
$entityType = 'user';
$entityTypeManager = \Drupal::entityTypeManager();
while (TRUE) {
$entityStorage = $entityTypeManager->getStorage($entityType);
$entity = $entityStorage->load($entityId);
}
Proposed resolution
TBD
- Semaphore to prevent entity being loaded while it is being saved
- State that prevents entity being cached while it is being saved
- ????
Remaining tasks
TBD
User interface changes
TBD
Introduced terminology
TBD
API changes
TBD
Data model changes
TBD
Release notes snippet
TBD