Problem/Motivation
Classes that need to rebuild the container must interact with phpstorage() directly. This unnecessarily couples them to the implementation of the compiled container.
Proposed resolution
- Ensure that container storage is completely independent from any other things.
Remaining tasks
- Port patch from #2497243: Replace Symfony container with a Drupal one, stored in cache
User interface changes
- None
API changes
- API addition:
\Drupal::service('kernel')->invalidateContainer();
Data model changes
- None
Original report
There is no API for triggering a container rebuild. I propose that we add a new event to signal when the current container is invalid and a listener that performs whatever necessary to invalidate the container, so that it isn't used on subsequent requests.
Container rebuilds are mostly needed during module installs, where ModuleInstaller calls
$this->kernel->updateModules($module_filenames, $module_filenames);
Which leads to the kernel calling `save` on it's instance of PhpStorageInterface. The new container is created on the same request, and is used for the duration.
The kernel also has a `rebuildContainer` method, but from what I can tell, it's only ever called in the installer and in tests. It uses the same code path as before that saves and loads the new container in-request.
The only other change I know of that needs to trigger a container rebuild is changing the language, but I imagine there are or will be more.
Both ConfigurableLanguageManager and ConfigSubscriber do this (not sure why they both exists) by interacting directly with the php storage object for the service container:
PhpStorageFactory::get('service_container')->deleteAll();
In this case, the container is being deleted from disk to mark that it needs to be rebuilt on the next request. No attempt is made to rebuild it during the same request.
There are two different use cases here for needing to rebuild the container that are only defined by our current implementation. I'm not sure if both uses cases can be covered by a new event, but the latter case of invalidating the container to be rebuilt on the next request definitely can.
This would decouple any class that needs a rebuild from PhpStorageInterface, which is a very specific implementation detail of the current container, as well as provide a single place for the logic of how a container is invalidated.
This is especially important when considering issues like #2301163: Create a phpstorage backend that works with a local disk but coordinates across multiple webheads, where calling `deleteAll` on a php storage object doesn't invalidate the container, and a counter needs to be incremented instead.
Beta phase evaluation
| Issue category | Task, because nothing is really broken. (except for an edge case in updateModules that never was trigged in practice) |
|---|---|
| Issue priority | Major because the invalidation service needs to be swappable to support multiple webservers. Currently PhpStorage is hardcoded all over core, but in reality it is an internal implementation detail of the Kernel where and how the container is stored. |
| Prioritized changes | The main goal of this issue is scalability (preparation work), to reduce fragility (by isolating internals into the kernel) and to help a critical, which uses a different invalidation service. (database) |
| Disruption | Potentially disruptive (in the future) for drush or projects like registry_rebuild that relied on PhpStorage to delete the service container directly. Currently the storage does not change, so in this patch: Not disruptive, however contrib should maybe be informed of the new API? |
| Comment | File | Size | Author |
|---|---|---|---|
| #28 | add_centralized-2493665-28.patch | 11.88 KB | fabianx |
| #28 | interdiff.txt | 2.43 KB | fabianx |
Comments
Comment #1
fabianx commentedComment #2
fabianx commentedComment #3
fabianx commentedComment #4
fabianx commentedHere is the patch from that issue.
Comment #5
fabianx commentedUhm, #4 was fail as it should have used $this->storage() for now.
I checked that there are no more direct references to the PhpStorageFactory with the container directly.
Comment #6
dawehnerJust some quick feedback about the API.
Mh I'm not sure whether we really need a rebuild parameter. This feels optional for me. Can't you also always use $kernel->invalidateContainer() and then boot()? Splitting it in two steps is not a bad idea, IMHO.
Comment #7
fabianx commented#6: No, this is needed for being able to do core/rebuild.php.
Else this will quickly get really complicated, because without bootstrap container, how could you access the database or any other service to delete?
Or rather how can invalidateContainer() ensure that the container is invalidated, when settings might not yet be loaded.
Unless we want to do:
That could work. Thoughts?
Comment #10
fabianx commentedNew approach using a flag if the container needs to be rebuild on next initialization or not.
I think that should be more correct in general anyway.
e.g. updateModules() if ever called before boot (no one in core) would have been broken, because if there would have been a cached container it would have been used.
Comment #11
dawehnerThis looks better for me!
Comment #12
dawehnerFor now I think this is a sane step forward.
I'm not sure whether its a good idea to encourage more code to use the kernel directly instead of providing something in the middle which does the rebuilding, and is used by kernel.
Comment #13
alexpottIs the invalidate method testable using a unit test or a kernel test?
Why bother with the if?
Comment #14
alexpottForgot to say that this is way better than reaching into PhpStorage from the config system, +1 to the approach.
Comment #15
jibranCan we please have a beta eval as well?
Comment #16
msonnabaum commentedEdited the recent issue summary addition so that we're not talking about things in terms of right and wrong.
Comment #17
msonnabaum commentedAny reason the event idea wasn't implemented? This is certainly an improvement, but I was hoping to avoid coupling having direct calls to the kernel like in the patch.
Also, it's not clear to me how this helps in the case of needing to increment a counter rather than deleting from php storage, since this just deletes the php storage directly in DrupalKernel.
Comment #18
fabianx commented#17 I don't believe an event will work in the core/rebuild.php case or in the drush cr case, because it means you need to have the invalidation service loaded to invalidate the container.
That feels wrong, while isolating to the Kernel we can actually swap out the service.
Edit:
Talked a little more in IRC:
Comment #19
fabianx commentedThanks for all the reviews!
#13: Fixed.
Added tests and beta evaluation.
Comment #21
fabianx commentedAdding correct interdiff for #19.
Comment #22
fabianx commentedRemoved the sleep(5), which was only necessary because my local opcache is set to 2s revalidate interval, so it saw the old settings.php still.
Comment #23
znerol commentedTest looks fine, thanks!
I also very much like the fact that this issue moved away from events. Container rebuilds are to avoid at all cost on production sites. Exposing a high-level API for rebuilding the container is not a good idea IMHO. I am fine with the new approach though.
Comment #24
dawehner+1 for the RTBC
Comment #25
alexpottLet's get a CR for this. So that if any custom/contrib code is calling
PhpStorageFactory::get('service_container')->deleteAll();they can discover it needs changing.Comment #26
catchJust nits.
Wipe the Twig Php Storage cache?
s/rebuild/rebuilt
Does this need a self::?
rebuilt.
rebuilt.
Comment #27
fabianx commentedCreated https://www.drupal.org/node/2528246 as change record draft.
Will work on the nits next.
Comment #28
fabianx commentedFixed all the nits.
Comment #30
fabianx commentedComment #31
alexpottCommitted 5a528e5 and pushed to 8.0.x. Thanks!