Problem/Motivation

Under certain circumstances, it is relatively easy to cause cache polution for options_allowed_values's drupal_static.

Steps to reproduce

In custom entity:

  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    $fields = parent::baseFieldDefinitions($entity_type);

    $fields['options_list'] = BaseFieldDefinition::create('list_string')
      ->setLabel(t('Options'))
      ->setSettings([
        'allowed_values' => ['foo' => t('Foo'), 'bar' => t('Bar')],
      ]);

  }

  public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
    if ($bundle === 'baz') {
      $fields['options_list'] = clone $base_field_definitions['options_list'];
      $fields['options_list']->setSettings([
        'allowed_values' => ['baz' => t('Baz')],
      ]);
      return $fields;
    }

    return [];
  }

Then bulk insert a bunch of entities where some have a bundle of baz and the rest are foobar. Once the foobar bundled entities are validated/saved, any attempts to perform entity validation on baz bundle entities will fail because they are passing an option value of 'baz' and the previous list of ['foo', 'bar'] was already statically cached by drupal_static().

Proposed resolution

Add the target bundle to the cache keys.

Remaining tasks

Tests

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3161345

Command icon 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

heddn created an issue. See original summary.

heddn’s picture

Status: Active » Needs review
Issue tags: +Needs tests
StatusFileSize
new535 bytes
heddn’s picture

Title: options_allowed_values cache polution » options_allowed_values() cache pollution

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

stefan.korn’s picture

I would absolutely support this.

There is another case where missing bundle can hit back.

If you use the allowed_values_function callback and try to distinct options by entity bundle, you might get in trouble when using paragraphs. If you have two (or more) paragraph entities with different bundle but same field, the caching will hit and you will get the allowed values for the first entity on subsequent entities.

This could be fixed by adding the entity bundle to the cache key.

Though patch from #2 seems not valid for this. if condition is wrong and $definition->getTargetBundle() does not exist for FieldStorageDefinitionInterface as far as I can see.

Providing a different patch to solve this.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

berdir’s picture

Hm. bundle field definitions are tricky. allowed values is defined as a storage level setting, it's not expected to vary between bundles, you can't do that with configurable fields. And I'm not sure if you can rely on always being called on the bundle specific definition, but these days, it might be I guess.

heddn’s picture

I ran into this with code defined fields and migrating data. The entity validation would "randomly" start to fail. Root cause we tracked down to the first loaded field definition would break the other bundles. This wasn't an issue for non migration use cases, since we didn't typically load and save multiple entities in the same PHP memory space. But with migrate, it was relatively easy to see happen.

As far as allowed values being storage or not, that was not my decision to hack the fields that way. But except for migrate, it did work and works pretty well.

berdir’s picture

Yeah, I get that, but there are cases where that's not going to work. The function receives a field storage definition, and if you look at e.g. the ListField views filter plugin usage, that won't be able to get the bundle specific definition obviously, so your exposed views filter isn't going to work (I think views list field filters are broken anyway, but that's a detail ;)).

The documentation for the custom callback is also quite clear on "restrict the options or customize labels". The entity storage level needs to return all allowed values, and it may only be allowed to dynamically have a subset of those values for a specific entity.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

joachim’s picture

> The entity storage level needs to return all allowed values, and it may only be allowed to dynamically have a subset of those values for a specific entity.

So is the fix that the example code in the IS should be causing an exception to be thrown by the field system?

stefan.korn’s picture

The documentation of options_allowed_values says about $entity:

... This allows custom 'allowed_values_function' callbacks to either restrict the values or customize the labels for particular bundles and entities.

So patch from #5 seems legitimate and afaik necessary to be able to use this for bundles. If that's not true, then at least the documentation is wrong.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Needs review » Needs work

Moving back for the tests.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

vidorado made their first commit to this issue’s fork.

vidorado’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests

Created a MR, applied the patch from #5 and added a kernel test.

oily made their first commit to this issue’s fork.

ironnuts’s picture

Updated the test, added comments to clarify each step.

ironnuts’s picture

All pipeline tests are green. Except test-only test which fails as it should.

ironnuts’s picture

Added comments to the test in the MR.

smustgrave’s picture

Status: Needs review » Needs work

Added some additional comments after doing some code standard reviews. I didn't resolve any of the open threads but if we can do that next please.

vidorado’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative

Small comments on MR>

vidorado’s picture

Status: Needs work » Needs review

@smustgrave I've replied to your comments.

Thanks for the review!

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Feedback appears to be addressed.

Thanks!

catch’s picture

Status: Reviewed & tested by the community » Needs work

@berdir's points in #7 and #9 still haven't been adequately answered. Should the cache key instead use the entity ID?

heddn’s picture

From re-reviewing the docs on options_allowed_values, I think we need to cache it on both, yes. Leaving at NW for this.

vidorado’s picture

About Berdir's comments above

I believe they have been taken into account. We now understand that we can only add a callback function to a bundle definition to restrict the options or customize the labels, but the allowed_values setting must not be overridden.

Regarding this, I think the only unanswered comment is #11, which asks what would happen if we did override it.

I tested this scenario, and it turns out that it is possible. I created a custom test entity with an options_field that has a default allowed_values setting of ['Foo', 'Bar'], two bundles, and an overridden allowed_values setting of ['Baz'] for the second bundle. I was able to create entities for both bundles, and the values in the options_values field dropdown were displayed as configured, with no errors thrown.

I'm not sure what action, if any, we should take regarding this.

About adding the entity ID to the cache ID

This is a separate question, and I assume you're suggesting adding the entity ID to the cache key because the allowed_values callback function receives the entity as a parameter, meaning it could depend on any part of the entity, not just the bundle.

In that case, I agree that we should add the entity ID to the cache key. It's unfortunate, as this will result in a large number of cache entries—one per entity—but I don't see another way of handling it since the callback function does not return any cacheability metadata.

Please confirm that we're all on the same page, regarding to this, and I'll proceed with adding the entity ID to the cache key.

Thanks!

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

smustgrave’s picture

Will need to rebase but @berdir does #31 answer your questions?

nicxvan’s picture

We just refactored this it would be good to see if it fails on main still.

https://git.drupalcode.org/project/drupal/-/blob/main/core/modules/optio...

At the very least we can add the test coverage.

smustgrave’s picture

Status: Needs work » Needs review

Think still could use the new cache tag? Tests did fail

nicxvan’s picture

Title: options_allowed_values() cache pollution » OptionsAllowedValues has cache pollution

I think it makes sense, can you run the test only?

smustgrave’s picture

Unfortunately no don't have permissions :(

joachim’s picture

> In that case, I agree that we should add the entity ID to the cache key. It's unfortunate, as this will result in a large number of cache entries—one per entity—but I don't see another way of handling it since the callback function does not return any cacheability metadata.

Now that we have the variation cache, could that be used here, so that we only have caches per-entity ID when needed?

smustgrave’s picture

Status: Needs review » Needs work

May be worth exploring. Sounds like this one definitely needs more work.