Problem/Motivation
Our current container serialization solution adds the _serviceId property to every container service. This creates a number of problems.
1) It creates a dynamic property bypassing the zend engine's optimizations for predefined properties.
2) It leaks our container implementation on to every single service.
3) Being present on every service it can break their serialization requiring existing complexity on services unrelated to their actual purpose to work around Drupal.
4) Dynamic properties are deprecated in PHP 8.2, therefore set for removal in a future release (PHP 9?)
Proposed resolution
Remove use of _serviceId and provide an API for mapping container services to persistent objects. Being as container persistence is managed through the Kernel that is the logical place for the implementation.
Remaining tasks
Figure out out if hashing the service identifier makes sense.
User interface changes
n/a
API changes
This creates an actual public API for accessing the same functionality. It removes internal properties that could be considered an "internal api" though they are not documented anywhere and are artifacts of the implementation only.
Data model changes
n/a
Release notes snippet
For compatibility with PHP 8.2, the _serviceId property is no longer added to services in the container. Developers who relied on this property in custom code should see https://www.drupal.org/node/3292540 for alternatives.
Original post
PHP 5.4 has an optimization where class properties are accessed as a fixed array if they are predefined instead of a Zend hashtable like an array (which it was previously). By adding _serviceId to every service we meticulously destroy this for our services.
Proposed resolution
I would recommend adding a method to Container returning the spl_object_hash of the existing services and then compare that in DependencySerializationTrait.
Since Container and ContainerBuilder both need this method, a trait is used containing the getHashes method. There's also a setHashes method because installing a module breaks the module form and the hashes need to be passed from the before-submit-handlers-rebuilding-the-container Container to the after-submit-handlers-rebuilt-the-container ContainerBuilder. The current user id is passed along similarly in the kernel.
Because of this hash passing the approach is not 100% bulletproof: the hash depends on name of the class and the number of instances alive for a class. So theoretically it is possible that a Foo instance lived in the Container, then the container was rebuilt and now both Container and this Foo instance are gone and the class using a DependencySerializationTrait creates a new Foo object which is not a service. I am not worried because in general I think that if a Foo is used as a service it's always used as a service and because this edge case only applies within the same request when the container is rebuilt.
| Comment | File | Size | Author |
|---|---|---|---|
| #249 | 2531564-249.interdiff.txt | 971 bytes | neclimdul |
| #249 | 2531564-249-10.patch | 40.95 KB | neclimdul |
| #249 | 2531564-249-9.patch | 40.99 KB | neclimdul |
Comments
Comment #1
chx commentedComment #3
neclimdulIt took about 6 readings to make sense of it but I think this makes sense. Pushing properties on to objects probably isn't the best way to do things anyways.
Comment #4
cilefen commented@chx Is this 5.4 and higher?
Comment #5
larowlanPlus 1
Comment #6
chx commented@cilefen yes this is 5.4 and higher.
Comment #7
cilefen commented@chx I thought my question was silly as soon as I typed it.
Comment #8
cilefen commentedComment #9
dawehnerI learned something new today!
Here is a benchmark to see whether | how much this improves things:
Note: I ran this multiple times and see improvements between 0.5% and 1%, so certainly some improvements.
Also one less % of memory is also interesting.
Comment #10
twistor commented+1, I've been meaning to create this issue ever since the serialization issue was fixed.
Comment #11
chx commentedIf a module is installed then before we have a Container loaded from disk and after we have a ContainerBuilder (dumped to disk) so we need a helper class besides a helper trait. The minor modulelistform change is semi-related only but I can't bothered to file a separate issue for that.
Comment #12
chx commented> [10:26] <dawehner> chx: mh do you really think a static is the only solution?
Thanks for asking! No.
Comment #13
dawehnerAdded #2389193: Provide a way to determine whether a given instance was retrieved from the container which seemed to try to solve the same problem.
Thank you chx for getting rid of the static!
Comment #16
Crell commentedSeems all of this can move into the try {} block, no? Keep the code together, move the exceptional cases to the end. (Even if in this case the exceptional case is treated as a ¯\_(ツ)_/¯ ).
Comment #17
chx commentedRemoved tests trying to access _serviceId. Good riddance. Added tests for the new getHashes function. Moved everything into the try.
Comment #18
chx commentedComment #19
fabianx commentedIf we always overwrite the hashes, why would we need to set them?
Also if we persist the object from one container to another, why would it change?
Comment #20
chx commentedWe do not override on rebuild -- that's why we index with hash and not id. Let's see an example when rebuild happens: on Container you have AccessManager(1) which gets referenced from ModuleListForm->accessManager so when Container gets destructed in rebuild the AccessManager(1) object doesn't go away. Now after rebuild ContainerBuilder holds AccessManager(2) so now you have $hashes[hash(AccessManager(1))] = access_manager and you have $hashes[hash(AccessManager(2))] = access_manager too and you need both.
On a non-rebuild page we might override a hash with the same hash if multiple DependencySerializationTrait are __sleep() ing but I guess that happens relatively rarely and also that doesn't happen in any performance sensitive pages -- not on GET more or less. If we are not satisfied with this compromise we'd need to add a dirty flag and every get call needs to clear that if the service does not exist yet -- on every page. I don't like that.
Comment #21
chx commentedLooking at the issue dawehner points to I found reInjectMe which should be removable by this point: no tests fail and it shouldn't do anything as _serviceId is just gone.
Comment #22
dawehnerDon't we want to add some documentation which explains that into the patch itself?
Nice!! One less hack
Nitpcik: 2 lines
Needs some helpful docs. Maybe some high level overview.
Missing FQCN
It is pretty cool that this continues to work!
Comment #23
fabianx commentedWhile I like the direction this is going, I don't think reinjectMe can be removed that easily.
It might be we have changed things elsewhere, so it is no longer necessary, but originally it was necessary and I can't see how the hashing helps here to avoid ConfigImporter getting out of sync.
Comment #24
znerol commentedI'm all for removing
_serviceId, introducing that was a terrible idea in the first place.This looks like a lot of trouble, we must get rid of all situations where multiple instances of one service are used (#2282371: Document the fact that it is not safe to reuse services across container rebuilds). Therefore removing
reinjectMeis not a good idea - even if tests pass. In fact the test failures in #2389193: Provide a way to determine whether a given instance was retrieved from the container indicate where we still have issues with services being reused across container rebuilds.Comment #25
chx commentedIt says something (but perhaps not enough) that I was working on LocaleConfigTranslationTest the same as the other issue and when that one passed so did everything else. Also,
> avoid ConfigImporter getting out of sync... removing reinjectMe is not a good idea - even if tests pass.
I need more than that. How does it get out of sync? I need a bit more to actually try to write a failing test. I presume some services are holding state which gets lost when rebuilt but we have added the
persistservice tag just for that purpose very long ago. Currently only the request_stack and the request_context is persisted and the current user id is passed along via a specific hack in DrupalKernel. What other service is stateful?Comment #26
znerol commentedThe persist tag turned out to be a terrible idea also: #2190665: Remove persist flag from services that do not need it, #2285621: Remove persist-tag and replace it with a container.state_recorder service, #2368263: Remove the persist service tag
In most instances the problems do not stem from state being lost during container rebuilds but from obsolete state being reused. For example entity manager and field definitions, maybe plugins also caused problems in the past because obsolete state was reused after enabling a module.
Comment #27
fabianx commentedAlso related #2260187: [PP-1] Deprecate drupal_static() and drupal_static_reset() I don't think any class should use static:: that is not resettable and any persisted service should have a clean up interface.
Comment #28
chx commentedI reimplemented reinjectMe. I doubt it could get any simpler... and, I believe, it should work with all implementations of DependencySerializationTrait .
Comment #29
fabianx commentedOverall looks great!
lol :)
Should we not store _serviceIds in $vars?
That feels a little strange ...
Would that also break the property optimization?
Is it possible to only do this on a rebuild?
Or do we do that already? (Could be from the code indentation).
I now finally understood that something might still reference an old service of an outdated container, but because hashes are propagated we keep that information.
However need to carefully measure memory, with our webtests could easily lead to a kinda memory leak.
Also might want to profile this at least a little. I have no idea how fast or not fast spl_object_hash() is.
We have lots of services initialized on container rebuild time, so it depends on how fast that is.
This was just missed before I assume?
Comment #30
chx commented> Should we not store _serviceIds in $vars? Would that also break the property optimization?
I haven't changed the actual workflow of how _serviceIds are built, only the conditions. It's still initialized to all properties
$vars = get_object_vars($this);and then we unset those that are services. Ie:_serviceIdswill be there since it's not a service and it's also defined in DependencySerializationTrait so it doesn't break the optimization, no.> Is it possible to only do this on a rebuild?
Yes, that's the only time this happens. It's in the same block that makes sure the current user uid is kept.
> However need to carefully measure memory, with our webtests could easily lead to a kinda memory leak.
Well, it's just an array and not a horribly large one -- a thousand short string items or so. We fork per test on CLI and batch on web runner so I do not see how could we have a "runaway hashes" problem.
> We have lots of services initialized on container rebuild time, so it depends on how fast that is
You want to optimize container rebuild?? I do not think that's a worthy path to pursue. In fact I do not know what do we want to profile here, dawehner above already profiled this runtime.
> This was just missed before I assume?
Yes. I mentioned above: it's a semi-related (ie it breaks the same optimization in a file that you need to debug the hell out of to make this patch pass)
Comment #31
fabianx commentedThanks for answering my feedback.
This is RTBC :).
Comment #32
benjy commentedHow about SplObjectStorage?
Comment #33
chx commentedI guess we can. The memory load is still not worse: initializeContainer in HEAD (and in here) already needs enough memory to keep the prerebuild and the postrebuild containers in memory between
$container = $this->compileContainer();and a few lines later$this->container = $container;. And the code becomes really neat.Comment #35
chx commentedThe above has a copypaste error. Interdiff against #28.
Comment #36
chx commentedComment #38
chx commentedComment #39
chx commentedComment #41
chx commentedI was wrong ; sorry ; the SplObjectStorage does have a runaway memory problem -- storing just the hashes instead of the objects solves it. Good to know though that the strings are small enough. So we are back to #28 which was RTBC with a trivial simplification: now the hashes contain the container which makes DependencySerializationTrait significantly simpler.
Comment #43
chx commentedOK, any and all optimization attempts are impossible here. #28 was RTBC'd in #31, that's the one to commit. I've hidden everything else.
Comment #44
dawehnerOh yeah that makes sense.
Comment #45
benjy commentedYeah that does make sense, shame the code was cleaner but alas, +1
Comment #46
znerol commentedI doubt that this will work when a module swaps out a class of a service injected into the config importer. E.g., if a module provides a new event dispatcher, then the service id of the injected one will not be detected because
__sleepoperates on the new container. This will result in different object hashes for the old injected service vs. the new one from the container.Also, I guess that the service in the new container might not have been initialized yet(?) As a result sleep helper wouldn't return any hash for it.
In order to make this work, the hashes need to be collected before the container is rebuilt (or alternatively from the old container).
Documentation missing.
What?
Should have a more specific name which makes it clear, that this is container internals. E.g.
ContainerSleepHelperTrait.Comment #47
fabianx commented#46.1: That is what is happening.
It is non-obvious and it took me a while, but that indeed works that way.
I am still happy with #28.
Comment #48
znerol commentedI fear that I do not quite understand #47. Are you saying that it is acceptable to work with outdated services? Or do you think that what I'm proposing here is already implemented and thus my concerns are invalid?
Comment #49
dawehnerSo to be fair this is at least not RTBC at the moment.
This should at least explain how this exactly works / vs. the limitations as described in #47.1
Comment #50
chx commentedAdded comments, renamed interface and trait.
Comment #51
pwolanin commentedInstead of this can we simply define and apply to classes used as services a trait that defines that class property so as to avoid breaking the optimization?
Comment #52
chx commented> Instead of this can we simply define and apply to classes used as services a trait that defines that class property so as to avoid breaking the optimization?
Not easily. There 40 non-Drupal classes in core.services.yml alone.
Here's a patch that doesn't add new functionality to the containers -- every container change here could potentially be submitted to upstream (perhaps even as bug reports?): adds getServiceIds to the IntrospectableContainerInterface and optimizes it a little. Then it puts the duty of mapping in DrupalKernel -- it's not a lot of code, after all.
Comment #54
dawehnerIt is not only that, its also that contrib/custom could easily miss that.
Comment #55
andypostwhy getter saves any hashes?
maybe better change name of method?
Comment #56
chx commentedFiled #2536012: The app.root and site.path services are invalid and monkey patched for now.
Edit: Re #55 on next reroll I will split the collect and get.
Comment #58
chx commentedPatched up the tests and split the method as asked. In the tests I have utilized http://www.phpwtf.org/php-protected-and-assault for fun and profit ( let's agree on trying very, very hard not to put phpwtf.org worth code in runtime. Tests are different. ).
Comment #59
znerol commentedAs far as I can tell we wouldn't need that if the mapping would be stored in its own service instead of inside the kernel. So maybe clean test code in addition to separation of concerns (as pointed out in IRC yesterday) is reason enough to extract that functionality?
Comment #60
chx commentedAt that point I would rather revert to the previous solution. There's absolutely no point in a separate service when you need to persist state across container rebuilds and service instances.
Comment #61
chx commentedOn further discussion with znerol I am closing this one and let him get his version to pass based on what we learned here: that we need to persist across rebuilds.
Comment #62
znerol commentedIt does not make sense to close this issue because it has more substantial conversation and more followers than the other one.
Comment #63
chx commentedIf you want to make the tests simpler then you need to put the service hash collector right on the Container. #50 was the last to patch containers.
If you put it somewhere else then for any unit tests testing DependencySerializationTrait you need to create a container and a hash collector object and set this object on the container as the hash collector service. The current patch uses the kernel service so it is 1 lines more: you need to set the container on the kernel as well. That one line is all you can save. Also, if it'll be a separate service then you need to initialize the new service after a rebuild with the old hashes so DrupalKernel won't do the actual collection but it still needs to have knowledge about these hashes anyways.
Nonetheless, I wrote three solutions, two actually works, there are enough people in here to decide which one to use or to write the new service: I will let others finish this issue.
Comment #65
dawehnerI would still like to see this issue going somewhere :(
Comment #66
neclimdulAgreed. Here is a naive re-roll of what chx had done previously to get it applying. Only change from the original patch is removing @file from new file and removing a dead use from one of the files related to code removed in the patch. I did it in the merge commit though so I don't have a interdiff. Sorry.
Haven't really done a review of it other then confirming the patches looked similar, installing drupal, and clicking around and confirming things worked.
Comment #68
neclimdulAnother reroll. Trying to figure out what the failure in search still because I'm having trouble recreating it.
Comment #70
neclimdul#2380293: Properly inject services into ModuleInstaller unfortunately started using this non-api and needs to be unraveled now before this can be committed.
Comment #72
neclimdulOk now that is weird...
Fatal error: Call to undefined method Drupal\Core\PathProcessor\PathProcessorAlias::getSourceStorage() in /var/www/html/core/lib/Drupal/Core/Config/ConfigImporter.php on line 784That line:
So clearly that's not suppose to be a PathProcessorAlias. Its like spl_object_hash() is colliding somehow and we're getting the wrong object in random places. But that can't be possible because we type hint service injection... So the only way I can see that happening is if there are some reference wonkyness replacing the internal object.
The documentation does document a possible collision case:
That seem OK for our purposes really and the object clearly isn't destroyed if its being used right? So... what's going on?
Comment #76
alexpottDiscussed with @effulgentsia, @catch, @xjm, @cilefen. We decided to mark #2536370: DependencySerializationTraitPass does not add _serviceId to decorated services as a duplicate of this issue. This issue should include a test for that bug. We agreed that given the nature of that bug and performance improvements hinted at by #9 that this issue is a major bug.
The #2536370: DependencySerializationTraitPass does not add _serviceId to decorated services bug is pretty nasty to debug allow there is a workaround - on the decorated service you can set the serviceId manually in the service definition see #2536370-7: DependencySerializationTraitPass does not add _serviceId to decorated services
Comment #78
neclimdulI personally consider this blocked on drupalci changes since the current errors seem specific to an unpatch spl_object_hash() behaving in a way other then its documentation in an unpatched 5.5.23. That or we implement our own version but I'm not sure we can. I don't know if we need to bump the php version or how to handle the specifics of treating and resolve this.
Comment #79
neclimdulJust kinda a blind reroll to see what testbot thinks.
Comment #80
neclimdulComment #83
pk188 commentedRe rolled.
Comment #85
pk188 commentedRe rolled.
Comment #87
cburschkaThe last two patches are incomplete, as they're missing the IntrospectableContainerInterface.
Comment #89
cburschkaThe IntrospectableContainerInterface actually was already deprecated and is removed in 3.0, so we should be extending the ContainerInterface instead.
Comment #91
cburschkaNote that replacing DependencySerializationTraitPass with this solution, which gets the service ID mapping from the live container, coincidentally fixes #2896993: Decorated services crash on serialization., because the compiler pass currently runs before decorators are resolved, so overridden services have incorrect (or missing) IDs.
Comment #92
cburschkaIf I'm understanding the changes correctly, then the DependencySerializationTest now uses an instance of the Symfony container class, rather than the Drupal one. The Symfony class hardcodes
service_containerto$thisand actively blocks it being assigned; therefore the test fails.I've replaced the set() command with a sanity check that asserts
get('service_container')returns the container itself.Comment #94
cburschkaYes, that ContextualFiltersBlockContextTest failure is exactly what it looks like: A TranslateableMarkup object gets picked up by the DST and somehow mistakenly identified as a service.
Comment #95
cburschkaWTF.
Added the following to
Drupal\Core\Plugin\Context\ContextDefinition:Got out the following:
These two hashes are 0x32e0000000000000000 apart and yet they return the same array value.
Comment #96
cburschkaIt appears that one of the many hashes mapped to the plugin.cache.clearer service collides with this particular translateable markup object, and what is more, does so in a consistent fashion (see the attached text file for a dump of all hashes and service IDs at the time of the collision).
I'm not sure why there are so many hashes, by the way. spl_object_hashes guarantees "A string that is unique for each object and is always the same for the same object."
Unless the container deinits and reinits the same service multiple times (up to sixteen for some, such as the cache_tags invalidator), the second condition appears to be violated. (And if it does reinstantiate the same service, then why does it do so?)
The first condition appears to be definitely violated considering we're getting this collision.
Comment #97
cburschkaEmptying the service mapping every time seems like it fixes this specific case, but it doesn't solve the underlying mystery.
And in particular, if a service can be instantiated multiple times, then we can't actually rely on the mapping. By the time an object is serialized, its injected service might not match the service registered in the container anymore.
Comment #99
cburschkaRebuilding this map with every serialization might be too much of a performance drag. Which is kind of ironic.
Maybe it would be better to update the map from within the container whenever a service gets instantiated?
Comment #100
cburschkaFrom the docs:
I suspect this entire approach is broken. We can't identify services by their SPL hash, because it's possible to reset the container. At which point
1) any service that is still injected somewhere is orphaned (the container will get a fresh instance when it's next required). If we clear the mapping, we lose its hash.
2) any service that is not in use gets GCd, and if we don't clear the mapping, we may find its old hash now being used by a completely different object.
So why are we using spl here? It would be more reliable to only use the class name. The only consequence would be picking up instances of the class that didn't come from the container - an edge case that the spl hash also couldn't quite handle:
For extra safety, we could concatenate both class name and spl - provided we ensure that we never get the case where a service is initialized, injected and deinitialized without ever being mapped.
Comment #101
cburschkaNew patch.
I kind of suspect the problem of non-service instances of a service class is quite negligible. Classes that use the DependencySerializationTrait already have so many pitfalls to deal with - private variables that get lost, non-service variables that store services and don't use DependencySerializationTrait, other variables that might not be serializable for weird reasons...
By comparison, a rule that if you put your own instance of a service class in a non-private variable, serialization&disinterment will replace it with an instance pulled from the container, is fairly straightforward.
Comment #102
andypostComment #105
neclimdulWe where using spl because class doesn't tell us enough to map to a specific service. It is reasonable to have the same class in different places on the container. I see ViewsPluginManager registered 12 times for different services and getting the wrong one is certainly going to cause problems and all the other instances will not be properly replaced.
Comment #107
neclimdulLets give this a shot. Its not pretty but I'm curious what the outcome is.
Comment #109
neclimdulSo.. it says clearly two failures but that's one failure. And of course something started relying on the serviceid property we're trying to kill :( :(
#2849674: Complex job in ViewExecutable::unserialize() causes data corruption
Comment #110
neclimdulHopefully we can just convert this to the service map on the kernel. :pray:
Comment #111
neclimdulComment #112
andypostmaybe move that to some helper reusable for contrib?
Comment #113
neclimdulI thought about it and am not sure what an interface for the helper would look like or where it would live.
The current interface is concise, there is an array of service id's keyed by a hash of the class. There are public methods for getting that hash and creating the hash. It'd be more concise if it wasn't for the PHP quirk but it isn't the first or last quirk we'll work around.
One final note, contrib _really_ shouldn't be digging around in the map in the first place. Its there for core magic(the container serialization logic) and if you're doing something ugly well, the rope is there to hang yourself with but we don't need to make it pretty at the cost of complexity to interfaces. I would have honestly refactored the views code so it wasn't even used but it wasn't immediately obvious to me what it was doing and that made it out of the scope of what this and easier to handle in a follow up than block.
Comment #114
cburschkaComing back to this: Would it be feasible to instead make services implement a
ServiceInterfacewith a setter/getter for the service ID (and a trait)? For interoperability, we could add a compiler pass that checks for services not implementing ServiceInterface and proxies them, analogously toDrupal\Core\DependencyInjection\Compiler\ProxyServicesPass.The only problem with the current approach is setting a property that isn't statically defined. This could be solved by statically defining it, using a generated wrapper class if necessary.
I still have the impression that the array idea is too magical. As we've already seen, it comes with serious pitfalls - the original approach used only the internal pointers (
spl_object_hash), and even the new approach only mitigates this by also using the class name.Comment #115
neclimdulIts been a while since I've looked at this but memory serves we can't rely on classes to do it for us because the container builder needs to know thing about and cache all services.
Comment #116
neclimdulRe-roll of the current solution. Just fixes a use conflict.
Comment #118
neclimdul#2935503: Remove rest.module's use of _serviceId for module resolution to resolve rest.module's use.
Comment #121
hchonovThe inability to correctly "serialize" decorated services makes it impossible to use together the modules autosave_form and entity_embed - #2980855: Entity Embed breaks if Autosave form is active, which now officially makes the issue a contributed project blocker.
Comment #122
neclimdulblocked by #2935503: Remove rest.module's use of _serviceId for module resolution
Comment #123
MixologicCurious if the issue title/issue summary could use a refresh. The launch of 8.7.0 will see the removal of support for php 5.x, so if this is a 5.x specific issue, I cant see why it'd be fixed. But if this is still an issue for 5.x all the way to 7.3, then maybe the title shouldnt say "5.4" in it.
Comment #124
neclimdulHonestly with the current implementation I'm not sure the we're pushing any definable performance gains. Or more accurately, it probably does produce performance improvements in general usage because objects on the container are slightly smaller, there's a dropped function call on container set, and the implementation is now entirely container to the sleep/wake process. But they are small enough in 7.X it is going to difficult to measure them.
Mostly the current implementation pollutes classes with random properties, leaks implementation details, and generally causing problems and that should be fixed.
I've tried to update the title and summary to reflect that.
Bonus, a reroll to fix a use conflict. We can still discuss the implementation/review while the Rest issue blocks actually passing tests.
Comment #127
neclimdulReroll. Includes the rest fixes because its just not moving and without it its not really possible to meaningfully review this task. Also includes some other tests that have been added in the past year.
There where some conflicts so the interdiff isn't perfect but it has the meaningful changes.
Comment #129
dawehnerWhile reading the patch I'm wondering about the following things:
getServiceIdMappinginto the service container.Here is some nitpick review:
Nitpick: This doesn't comply with our documentation standard. The comment should be on the next line
I'm curious how
get_classmakes this more unique. Is there a reason for this? Maybe there could be an inline comment about that.Comment #130
neclimdulYeah, we still have goofy rehydration. I'm not sure it increases the surface area as creates a public API for something that was occasionally (at least in rest.module) used like one. I'll see if it can fit on the container without getting... recursively terrible though.
1) eww... yes.
2) yeah, there is some history hidden in the 100s of previous comments but its because spl_object_hash is not unique in time but entirely unique. @see #73 and this bit of documentation on spl_object_hash "When an object is destroyed, its hash may be reused for other objects." Basically we try to re-hydrate and end up with a different type of object and everything dies. In reality... I'm not sure we'd ever run into this on a real site, there would likely have to be a lot of rebuilding, object destruction, and gc. That is exactly what happens in our test suite though and we just got miles of random brokenness. Narrowing it by class name made things "good enough".
Well at least good enough in previous tests to make things work. I can't recreate the failures in the last patch(if they are failures... they show passes and claim failures) so I'm going to have to do some deep digging and hopefully this isn't the cause again...
Comment #131
dawehnerWow! Thank you for the explanation! I think it would be really valueable to document this in the code.
Comment #132
mxr576Referenced this issue in #3056604: Add/remove event listeners provided by a module on module install/uninstall.
Comment #133
neclimdulYeah, we're still adding these... I guess I'll pick this up and add some more issues to remove the other uses though I feel like no one cares anymore.
Comment #134
neclimdulI guess its not actually that bad.
- reroll
- removed #2935503: Remove rest.module's use of _serviceId for module resolution from the patch since its not relevant anymore
- optimistically updated version numbers in deprecations.
- Added a comment re #131
Comment #135
neclimdulwoops, I um... didn't include the interdiff changes in the patch.
Here's the patch that should have been in 134 for posterity plus an updated patch to fix for the way deprecation where being tested.
Comment #137
neclimdulOk, this should pass. It looks like I got a little flustered a few patches ago with testing failures and didn't quite fix the ConfigEntityBaseUnitTest correctly. Building a container and kernel in unit tests is gnarly but I guess its not "a fully functioning kernel" so we do it pretty often and don't need to move that test to the kernel suite.
Anyways this should be pretty solid again. That only thing that stood out to me skimming that patch again was that _maybe_ these service mapping kernel methods should be tagged @internal. They are still not really designed as an API for application development but hacky bits to resolve this serialization problem so there would be some logic in doing it but I don't think its strictly necessary.
Comment #138
pdenooijer commentedReviewed and tested patch 136, seems to work fine and is even slightly faster.
I have two comments:
Uploaded a new patch with above two comments fixed.
Comment #140
pdenooijer commentedBug in the ether it seems... green now :).
Comment #141
neclimdulThanks for the review!
You're right,the ContainerInterface alias isn't strictly necessary. It's been 3 years since I added it but I'm pretty sure my intention was to make the distinction from the ContainerInterface in the same namespace explicit to developers where the interaction with the use might not be obvious. (shrug)
Comment #142
pdenooijer commentedIf your this deep into the workings of Drupal you know how to read namespaces and know that Drupal depends on the Symfony framework. I don't see any upside to this alias to be honest.
RTBC then I guess, agreed?
Comment #143
mxr576I am giving this a RTBC, it looks good and if it is merged, I can probably finalize #3056604: Add/remove event listeners provided by a module on module install/uninstall.
Comment #144
alexpottUnrelated change.
Why do have to hash here? Couldn't we do
get_class($object) . spl_object_hash($object)Does the hashing buy us anything over than shorter keys which are meaningless and more compute time?
I think this is not necessary.
This change is tricky from a BC perspective.
I've been working on a more event driven config importer that will hopefully reduced what's being done here. But atm it still needs it and this implementation is neat.
I've been thinking about the removal of this class. Class removal often goes wrong. I think we should deprecate it and make it a no-op here. And remove in D10.
We definitely need a well written change record for this. I was wondering if there was a way to do this in a more BC compatible way. But I don't think there is. In an ideal world we could set the _serviceId to a function that triggers a deprecation if used. And somehow support the existing functionality and then remove it in D10. Looking at http://codcontrib.hank.vps-private.net/search?text=_serviceId&filename= shows that this change is definitely going to impact contrib :(
Comment #145
cburschkaNo reason in practice; afaict it's only used as an array key so there is no length limitation.
Though the function *is* named
generateServiceIdHash(not "generateServiceIdentifier" or something), so if it returns a variable-length unique string instead of a fixed-length hex string, it wouldn't be quite what it says on the tin.Comment #146
cburschkaThis one addresses 2), 4), 6) and 8) of #144.
Comment #147
neclimdulThere's at least one outstanding review I don't think has really been addressed which kept me from RTBCing this.
from #129
I did this before and went down the wrong rabbit whole and hadn't come back. I've started on this again last week and got something that's looking usable but there are some weird failures in the kernel test suite I'm trying to track down. Before posting it for discussion. The tradeoff is relying more on the Drupal Container interface instead of the Kernel which might make this a _little_ bit bigger change but maybe better.
Thanks for the review Alex! Sounds close though... addressing Daniel's concerns might shake that up... Hopefully not.
1) 100%
2) It was related to the original scope of fixing undefined properties but its a quick fix i guess. :-/
3) I think cburschka is right, originally it was just he spl hash then we needed to separate them. I think I _might have run into some key length issues but that should be validated. I'm not sure that large keys are free though there's probably a tradeoff we should quantify and make a decision on. I'll look into it some more.
4) It its not needed, that's awesome! :-D
5) I'd like to know more about your thoughts here. Its in testing so less BC promise and I think the difference was just that the Builder ended up with a kernel on it since this relied on this new part of the kernel. Its not ideal but its a useful wrapper for that pattern even if the method name is super confusing. That said the container version of this patch I was playing with might
6) Woops...
7) Nice! I hope this simplification helps your work.
8) I'm not sure I agree since this is clearly a very internal and limited class but you've got more experience with this.
9) For sure. Though a lot of those reference this issue, are fixtures in tests, or are unmaintained/unreleased so it might be even more important that this exist for site developers where we can't measure and there is a higher likely hood of weird hacks to fix edge cases.
Comment #148
cburschka(Regarding #2536370: DependencySerializationTraitPass does not add _serviceId to decorated services, I've checked up on a smaller patch I submitted to fix the decorated service bug in #2896993: Decorated services crash on serialization., which still works. That might be relevant for the branches that this major change can't be applied to.)
Comment #149
neclimdulOk this should do it. The interface looks a little bigger on the container. I believe the intent was _not_ to do that originally but the fact that the patch shrinks quite a bit and we aren't futzing with kernels in all those tests smells a bit better I think so I think Daniel was right in that overall this ends up simplified. Notably the ContainerInterface has to move from Core to Component because now
\Drupal\Component\DependencyInject\Containerneeds to implement it for everything to work and there is a new trait to share that logic with the container builder.Additional changes:
- I moved the deprecation to a trigger_error and updated the format for the new standard.
- I also removed a lot of the TestKernel stuff and moved the EntityTypeTests code back to the unit test as I expected. Only small changes to get those to work now.
Sort of unrelated in EntityTypeTest I modified the exception logic in the serialize test. When I reverted it and it failed it was really hard to understand what was failing.
Before:
After:
I tried using the "shouldNotBeCalled" prediction which seems more accurate on a mock but the error comes out really weird without line numbers or a useful trace. I think because serialize isn't an interface method but I'm not sure.
spun off #3143085: Define and optimize alias definition in OptimizedPhpArrayDumper and #3143087: Define accessManager property for ModulesListForm for another missing property definition I ran into.
Comment #150
neclimdulbah, I clicked the wrong file I guess. here's the interdiff.
Comment #151
neclimdulThose failures are rough. We can't install a site with this patch and I think I figured out why. What seems to happen(glossing over a lot of details because the installer is a ball of spaghetti) is this:
1) Installer bootstraps everything and sets up a container. Looking good.
2) We generate a batch array. On that array we put some
Urls, each end up with references to a urlAssembler from container #13) Batch installs a module
4) Module installer triggers rebuild with container #2
5) We wrap up the batch and serialize the batch array(you might see where this is going now)
6) DependencySerializationTrait kicks in on those
Urls from step 1. It pulls the container(#2), removes any services that match that container... which are obviously 0 since its different from what generated the Url.7) Serialize kicks in and BOOM! there's a database connection on those services and we're dead.
The reason the kernel approach worked is that it remembers service ids across these rebuilds by storing them on the container. I should have remembered that but it was hidden in a bunch of discussions and logic that wasn't clear mostly dating back 5 years :(
Comment #152
cburschkaOh yeah, the mappings can't be reset if the container is rebuilt, since the old services are still injected all over the place and may still need to be mapped. I think that array effectively has to last for the rest of the PHP runtime?
Comment #153
neclimdulPretty much, I like how some of the things shook out in this patch but I think the resolution is going to have to go to go back into the kernel because its the only thing that really persists like this. The "collection" logic might make sense to remain. I'm going to take another pass and see how much I can salvage or if we need to scrap it and go back to #146. At the very least this can be used to document some logic and maybe add some tests to explain why things are on the kernel.
Comment #156
bradjones1Comment #157
neclimdulI kinda got burned out on this but it looks like it might be coming back. RFC to deprecate the functionality dynamic properties like _serviceId goes to a vote soon and that'll shake things up.
https://wiki.php.net/rfc/deprecate_dynamic_properties
Comment #158
andypostAs I see voting will be Yes, so it needs reroll/revamp
Comment #159
neclimdulYes, I think the only effect we might have is a hard push might stretch the timeline. Even the no's only seem to be worried about the timeframe. But I've not heard anyone else in the Drupal community raising alarms so unless we start hitting the mailing list I think we're going to have to deal with the outcome. The change itself is very popular and I think probably with good reason so some for is certain to get through soon.
Comment #160
andypostrelated plan has number of issues to clean-up and cover by phpunit the missing/undefined properties
Comment #161
andypostComment #162
andypostComment #163
bradjones1The dynamic properties RFC is approved and going into PHP 8.2, so this is definitely affected. Adding related issue which depends on this property.
Comment #164
neclimdulMan its hard to get excited about rerolling this issue. Here it is though. I moved the collection logic down into the container like mentioned in #153 and did some general cleanups. Seems to work in local testing.
Lot of resolved conflicts but a rough approximation of an interdiff attached as well.
Not sure how to tag "this might be an interface and if it is it will 100% break during the next release cycle" but its probably bordering on critical. I guess we're all aware though so not touching it.
Comment #165
neclimdulThat's confusing... after some digging through coder I found the message doesn't really have anything to do with the problem. I guess we're following deprecation annotations with a @see comment now.
Anyways, this should fix everything.
Comment #166
neclimduloh jeees I diffed against 10. fingers... come on.
Comment #167
neclimdulI am running test's locally. ugh.
Comment #169
andypostHere's re-roll for 9.5 and 10.0
Used to test it with PHP 8.2alpha2 and it does not fix deprecated dynamic properties https://php.watch/versions/8.2/dynamic-properties-deprecated
Log a bit cleaner but still tons of messages
Comment #170
andypostupdated to 9.5 and filed CR https://www.drupal.org/node/3292540
changed to approach from #2987089: Remove views.module's direct use of the _serviceId property
Comment #172
andypostFixed deprecation message, meantime it means that
\Drupal\Tests\Component\DrupalComponentTestis fragileComment #173
znerol commentedI wonder whether it would be possible to design the API in a way which leaks less implementation details. Looking through the patch, I think the most important call site is
DependencySerializationTrait::__sleep(). The current patch wants to change the implementation into something like this (irrelevant parts removed):I'd prefer something like this:
This will simplify changing the implementation in the future if the mapping/hashing approach has issues which we haven't found yet. It also makes it more obvious for contrib on how to migrate code which used the
_serviceIdproperty before.Comment #174
znerol commentedAnother approach would be:
Like this it is still possible to update the lookup table in
getReverseServiceLookup(i.e. outside the for loop).Comment #175
andypostIMO #174 looks better
Comment #176
andypostInteresting measures of serialisation https://peakd.com/hive-168588/@crell/benchmarking-serialization
Comment #177
alexpottAre we fixing #2536370: DependencySerializationTraitPass does not add _serviceId to decorated services here? And can we add test coverage to ensure we do... FYI this will need a reroll now that #2987089: Remove views.module's direct use of the _serviceId property has landed.
Comment #178
andypostRe-roll after #2987089: Remove views.module's direct use of the _serviceId property
If we need
_serviceIdon every service it looks reasonable to add#[\AllowDynamicProperties]to every service, but meantime this is blocked on phpstan (see #3275858-4: View's ResultRow uses deprecated dynamic properties for details of test run)Comment #179
andypostPatch for 10.0.x to test
#[\AllowDynamicProperties]Comment #180
andypostComment #181
andypostNot clear why phpStan throws on php 8.2 - trying without
\Comment #183
andypostFiled https://github.com/JetBrains/phpstorm-stubs/pull/1404 to make phpstan find the attribute on PHP 8.2
Fix failed test - this test addresses #177 concern about testing decorated services
Comment #184
andypostThe test for decorated services is added in #2896993: Decorated services crash on serialization. (9.4.x and later) so interdiff in #183 changes it
patch will need re-roll for 9.5 so re-targeting the issue for 10.0.x
Comment #186
berdirThe 8.2 run seems to be stuck again at a kernel test? No new output for > 1h hour now.
Comment #187
andypostFix decoration test - it points that in case when service is not instantiated the hash can't be calculated
PS: removed attributes for 8.2 so the patch is ready for review
Comment #188
andypost@Berdir I used to run the test locally and get following output, OTOH I'm using php82 from alpinelinux
Comment #189
catchOverall this looks really encouraging.
This doesn't seem right for the interface docs.
Could the trigger_error() be inside the isset()? Should we put the value of _serviceId in the deprecation message?
Could use !== here I think.
Seems fine to deprecate in 9.5.x for removal in 10.0.x for this one - no-one should be using it directly and PHP 8.2 support is blocking. +1.
Maybe 'storage is for, so it can be used' - a lot of short words all at once here and wasn't able to immediately scan the sentence.
Comment #190
andypostAddressed #189
Still not sure about wording and naming from #173-174:
- having separate "lookup" service looks wrong approach as hash-map should not be stored in container (so it lives in kernel)
-
generateServiceIdHash()vsgetServiceId()is wrong as container should give a hash (runtime object hash)/cc @znerol
Comment #191
andypostThe patch & interdiff
Comment #192
mondrakeThis is contributing the larger number of issues with dynamic properties deprecation in PHP 8.2 - to a point that the noise is such that it is complex to sparate other issues. IMHO should be the first issue to be fixed towards PHP 8.2 compatibility, hence suggesting to promote to Critical
Comment #193
andypostfix cspell
Comment #194
mondrakeComment #196
neclimdulMan I was so burned out on this issue but seeing this activity warms my heart. You're amazing Andy, thanks for bringing this back into shape.
This change to the test asserted that the deprecation was triggered when the object definition was going to trigger the runtime exception. Moving the deprecation inside the isset broke it so we can remove this part of the test and just rely on the other test for asserting the deprecation. I think its probably fine to say "if your container is broken we don't have to warn you its also using a deprecated process because its broken" and still remove or update that exception when cleaning up the deprecation.
Comment #197
andypost@neclimdul thank you a lot! Yes, no reason in 2 legacy tests!
Comment #198
neclimdulSo alex asked earlier if we needed this to be a hash or if that was just extra cycles. I think he has a point. Considering the change, I think the only real benefit is it obfuscates the the information which I think might be important for providing a solid interface for "we can change this in the future" Like if we found certain site had problems with collisions and needed additional information. Also if this continues to D10 we might even consider xxHash but that would be new feature use and probably worth a follow up.
If we're applying this to d10 is this accurate? Not sure what the merge plan is, probably need some maintainer input on that process to finalize the string and different versions of the patch.
Comment #199
andypostRe #198
1) not clear - is it needs follow-up? Then probably 174 about naming makes sense (exclude hash wording)
2) catch in #189.4 said ok for 9.5
Comment #200
geek-merlinGreat progress! Some remarks though:
# ServiceIdHashTrait
Why not simply use SplObjectStorage (or better, WeakMap)? And if there's reasons not to, add an explaining comment, i'll not be the last to wonder.
# Non-shared services
Care for them, or throw? If we use SplObjectStorage, we get that for free.
Comment #201
geek-merlinAnother possible follow-up:
# if ($value instanceof EntityStorageInterface) {
That reminds me of something: group 2.x adds EntityStorage discovery by service name convention. It simplifies decorating and more, and i like it a lot.
Comment #202
znerol commentedI still hope we could get away without computing / storing any state (hashes) at all. #2389193: Provide a way to determine whether a given instance was retrieved from the container explores that idea a little bit.
The gist of that approach is that when something wants to know whether some
$instanceis coming from the$container, then we just loop over$service_idsin the container and select the$service_idif$container->initialized(service_id) && $container->get($service_id) === $instance.This will result in repeated loops over the container upon serialization. I still wouldn't expect any performance impact because
===is cheap.Comment #203
andypostSplObjectHash is not unique, moreover if we'll move towards reactphp/swoole then it could cause weird bugs, that's why I suppose to store hashes per kernel - see #2218651: [meta] Make Drupal compatible with persistent app servers like ReactPHP, PHP-PM, PHPFastCGI, FrankenPHP, Swoole and https://gitlab.com/daffie/swoole
Btw #202 sounds interesting to explore so makes sense to split current patch for future rework - so naming needs reconsider
Comment #204
neclimdulyeah, as Andy said, SplObjectStorage is just spl_object_hash as an object array. So all the comments about hashing apply to it the same. You could override the hash but its not much of a win at that point. Just obfuscates debuggers and maybe adds some memory overhead. Not being safe across deletion they have a fairly limited usefulness and should have a bigger warning.
Re #198
re #1
Great, I think using a hash is the better approach just wanted to make sure Alex's concerns where addressed. Absolutely happy to move forward with the faster hash on 10! I had just made an assumption there might need to be a technical review but if there are no objections to xxHash why not?
re #2
Oh missed that! I was assuming from the 10.x patch containing deprecation there was some confusion. Can we remove the deprecated bits?
Comment #205
andypostRe #204-2 I think we should throw fatal in 10.0.x if we deprecate in 9.5.x
Comment #206
geek-merlinAgain, what about non-shared services (Of which the container creates a new instance on each get())?
With the current _serviceId implementation, retrieving the service id for one works just fine.
With the new implementation, the container does not store them, so the new logic will break, unless it saves their hash instantly on creation (and should add a test).
Comment #207
berdirDo you have an example? I assume you mean private/non-public services?
IMHO they shouldn't end up in this situation because they can only be injected into other services and shouldn't end up being serialized? I don't see how they could have worked before, as you can not get them by service ID.
I'm thinking about whether things like cache backends, of which there are multiple instances of the same class could be a problem, but they are different objects so that _should_ be fine.
Comment #208
neclimdulMerlin is referring to https://symfony.com/doc/current/service_container/shared.html. The only core use seems to be in Layout builder. Its unclear to me why its even doing it and nothing in LB would run into this currently. So firmly custom/contrib use case.
While I'm unhappy regressing contrib/custom, I'm not sure I'm ok with the cost that would be associated with supporting them. Instead of generating hashes during rebuilds, we'd be generating hashes on every get. Almost all of this work would just be thrown out and never used. Seems like this would be an edge cases the class would need to support on their own. I think something like this would work and be backwards compatible with the current implementation.
Comment #209
geek-merlin@neclimdul Yes, you got it.
I have no stakes either in supporting this or not. My point is to simply decide what to do, and document it well.
If we decide that this BC regression is justifiable, so be it.
Comment #210
andypostas this one is primary blocker for PHP 8.2 should we wait for #2389193: Provide a way to determine whether a given instance was retrieved from the container?
Comment #211
andypostComment #212
ghost of drupal pastBased on some recent work I did for bricks, I would recommend maybe looking into
SplObjectStorageinstead ofspl_object_hash. It's sort of cheating because while it usesspl_object_hashinternally but it keeps a reference to the object so it can't disappear unlike withspl_object_hash.Comment #213
andypost@chx thank you, this could make the patch compatible with 9.5 but will need follow-up to replace it with WeakMap to prevent maintaining link references (this is very sensitive topic for 8.1/8.2)
There's related issue #1199866: Add an in-memory LRU cache and it may help issues like #3292759: Create getters and setters for dynamic Extension properties
PS: the issue is primary blocker for PHP 8.2 support for core so let's file follow-up to improve current patch later
Comment #214
andypostFiled #3299828: Stop storing Settings singleton in object properties after PM discussion with chx
Comment #215
neclimdulDon't we _want_ to allow the objects to disappear? If we don't allow them to be GC'd aren't we going to add a memory leak as mentioned in #41. Properties on services are commonly used for internal caches too so it could be extremely brutal leak as well. Right now we're just storing what should be a very efficient array of strings.
Comment #216
ghost of drupal pastJust a few months ago I was working on a module which collects which entities are used when rendering an entity. This is super to hard determine without rendering because of entity embed and friends. I attached a failing test based on that module, it's as simple as:
As the linked module shows, this actually can happen, in much more elaborate ways, too. And something might store $cache in a property and then you are in trouble. Eg.: EntityLastInstalledSchemaRepository stores its cache backend. And, of course, this is not limited to caches -- any service which gets reset has the potential to break this patch. I just had a module at hand which does it with cache.
The only way out is indeed a potential memory problem for testing -- although not for normal Drupal because recreating services there is incredible rare. andypost and I have been working on getting an SplObjectStorage solution passing, it's very close -- if that's the direction we want to take, we can do it.
Or we can say this is not a supported operation and that's fine with me. Just wanted to point out a potential pitfall here.
Comment #217
neclimdulI'm not following obviously if you break a service on the container before rebuilding its broken. I'm not even sure how the described code would work since in general the container should be compiled and locked and that set should throw a BadMethodCallException because its creating a possibly inconsistent state for the environment.
Edit: I was confused because apparently core forgot to re-enable compiled container protections when Symfony fixed synthetic services ~10 years ago. Will file follow up to discuss because mutating live containers is probably not something we want.
Comment #218
geek-merlin#216: Huh, how could that happen. I suppose this is the related issues.
Comment #219
catchIs this issue actually blocked on #2368263: Remove the persist service tag, #3300306: [PP-1] Drupal ContainerBuilder allows mutation of non-synthetic services, #2937010: Bring ContainerBuilder inline with Symfony Container and apply upstream improvements, or #2389193: Provide a way to determine whether a given instance was retrieved from the container or are they just related/potential follow-ups?
Comment #220
andypostFYI related issue looks more like duplicate with another naming reverse_service_resolver #2389193: Provide a way to determine whether a given instance was retrieved from the container
Comment #221
neclimdul@catch I think they're probably blocked on this being fixed actually. Things keep stumbling into it.
We had a rtbc patch and I think if we had a finalized release plan we could clean up all the depreciation methods and agree to resolve Alex's performance concerns with an xxhash follow up.
Comment #222
andypostTrying to deprecate settings service is just 40k patch, looking for reviews #3299828: Stop storing Settings singleton in object properties
Comment #223
xjmComment #224
xjmSwitching up the tag since this needs to be fixed in 9.5 too.
Comment #225
catchI think we should try to backport this to 9.5.x if we can. Feels unlikely a module is relying on this, but since a lot of already ported, I reckon we should make it 'deprecated in drupal:9.5.0 for removal in drupal:11.0.0'.
I've opened #3307718: Implement xxHash for non-cryptographic use-cases for xxHash.
Comment #226
andypostThe #216 got reply, and it has follow-up
Comment #227
neclimdulMissed one.
Sad we'll hold this deprecation through a release but I guess that's the reality.
Comment #228
andypostThanks, fixed it
Comment #230
andypostFlaky test
Comment #231
longwaveinheritDoc -> inheritdoc
Do we need a fully-qualified interface name here?
Also the change record needs more detail - this doesn't tell me what I have to do: https://www.drupal.org/node/3292540
Why do we need a loop here? Just set the property directly or with array_merge()?
This test succeeds for me locally without this line at all.
testSerializationWithMissingServiceseems redundant, it looks to be testing the same as the previous one now the service ID has been removed.Out of scope change?
Comment #232
neclimdulre 1: not going to die on this hill but I'm going to gripe a bit. Capitalization is inconsistent and both in core and the phpDocs because it was changed in phpDoc 2. Because of that, despite what you suggest being more common in core, inheritDoc is the probably the correct tag according to the current phpDocs documentation and the proposed PSR replacement.
re 2: Agreed. And updated the changelog. Probably needs some love but it captures how to deal with possible breakage.
re 3: the loop is required because you could have multiple container rebuilds in the same kernel process and you need to be able to map for all of them. I believe this was actually required because tests or installs or something was breaking.
re 4: I think I decided to touch up these locations with the new helper because it avoids future bugs settings up a container in tests.
re 5: hm... seems like you're right.
re 6: looks like it is. the code makes more sense after the change though 🤣
Comment #233
longwave#232.1 PHPStorm doesn't help here as it uses the newer one by default, but we aren't that inconsistent: 18,505 uses of
inheritdocacross core but only 85 ofinheritDoc. https://www.drupal.org/docs/develop/standards/api-documentation-and-comm... still uses the old standard and we would need a coding standards change if we are to accept either.#232.3 Why not just use array_merge() then?
#232.4 What I meant was I don't think that test needs the container at all - if I remove the helper entirely then the test still passes, so we can probably clean that up here.
Comment #234
neclimdul#232.1 yes phpstorm doesn't help. Or rather it does because it does the right thing. :-D
#232.3 Oh, I think I just shy away from array_merge when dealing with large arrays like this since it creates a new array. Or because it came out of refactoring a more complicated for loop. Hard to say at this point. The multiple rebuild is uncommon and PHP would likely re-allocate under the hood in all cases so it probably doesn't save anything trying to avoid a copy.
#232.4 Oh, 🤷
Comment #235
longwave> I think we should try to backport this to 9.5.x if we can.
If we don't need to do this, we can use WeakMap (PHP 8 only). This solves the memory issue with SplObjectStorage and, I think, solves the hash collision issue in the OP: WeakMaps do not increase the reference count and an object stored in a WeakMap is automatically removed from the map when it is garbage collected.
Comment #236
andypostWeakMap does not work for it, in comments above catch and chx used to explore it
IIRC there's related about it #2389193: Provide a way to determine whether a given instance was retrieved from the container
Comment #237
spokjeComment #238
catchI think we should address WeakMap and/or different hashing or non-hashing solutions in a follow-up.
Comment #239
spokjeFollow-up is here: #3308859: Discuss WeakMap and/or different hashing or non-hashing solutions for container serialization solution
Comment #240
spokjeAddressed #231:
1. Went with the proposed
{@inheritdoc}for the sake of consistency. (18,505 vs 85). I do however think we need to decide on a new coding standard for it. Something for a follow-up?2. Done
Added
Needs change record updatestag for someone with an actual Big Brain to do.3. Left as-is.
4. Done.
5. Removed
testSerializationWithMissingService, which is now indeed the same astestSerializationexcept for an extra\Drupal::setContainer($container);. AlsotestSerializationhas more assertions so that one seems the one to keep.6. Reversed
Comment #241
longwaveThank you @Spokje. @neclimdul already updated the change record and I'm happy with that now, so removing the tag again. Setting to NR as there is no more work left to do but I don't have time for final review right now.
Comment #242
spokjeComment #243
spokjeComment #244
spokjeComment #245
spokjeComment #246
spokjeSorry, ran out of available time.
Failing test should be easily fixed (#FamousLastWords) by ignoring "lines" containing
E_USER_DEPRECATEDorexpectDeprecation.Comment #247
neclimdulWell that's fun. This isn't a bullet proof or complete solution but it fits the current need within the current DrupalComponentTest test framework.
Comment #248
longwaveThis all looks good to me, except two nits with the most recent fix:
We should update this comment to say "@see and deprecations".
We don't usually use Yoda conditions in Drupal (although Symfony does), we should probably switch this round or just say
return !preg_match(...).Comment #249
neclimdul1. oh yeah.
2. I take your point. The intention was more readability since at the end, the comparison hangs out past 80 characters making it less clear what's being returned but I guess that's the convention.
Comment #250
neclimdulComment #251
longwaveThanks. I've re-reviewed the whole patch and have no further points to make, therefore this is RTBC.
Comment #254
catchI'm not 100% sure we still need this, or at least that it couldn't be removed with a bit of work, now that we've got the memory cache backend in place for entity storage which should give us a bit more control. This is pre-existing code being moved around, so I've opened #3309369: Remove special-casing of entity storages in container serialization.
#3308859: Discuss WeakMap and/or different hashing or non-hashing solutions for container serialization solution is open too which is the other big one here.
I think it's better if this is in 9.5.x and 10.0.x as long as possible before release, so committed/pushed/cherry-picked the respective patches to 10.1.x/10.0.x/9.5,x thanks!
Hopefully we can tackle #3299828: Stop storing Settings singleton in object properties next.
Comment #255
neclimdul💙 Wow, that was a journey. Thanks everyone!
Comment #256
andypostThank you! Looks duplicate could be closed #2389193: Provide a way to determine whether a given instance was retrieved from the container
There's following improvements still open
- #2368263: Remove the persist service tag
- #3300306: [PP-1] Drupal ContainerBuilder allows mutation of non-synthetic services
Comment #257
phenaproximaI feel like this issue could benefit from release notes. Although it's not a hugely disruptive change, at least not exactly, I imagine a fair amount of custom and contrib code out there might be relying on
_serviceId, so it might be wise to put this in the API changes section of the release notes.Comment #258
longwaveAdded a short release note snippet.
Comment #259
longwaveComment #260
phenaproximaBeautiful.
Comment #261
samuel.mortensonRunning into serialization issues following this patch. Quickest way to replicate is to run this code in 9.5.x:
Comment #262
catchOK this is because 10.x doesn't have to handle non-object services, which were deprecated in Symfony 4.4 #3074585: [Symfony 5] Replace app.root and site.path string services with container parameters.
But we need to handle them in 9.5.x.
If we hadn't already tagged the 9.5 beta I'd roll this back so we could recommit after with a fix, but given how long and windy this issue is already, I think we could tackle it in a critical follow-up instead. Opened #3310271: Container serialization must handle string services in 9.x.
Comment #264
alexpottThere's some suspicion that this issue has caused a performance regression - see #3327856: Performance regression introduced by container serialization solution