Problem/Motivation
Currently, the actual store used to provide the expirable KV storage is supposed to be defined by the factory.keyvalue.expirable
parameter, which defaults to default: keyvalue.expirable.database
.
However, this fallback is duplicated in KeyValueFactory::get()
, which does this:
if (!isset($this->stores[$collection])) {
if (isset($this->options[$collection])) {
$service_id = $this->options[$collection];
}
elseif (isset($this->options[static::DEFAULT_SETTING])) { // 'keyvalue_expirable_default', not 'default'
$service_id = $this->options[static::DEFAULT_SETTING];
}
else {
$service_id = static::DEFAULT_SERVICE;
}
As a result, if a specific store is not provided when trying to instantiate an expirable KV store, the first and second checks fail because the default parameter is called default
in core.services.yml
but used as keyvalue_expirable_default
in code, so the logic falls back to the value of KeyValueExpirableFactory::DEFAULT_SERVICE
.
AFAICS, we have five use cases in core for this: form_cache
(see #2183275: Use cache for $form, kv for $form_state), update.manager
, update.manager
, update.processor
, user.private_tempstore
and user.shared_tempstore
.
There are two possible fixes: the simplest one is to change the factory.keyvalue.expirable
section in default.services.yml
, the better one is to fix that logic to use default
.
Steps to reproduce
TBD
Proposed resolution
TBD
Remaining tasks
Determine if this is a fix we want to add
If yes determine best patch forward
BC layer + tests
Code review
If no close as works as designed
User interface changes
TBD
API changes
TBD
Data model changes
TBD
Release notes snippet
TBD
Comment | File | Size | Author |
---|---|---|---|
#28 | drupal-2769955-1100-28-keyvalue-factory.patch | 3.62 KB | Sweetchuck |
#3 | 2769955-keyvalue_expirable.patch | 1.3 KB | fgm |
Comments
Comment #2
fgmIn addition, it seems that the
SPECIFIC_PREFIX
constant defined in both factories is never used as described.Comment #3
fgmLet's see what happens when removing this.
Comment #6
jhominal CreditAttribution: jhominal as a volunteer commentedWhile I agree that replacing the
DEFAULT_SETTING
key from'keyvalue_expirable_default'
to'default'
is the logical long-term fix, what are the backwards compatibility constraints on minor Drupal releases?I mean, there could be existing installations that override
factory.keyvalue.expirable
by supplyingkeyvalue_expirable_default
- changing the name of that key would "break" these installations (by forcing them to immediately update their services.yml file).Is that an acceptable breakage in 8.x?
Comment #9
DuaelFrAs this key is not/wrongly documented in default.services.yml and misused in core.services.yml. I don't think a lot of people are relying on it.
For consistency reasons, I think we should replace the default key by
default
instead ofkeyvalue_expirable_default
.If we really want to maintain BC, we can still override the
\Drupal\Core\KeyValueStore\KeyValueFactory::get()
method to look for thekeyvalue_expirable_default
key if there is nothing defined under thedefault
key.Comment #10
pguillard CreditAttribution: pguillard commentedI guess this is RTBC :
- SPECIFIC_PREFIX not found elsewhere in core code
- This is what I get on a page load before and after patch
$collection | $service_id BEFORE PATCH | $service_id AFTER PATCH
______________________________________________________________________________
state | keyvalue.database | keyvalue.database
user.private_tempstore.views_bulk_operations_content_page_1 | keyvalue.expirable.database | keyvalue.expirable.database
config.entity.key_store.block | keyvalue.database | keyvalue.database
update_available_releases | keyvalue.expirable.database | keyvalue.expirable.database
update | keyvalue.expirable.database | keyvalue.expirable.database
update_fetch_task | keyvalue.database | keyvalue.database
config.entity.key_store.tour | keyvalue.database | keyvalue.database
Comment #11
DuaelFrI'd feel more confident if we added the BC layer even if it's not likely to be used anywhere.
Comment #18
TR CreditAttribution: TR commentedI think the fix needs to go a little further, because currently
KeyValueExpirableFactory
can't be used to create an expirable store - it only creates non-expirableKeyValueStore
stores.For example, in core, if you do this:
drupal-check -a modules/update/src/UpdateManager.php
You will see (among other things):
I encountered this while working on Honeypot. Honeypot has this code:
Which looks reasonable. This code tries to:
\Drupal::service('keyvalue.expirable')
setWithExpire()
on thatKeyValueStoreExpirableInterface
But this doesn't work because
KeyValueExpirableFactory::get
does NOT returnKeyValueStoreExpirableInterface
as expected, it returns a non-expirableKeyValueStoreInterface
which does not support thesetWithExpire()
method.Note that if you use
keyvalue.expirable.database
instead (implemented by KeyValueDatabaseExpirableFactory), then this all DOES work, becauseKeyValueDatabaseExpirableFactory::get
returns the expectedKeyValueStoreExpirableInterface
.Comment #19
BerdirThis is definitely working, otherwise core wouldn't function. I think drupal-check is just confused about the trickery that's going on with the subclass and the extra interface that overrides the return type.
This issue is about the fact that you can't change the returned store to use a non-database implementation based on how it is documented.
Comment #20
TR CreditAttribution: TR commentedI linked as a related issue the original issue which removed the use of (but not definition of)
SPECIFIC_PREFIX
and addedfactory.keyvalue.expirable
as a service parameter.Yeah you're right that my comments aren't directly related, but they're an indication that this 'trickery' you referred to is fragile and could be broken by this patch.
Specifically, right now the
factory.keyvalue.expirable
parameter (which iskeyvalue.expirable.database
) is being ignored, and as long as no-one tries to override that in core.services.yml then things still work as expected because the default value in core.services.yml is the same value as the fallback DEFAULT_SERVICE, which is alsokeyvalue.expirable.database
.I assume you mean the 'trickery' here is that the
keyvalue.expirable.database
service will provide an expirable store with an interfaceKeyValueStoreExpirableInterface
, so that callingsetWithExpire()
will work even though the return type declaration ofKeyValueFactory::get
is technically the non-expirableKeyValueStoreInterface
.That's all well and good, and will currently always work because any override of that default service parameter is currently ignored.
But if you fix this issue so that the service parameter CAN be overridden, then there's nothing that guarantees that an expirable store will be returned from the overridden factory, so there's no guarantee that the
setWithExpire()
method will be available. And if it's not available, core breaks.Static analysis is not really being fooled, it's just pointing out a problem where the return type is not guaranteed to be
KeyValueStoreExpirableInterface
therefore there is no contract and no promise thatsetWithExpire()
method will be available.Comment #21
andypostNot sure if it's related but keyvalue storage test is what causes segfault for core+php8.1+sqlite in CI https://dispatcher.drupalci.org/job/drupal8_core_regression_tests/45252/...
Comment #22
andyposthttps://www.drupal.org/pift-ci-job/2225773
Comment #26
smustgrave CreditAttribution: smustgrave at Mobomo commentedThis issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.
Reading the comments seems there is still work/discussion to be had.
Updated the issue summary to include missing sections of the template.
Comment #28
SweetchuckI am not sure about the comments and the deprecation message.