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.

CommentFileSizeAuthor
#249 2531564-249.interdiff.txt971 bytesneclimdul
#249 2531564-249-10.patch40.95 KBneclimdul
#249 2531564-249-9.patch40.99 KBneclimdul
#247 2531564-247-10.patch40.85 KBneclimdul
#247 2531564-247.interdiff.txt830 bytesneclimdul
#247 2531564-247.patch40.89 KBneclimdul
#244 2531564-244-9.patch40.08 KBspokje
#244 interdiff_d9_228-244.txt7.13 KBspokje
#243 2531564-243-10.patch40.04 KBspokje
#243 interdiff_d10_228-243.txt6.44 KBspokje
#242 2531564-242-9.patch40.3 KBspokje
#242 interdiff_d9-228-242.txt6.91 KBspokje
#240 2531564-240-10.patch40.25 KBspokje
#240 interdiff_d10_228-240.txt6.23 KBspokje
#228 2531564-228-9.patch40.38 KBandypost
#228 2531564-228-10.patch40.39 KBandypost
#228 interdiff.txt769 bytesandypost
#226 2531564-226-10.patch40.39 KBandypost
#226 2531564-226-9.patch40.38 KBandypost
#226 interdiff.txt2.46 KBandypost
#216 2531564_216.txt767 bytesghost of drupal past
#197 2531564-197_9.5.x.patch40.42 KBandypost
#197 2531564-197_10.0.x.patch40.43 KBandypost
#197 interdiff-10vs9.txt798 bytesandypost
#197 interdiff.txt1.18 KBandypost
#193 2531564-192.patch41.08 KBandypost
#193 interdiff.txt679 bytesandypost
#191 2531564-190.patch41.08 KBandypost
#191 interdiff.txt3.68 KBandypost
#187 2531564-187.patch40.82 KBandypost
#187 interdiff.txt2.51 KBandypost
#183 2531564-183-10.0.x.patch42.09 KBandypost
#183 interdiff.txt2.53 KBandypost
#181 2531564-181-10.0.x.patch40.25 KBandypost
#181 interdiff.txt764 bytesandypost
#180 2531564-180-10.0.x.patch40.25 KBandypost
#180 interdiff.txt665 bytesandypost
#179 2531564-179-10.0.x.patch40.3 KBandypost
#179 interdiff.txt710 bytesandypost
#178 2531564-178.patch38.79 KBandypost
#178 interdiff.txt861 bytesandypost
#172 2531564-172.patch39.94 KBandypost
#172 interdiff.txt3.73 KBandypost
#170 2531564-170.patch39.89 KBandypost
#170 interdiff.txt3.59 KBandypost
#169 2531564-169-10.0.patch39.69 KBandypost
#169 2531564-169-9.5.patch39.67 KBandypost
#167 2531564-167.patch39.73 KBneclimdul
#166 2531564-165.patch40.4 KBneclimdul
#165 2531564-165.interdiff.txt5.81 KBneclimdul
#165 2531564-165.patch2.78 MBneclimdul
#164 2531564-164.patch40.91 KBneclimdul
#164 2531564-164.interdiff.txt20.9 KBneclimdul
#150 2531564-149.interdiff.txt28.84 KBneclimdul
#149 2531564-149.patch29.15 KBneclimdul
#149 2531564-149.patch29.15 KBneclimdul
#146 interdiff-138_146.txt5.53 KBcburschka
#146 2531564-146.patch34.71 KBcburschka
#138 interdiff-136_138.txt1.83 KBpdenooijer
#138 improve-container-serialization-2531564-138.patch34.67 KBpdenooijer
#137 2931564-136.interdiff.txt4.22 KBneclimdul
#137 2931564-136.patch35.51 KBneclimdul
#135 2531564-134-1.txt37.21 KBneclimdul
#135 2531564-135.interdiff.txt3.2 KBneclimdul
#135 2531564-135.patch39.05 KBneclimdul
#134 2531564-134.patch38.94 KBneclimdul
#134 2531564-134.interdiff.txt4.62 KBneclimdul
#127 2531564-127.interdiff.txt21.89 KBneclimdul
#127 2531564-127.patch40.96 KBneclimdul
#124 our_serialization-2531564-124.patch22.02 KBneclimdul
#116 our_serialization-2531564-116.patch21.76 KBneclimdul
#110 our_serialization-2531564-110.patch21.98 KBneclimdul
#110 our_serialization-2531564-110.interdiff.txt1.2 KBneclimdul
#107 our_serialization-2531564-107.interdiff.txt5.15 KBneclimdul
#107 our_serialization-2531564-107.patch20.78 KBneclimdul
#101 drupal-2531564-101-dependency-serialization.interdiff.patch1.94 KBcburschka
#101 drupal-2531564-101-dependency-serialization.patch18.43 KBcburschka
#97 drupal-2531564-097-dependency-serialization.interdiff.patch611 bytescburschka
#97 drupal-2531564-097-dependency-serialization.patch18.48 KBcburschka
#96 hash_table.txt117.96 KBcburschka
#92 drupal-2531564-092-dependency-serialization.interdiff.patch1.07 KBcburschka
#92 drupal-2531564-092-dependency-serialization.patch18.44 KBcburschka
#89 drupal-2531564-dependency-serialization-89.patch18.32 KBcburschka
#87 drupal-2531564-dependency-serialization-87.patch18.85 KBcburschka
#85 our_serialization-2531564-85.patch17.94 KBpk188
#83 our_serialization-2531564-83.patch17.58 KBpk188
#79 our_serialization-2531564-79.patch18.84 KBneclimdul
#68 2531564_68.patch18.79 KBneclimdul
#66 2531564_66.patch18.94 KBneclimdul
#58 2531564_57.patch18.38 KBchx
#58 interdiff.txt6.9 KBchx
#56 2531564_55.patch15.35 KBchx
#52 2531564_52.patch15.97 KBchx
#50 interdiff.txt2.7 KBchx
#50 2531564_50.patch13.19 KBchx
#41 interdiff.txt2.73 KBchx
#41 2531564_41.patch12.45 KBchx
#38 interdiff.txt5.64 KBchx
#38 2531564_37.patch12.54 KBchx
#35 2531564_35.patch12.5 KBchx
#35 interdiff.txt5.69 KBchx
#33 2531564_33.patch12.51 KBchx
#33 interdiff.txt5.7 KBchx
#21 2531564_21.patch13.45 KBchx
#28 2531564_28.patch12.61 KBchx
#11 2531564_11.patch9.38 KBchx
#28 interdiff.txt1.33 KBchx
#17 2531564_17.patch11.12 KBchx
#12 2531564_12.patch9.04 KBchx
serialization.patch7.01 KBchx

Comments

chx’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, serialization.patch, failed testing.

neclimdul’s picture

It 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.

cilefen’s picture

@chx Is this 5.4 and higher?

larowlan’s picture

Plus 1

chx’s picture

@cilefen yes this is 5.4 and higher.

cilefen’s picture

@chx I thought my question was silly as soon as I typed it.

cilefen’s picture

Title: Our serialization solution breaks the PHP 5.4 property optimization » Our serialization solution breaks the PHP >= 5.4 property optimization
dawehner’s picture

Title: Our serialization solution breaks the PHP >= 5.4 property optimization » Our serialization solution breaks the PHP 5.4 property optimization
Status: Needs work » Needs review

I learned something new today!

Here is a benchmark to see whether | how much this improves things:

=== 8.0.x..8.0.x compared (55a20404acc7e..55a2048e676cb):

ct  : 31,693|31,693|0|0.0%
wt  : 72,797|72,652|-145|-0.2%
mu  : 16,746,536|16,746,536|0|0.0%
pmu : 16,800,352|16,800,352|0|0.0%

=== 8.0.x..2531564 compared (55a20404acc7e..55a2049936f3c):

ct  : 31,693|31,701|8|0.0%
wt  : 72,797|72,265|-532|-0.7%
mu  : 16,746,536|16,602,960|-143,576|-0.9%
pmu : 16,800,352|16,657,096|-143,256|-0.9%

---
ct = function calls, wt = wall time, cpu = cpu time used, mu = memory usage, pmu = peak memory usage

### XHPROF-LIB REPORT

+-------------------+------------+------------+------------+------------+------------+
| namespace         |        min |        max |       mean |     median |       95th |
+-------------------+------------+------------+------------+------------+------------+
| Calls             |            |            |            |            |            |
|                   |            |            |            |            |            |
| 2531564           |     31,701 |     34,361 |     31,728 |     31,701 |     31,701 |
| 8_0_x             |     31,693 |     33,993 |     31,716 |     31,693 |     31,693 |
|                   |            |            |            |            |            |
| Wall time         |            |            |            |            |            |
|                   |            |            |            |            |            |
| 2531564           |     72,265 |    102,852 |     75,836 |     74,694 |     82,105 |
| 8_0_x             |     72,652 |    104,095 |     76,345 |     75,276 |     82,722 |
|                   |            |            |            |            |            |
| Memory usage      |            |            |            |            |            |
|                   |            |            |            |            |            |
| 2531564           | 16,602,912 | 17,155,888 | 16,647,232 | 16,602,960 | 16,996,792 |
| 8_0_x             | 16,746,536 | 17,295,648 | 16,799,315 | 16,746,536 | 17,099,089 |
|                   |            |            |            |            |            |
| Peak memory usage |            |            |            |            |            |
|                   |            |            |            |            |            |
| 2531564           | 16,656,952 | 17,337,896 | 16,702,663 | 16,657,096 | 17,051,120 |
| 8_0_x             | 16,800,352 | 17,476,320 | 16,854,471 | 16,800,352 | 17,153,112 |
|                   |            |            |            |            |            |
+-------------------+------------+------------+------------+------------+------------+

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.

twistor’s picture

+1, I've been meaning to create this issue ever since the serialization issue was fixed.

chx’s picture

StatusFileSize
new9.38 KB

If 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.

chx’s picture

StatusFileSize
new9.04 KB

> [10:26] <dawehner> chx: mh do you really think a static is the only solution?

Thanks for asking! No.

dawehner’s picture

The last submitted patch, 11: 2531564_11.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 12: 2531564_12.patch, failed testing.

Crell’s picture

+++ b/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php
@@ -25,20 +25,36 @@
+    if (isset($container) && $container instanceof SleepHelperInterface) {
+      $hashes = $container->getHashes();
+      foreach ($vars as $key => $value) {
+        if (is_object($value)) {
+          $service_id = FALSE;
+          // Special case the container.
+          if ($value instanceof ContainerInterface) {
+            $service_id = 'service_container';
+          }
+          else {
+            $hash = spl_object_hash($value);
+            if (isset($hashes[$hash])) {
+              $service_id = $hashes[$hash];
+            }
+          }
+          if ($service_id) {
+            // If a class member was instantiated by the dependency injection
+            // container, only store its ID so it can be used to get a fresh object
+            // on unserialization.
+            $this->_serviceIds[$key] = $service_id;
+            unset($vars[$key]);
+          }
+        }

Seems 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 ¯\_(ツ)_/¯ ).

chx’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new11.12 KB

Removed tests trying to access _serviceId. Good riddance. Added tests for the new getHashes function. Moved everything into the try.

chx’s picture

Issue summary: View changes
fabianx’s picture

+++ b/core/lib/Drupal/Core/DependencyInjection/SleepHelperTrait.php
@@ -0,0 +1,39 @@
+  public function getHashes() {
+    foreach ($this->services as $id => $service) {
+      if (is_object($service)) {
+        $this->hashes[spl_object_hash($service)] = $id;
+      }
+    }

If 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?

chx’s picture

We 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.

chx’s picture

StatusFileSize
new13.45 KB

Looking 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.

dawehner’s picture

Priority: Normal » Major
Issue summary: View changes
Issue tags: +Performance

We 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.

Don't we want to add some documentation which explains that into the patch itself?

  1. +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
    @@ -1035,21 +1032,4 @@ public function alreadyImporting() {
    -  /**
    -   * Gets all the service dependencies from \Drupal.
    -   *
    -   * Since the ConfigImporter handles module installation the kernel and the
    -   * container can be rebuilt and altered during processing. It is necessary to
    -   * keep the services used by the importer in sync.
    -   */
    -  protected function reInjectMe() {
    -    $this->_serviceIds = array();
    -    $vars = get_object_vars($this);
    -    foreach ($vars as $key => $value) {
    -      if (is_object($value) && isset($value->_serviceId)) {
    -        $this->$key = \Drupal::service($value->_serviceId);
    -      }
    -    }
    -  }
    -
    

    Nice!! One less hack

  2. +++ b/core/lib/Drupal/Core/DependencyInjection/SleepHelperInterface.php
    @@ -0,0 +1,21 @@
    +
    +
    

    Nitpcik: 2 lines

  3. +++ b/core/lib/Drupal/Core/DependencyInjection/SleepHelperInterface.php
    @@ -0,0 +1,21 @@
    +
    +interface SleepHelperInterface {
    +
    

    Needs some helpful docs. Maybe some high level overview.

  4. +++ b/core/lib/Drupal/Core/DependencyInjection/SleepHelperTrait.php
    @@ -0,0 +1,39 @@
    + * @file
    + * Contains
    + */
    

    Missing FQCN

  5. +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/ContainerTest.php
    index ad05400..bd7fe01 100644
    --- a/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTest.php
    
    --- a/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTest.php
    +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTest.php
    

    It is pretty cool that this continues to work!

fabianx’s picture

While 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.

znerol’s picture

I'm all for removing _serviceId, introducing that was a terrible idea in the first place.

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.

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 reinjectMe is 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.

chx’s picture

It 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 persist service 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?

znerol’s picture

I presume some services are holding state which gets lost when rebuilt but we have added the persist service tag just for that purpose very long ago.

The 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.

fabianx’s picture

Also 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.

chx’s picture

StatusFileSize
new1.33 KB
new12.61 KB

I reimplemented reinjectMe. I doubt it could get any simpler... and, I believe, it should work with all implementations of DependencySerializationTrait .

fabianx’s picture

Issue tags: +needs profiling

Overall looks great!

  1. +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
    @@ -1043,13 +1043,8 @@ public function alreadyImporting() {
    +    $this->__sleep();
    +    $this->__wakeup();
    

    lol :)

  2. +++ b/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php
    @@ -25,22 +25,38 @@
    +              // If a class member was instantiated by the dependency injection
    +              // container, only store its ID so it can be used to get a fresh object
    +              // on unserialization.
    +              $this->_serviceIds[$key] = $service_id;
    +              unset($vars[$key]);
    

    Should we not store _serviceIds in $vars?

    That feels a little strange ...

    Would that also break the property optimization?

  3. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -755,6 +755,8 @@ protected function initializeContainer() {
    +      // Save the current hashes.
    +      $hashes = $this->container->getHashes();
    

    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.

  4. +++ b/core/modules/system/src/Form/ModulesListForm.php
    @@ -103,6 +103,13 @@ class ModulesListForm extends FormBase {
       /**
    +   * The access manager.
    +   *
    +   * @var \Drupal\Core\Access\AccessManagerInterface
    +   */
    +  protected $accessManager;
    

    This was just missed before I assume?

chx’s picture

> 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: _serviceIds will 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)

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for answering my feedback.

This is RTBC :).

benjy’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/DependencyInjection/SleepHelperTrait.php
@@ -0,0 +1,39 @@
+        $this->hashes[spl_object_hash($service)] = $id;

How about SplObjectStorage?

chx’s picture

Status: Needs work » Needs review
StatusFileSize
new5.7 KB
new12.51 KB

I 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.

Status: Needs review » Needs work

The last submitted patch, 33: 2531564_33.patch, failed testing.

chx’s picture

StatusFileSize
new5.69 KB
new12.5 KB

The above has a copypaste error. Interdiff against #28.

chx’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 35: 2531564_35.patch, failed testing.

chx’s picture

StatusFileSize
new12.54 KB
new5.64 KB
chx’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 38: 2531564_37.patch, failed testing.

chx’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new12.45 KB
new2.73 KB

I 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.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 41: 2531564_41.patch, failed testing.

chx’s picture

Status: Needs work » Reviewed & tested by the community

OK, 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.

dawehner’s picture

I was wrong ; sorry ; the SplObjectStorage does have a runaway memory problem -- storing just the hashes instead of the objects solves it.

Oh yeah that makes sense.

benjy’s picture

Yeah that does make sense, shame the code was cleaner but alas, +1

znerol’s picture

  1. +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
    @@ -1043,13 +1043,8 @@ public function alreadyImporting() {
    +    $this->__sleep();
    +    $this->__wakeup();
    

    I 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 __sleep operates 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).

  2. +++ b/core/lib/Drupal/Core/DependencyInjection/SleepHelperInterface.php
    @@ -0,0 +1,21 @@
    +interface SleepHelperInterface {
    

    Documentation missing.

  3. +++ b/core/lib/Drupal/Core/DependencyInjection/SleepHelperTrait.php
    @@ -0,0 +1,39 @@
    +/**
    + * @file
    + * Contains
    + */
    

    What?

  4. +++ b/core/lib/Drupal/Core/DependencyInjection/SleepHelperTrait.php
    @@ -0,0 +1,39 @@
    +trait SleepHelperTrait {
    

    Should have a more specific name which makes it clear, that this is container internals. E.g. ContainerSleepHelperTrait.

fabianx’s picture

#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.

znerol’s picture

I 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?

dawehner’s picture

So to be fair this is at least not RTBC at the moment.

+++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
@@ -1043,13 +1043,8 @@ public function alreadyImporting() {
   protected function reInjectMe() {
...
+    $this->__sleep();
+    $this->__wakeup();

This should at least explain how this exactly works / vs. the limitations as described in #47.1

chx’s picture

StatusFileSize
new13.19 KB
new2.7 KB

Added comments, renamed interface and trait.

pwolanin’s picture

Status: Reviewed & tested by the community » Needs review

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?

chx’s picture

StatusFileSize
new15.97 KB

> 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.

Status: Needs review » Needs work

The last submitted patch, 52: 2531564_52.patch, failed testing.

dawehner’s picture

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?

It is not only that, its also that contrib/custom could easily miss that.

andypost’s picture

+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -755,6 +774,8 @@ protected function initializeContainer() {
+      // Save the current hashes.
+      $this->getServiceIdMapping();

+++ b/core/lib/Drupal/Core/DrupalKernelInterface.php
@@ -134,4 +134,9 @@ public function preHandle(Request $request);
+   * Get a mapping from service hashes to service IDs.
+   */
+  public function getServiceIdMapping();

why getter saves any hashes?
maybe better change name of method?

chx’s picture

Status: Needs work » Needs review
StatusFileSize
new15.35 KB

Filed #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.

Status: Needs review » Needs work

The last submitted patch, 56: 2531564_55.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
StatusFileSize
new6.9 KB
new18.38 KB

Patched 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. ).

znerol’s picture

let's agree on trying very, very hard not to put phpwtf.org worth code in runtime. Tests are different.

As 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?

chx’s picture

At 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.

chx’s picture

Status: Needs review » Closed (won't fix)

On 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.

znerol’s picture

Status: Closed (won't fix) » Needs review

It does not make sense to close this issue because it has more substantial conversation and more followers than the other one.

chx’s picture

If 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.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

I would still like to see this issue going somewhere :(

neclimdul’s picture

Version: 8.1.x-dev » 8.2.x-dev
StatusFileSize
new18.94 KB

Agreed. 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.

Status: Needs review » Needs work

The last submitted patch, 66: 2531564_66.patch, failed testing.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new18.79 KB

Another reroll. Trying to figure out what the failure in search still because I'm having trouble recreating it.

Status: Needs review » Needs work

The last submitted patch, 68: 2531564_68.patch, failed testing.

neclimdul’s picture

#2380293: Properly inject services into ModuleInstaller unfortunately started using this non-api and needs to be unraveled now before this can be committed.

The last submitted patch, 68: 2531564_68.patch, failed testing.

neclimdul’s picture

Ok 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 784

That line:

    \Drupal::service('config.installer')
      ->setSourceStorage($this->storageComparer->getSourceStorage());

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:

When an object is destroyed, its hash may be reused for other objects.

That seem OK for our purposes really and the object clearly isn't destroyed if its being used right? So... what's going on?

The last submitted patch, 68: 2531564_68.patch, failed testing.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

The last submitted patch, 68: 2531564_68.patch, failed testing.

alexpott’s picture

Discussed 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

The last submitted patch, 66: 2531564_66.patch, failed testing.

neclimdul’s picture

I 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.

neclimdul’s picture

StatusFileSize
new18.84 KB

Just kinda a blind reroll to see what testbot thinks.

neclimdul’s picture

Status: Needs work » Needs review

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Status: Needs review » Needs work

The last submitted patch, 79: our_serialization-2531564-79.patch, failed testing.

pk188’s picture

Status: Needs work » Needs review
StatusFileSize
new17.58 KB

Re rolled.

Status: Needs review » Needs work

The last submitted patch, 83: our_serialization-2531564-83.patch, failed testing.

pk188’s picture

Status: Needs work » Needs review
StatusFileSize
new17.94 KB

Re rolled.

Status: Needs review » Needs work

The last submitted patch, 85: our_serialization-2531564-85.patch, failed testing.

cburschka’s picture

Status: Needs work » Needs review
StatusFileSize
new18.85 KB

The last two patches are incomplete, as they're missing the IntrospectableContainerInterface.

Status: Needs review » Needs work

The last submitted patch, 87: drupal-2531564-dependency-serialization-87.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

cburschka’s picture

Status: Needs work » Needs review
StatusFileSize
new18.32 KB

The IntrospectableContainerInterface actually was already deprecated and is removed in 3.0, so we should be extending the ContainerInterface instead.

Status: Needs review » Needs work

The last submitted patch, 89: drupal-2531564-dependency-serialization-89.patch, failed testing. View results

cburschka’s picture

Note 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.

cburschka’s picture

Status: Needs work » Needs review
StatusFileSize
new18.44 KB
new1.07 KB

If 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_container to $this and 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.

The last submitted patch, 92: drupal-2531564-092-dependency-serialization.patch, failed testing. View results

cburschka’s picture

Status: Needs review » Needs work

Yes, 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.

(
    [label] => drupal.proxy_original_service.plugin.cache_clearer
)
cburschka’s picture

WTF.

Added the following to Drupal\Core\Plugin\Context\ContextDefinition:

  public function __sleep() {
    $mapping = \Drupal::service('kernel')->getServiceIdMapping();
    assert(FALSE,
      $this->label . "\n" .
      spl_object_hash($this->label) . "\n" .
      spl_object_hash(\Drupal::service('plugin.cache_clearer')) . "\n" .
      $mapping[spl_object_hash($this->label)] . "\n" .
      $mapping[spl_object_hash(\Drupal::service('plugin.cache_clearer'))]
    );
    return parent::__sleep();
  }

Got out the following:

000000007c5c204f0000000007b14bfc
000000007c5c23610000000007b14bfc
drupal.proxy_original_service.plugin.cache_clearer
drupal.proxy_original_service.plugin.cache_clearer

These two hashes are 0x32e0000000000000000 apart and yet they return the same array value.

cburschka’s picture

StatusFileSize
new117.96 KB

It 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.

cburschka’s picture

Status: Needs work » Needs review
StatusFileSize
new18.48 KB
new611 bytes

Emptying 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.

The last submitted patch, 97: drupal-2531564-097-dependency-serialization.patch, failed testing. View results

cburschka’s picture

Rebuilding 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?

cburschka’s picture

Status: Needs review » Needs work

From the docs:

This id can be used as a hash key for storing objects, or for identifying an object, as long as the object is not destroyed. Once the object is destroyed, its hash may be reused for other objects.

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:

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.

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.

cburschka’s picture

Status: Needs work » Needs review
StatusFileSize
new18.43 KB
new1.94 KB

New 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.

The last submitted patch, 101: drupal-2531564-101-dependency-serialization.patch, failed testing. View results

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

neclimdul’s picture

So why are we using spl here? It would be more reliable to only use the class name.

We 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.

The last submitted patch, 101: drupal-2531564-101-dependency-serialization.patch, failed testing. View results

neclimdul’s picture

Lets give this a shot. Its not pretty but I'm curious what the outcome is.

Status: Needs review » Needs work

The last submitted patch, 107: our_serialization-2531564-107.patch, failed testing. View results

neclimdul’s picture

So.. 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

neclimdul’s picture

StatusFileSize
new1.2 KB
new21.98 KB

Hopefully we can just convert this to the service map on the kernel. :pray:

neclimdul’s picture

Status: Needs work » Needs review
andypost’s picture

+++ b/core/modules/views/src/ViewExecutable.php
@@ -2488,8 +2489,8 @@ public function __sleep() {
+      'views_data' => \Drupal::service('kernel')->getServiceIdMapping()[DrupalKernel::generateServiceIdHash($this->viewsData)],
+      'route_provider' => \Drupal::service('kernel')->getServiceIdMapping()[DrupalKernel::generateServiceIdHash($this->routeProvider)],

+++ b/core/tests/Drupal/Tests/Core/DrupalKernel/DrupalKernelTest.php
@@ -192,6 +195,25 @@ public function testFindSitePath() {
+      $this->assertEquals($container->get('kernel')->getServiceIdMapping()[DrupalKernel::generateServiceIdHash($service)], 'bar');
...
+      $this->assertNotEquals(DrupalKernel::generateServiceIdHash($service1), DrupalKernel::generateServiceIdHash($service2));

maybe move that to some helper reusable for contrib?

neclimdul’s picture

I 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.

cburschka’s picture

Coming back to this: Would it be feasible to instead make services implement a ServiceInterface with 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 to Drupal\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.

neclimdul’s picture

Its 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.

neclimdul’s picture

StatusFileSize
new21.76 KB

Re-roll of the current solution. Just fixes a use conflict.

Status: Needs review » Needs work

The last submitted patch, 116: our_serialization-2531564-116.patch, failed testing. View results

neclimdul’s picture

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

hchonov’s picture

The 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.

neclimdul’s picture

Title: Our serialization solution breaks the PHP 5.4 property optimization » [PP-1] Our serialization solution breaks the PHP 5.4 property optimization
Status: Needs work » Postponed
Mixologic’s picture

Curious 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.

neclimdul’s picture

Title: [PP-1] Our serialization solution breaks the PHP 5.4 property optimization » [PP-1] Fix leaky and brittle container serialization solution
Issue summary: View changes
Issue tags: -Performance, -needs profiling, -Needs tests
StatusFileSize
new22.02 KB

Honestly 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.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

neclimdul’s picture

Status: Postponed » Needs review
StatusFileSize
new40.96 KB
new21.89 KB

Reroll. 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.

Status: Needs review » Needs work

The last submitted patch, 127: 2531564-127.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

dawehner’s picture

While reading the patch I'm wondering about the following things:

  1. This doesn't resolve the general weirdness that we need this crazy rehydration
  2. This increases the surface area quite a bit: Previously we had mostly just two places, now we have 3 places plus a public API. One thing I see directly is that the kernel and the container is aware of things now. Is this something which could be improved? Could we move getServiceIdMapping into the service container.

Here is some nitpick review:

  1. +++ b/core/lib/Drupal/Core/DependencyInjection/ContainerInterface.php
    @@ -0,0 +1,19 @@
    +   *
    +   * @return array An array of all defined service ids
    +   */
    

    Nitpick: This doesn't comply with our documentation standard. The comment should be on the next line

  2. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -827,6 +832,24 @@ public function updateModules(array $module_list, array $module_filenames = [])
    +    return hash('sha256', get_class($object) . spl_object_hash($object));
    

    I'm curious how get_class makes this more unique. Is there a reason for this? Maybe there could be an inline comment about that.

neclimdul’s picture

Yeah, 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...

dawehner’s picture

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".

Wow! Thank you for the explanation! I think it would be really valueable to document this in the code.

mxr576’s picture

neclimdul’s picture

Yeah, 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.

neclimdul’s picture

Title: [PP-1] Fix leaky and brittle container serialization solution » Fix leaky and brittle container serialization solution
Version: 8.9.x-dev » 9.1.x-dev
Status: Needs work » Needs review
StatusFileSize
new4.62 KB
new38.94 KB

I 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

neclimdul’s picture

StatusFileSize
new39.05 KB
new3.2 KB
new37.21 KB

woops, 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.

Status: Needs review » Needs work

The last submitted patch, 135: 2531564-135.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new35.51 KB
new4.22 KB

Ok, 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.

pdenooijer’s picture

Reviewed and tested patch 136, seems to work fine and is even slightly faster.
I have two comments:

  • The change to ContainerInjectionInterface is not really needed
  • ViewExecutable getServiceIdMapping array can be reused

Uploaded a new patch with above two comments fixed.

Status: Needs review » Needs work

The last submitted patch, 138: improve-container-serialization-2531564-138.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

pdenooijer’s picture

Status: Needs work » Needs review

Bug in the ether it seems... green now :).

neclimdul’s picture

Thanks 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)

pdenooijer’s picture

If 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?

mxr576’s picture

Status: Needs review » Reviewed & tested by the community

I 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.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record
  1. I like this change. The container mutating the state of container services was a pragmatic solution but this solution is better.
  2. +++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
    @@ -46,6 +46,8 @@ class OptimizedPhpArrayDumper extends Dumper {
    +  protected $aliases = [];
    +
    

    Unrelated change.

  3. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -819,6 +824,29 @@ public function updateModules(array $module_list, array $module_filenames = [])
    +    // Include class name as an additional namespace for the hash since
    +    // spl_object_hash's return can be recycled. This still is not a 100%
    +    // guarantee to be unique but makes collisions incredibly difficult and even
    +    // then the interface would be preserved.
    +    // @see https://php.net/spl_object_hash#refsect1-function.spl-object-hash-notes
    +    return hash('sha256', get_class($object) . spl_object_hash($object));
    

    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?

  4. +++ b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php
    @@ -8,6 +8,7 @@
    +  use Drupal\Tests\Traits\ExpectDeprecationTrait;
    
    @@ -24,6 +25,8 @@
    +    use ExpectDeprecationTrait;
    +
    

    I think this is not necessary.

  5. +++ b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
    @@ -63,7 +64,7 @@ protected function setUp(): void {
    -    $container = new ContainerBuilder();
    +    $container = TestKernel::setContainerWithKernel();
    

    This change is tricky from a BC perspective.

  6. Some coding standards stuff
    FILE: /Users/alex/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php
    -----------------------------------------------------------------------------------------------------------------------------------------------------
    FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
    -----------------------------------------------------------------------------------------------------------------------------------------------------
     1 | WARNING | No PHP code was found in this file and short open tags are not allowed by this install of PHP. This file may be using short open tags
       |         | but PHP does not allow them.
    -----------------------------------------------------------------------------------------------------------------------------------------------------
    
    FILE: /Users/alex/dev/sites/drupal8alt.dev/core/lib/Drupal/Core/DrupalKernel.php
    --------------------------------------------------------------------------------
    FOUND 1 ERROR AFFECTING 1 LINE
    --------------------------------------------------------------------------------
     831 | ERROR | [x] Separate the @param and @return sections by a blank line.
    --------------------------------------------------------------------------------
    PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
    --------------------------------------------------------------------------------
    
    FILE: /Users/alex/dev/sites/drupal8alt.dev/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTest.php
    ---------------------------------------------------------------------------------------------------------------------------
    FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
    ---------------------------------------------------------------------------------------------------------------------------
     10 | WARNING | [x] Unused use statement
    ---------------------------------------------------------------------------------------------------------------------------
    PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
    ---------------------------------------------------------------------------------------------------------------------------
    
    FILE: /Users/alex/dev/sites/drupal8alt.dev/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
    -------------------------------------------------------------------------------------------------
    FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
    -------------------------------------------------------------------------------------------------
     10 | WARNING | [x] Unused use statement
    -------------------------------------------------------------------------------------------------
    PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
    -------------------------------------------------------------------------------------------------
    
    FILE: /Users/alex/dev/sites/drupal8alt.dev/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
    -----------------------------------------------------------------------------------------------------
    FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
    -----------------------------------------------------------------------------------------------------
     19 | WARNING | [x] Unused use statement
    -----------------------------------------------------------------------------------------------------
    PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
    -----------------------------------------------------------------------------------------------------
    
    FILE: /Users/alex/dev/sites/drupal8alt.dev/core/tests/Drupal/Tests/Core/TempStore/SharedTempStoreTest.php
    ---------------------------------------------------------------------------------------------------------
    FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
    ---------------------------------------------------------------------------------------------------------
     10 | WARNING | [x] Unused use statement
    ---------------------------------------------------------------------------------------------------------
    PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
    ---------------------------------------------------------------------------------------------------------
    
  7. +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
    @@ -1056,13 +1056,13 @@ public function alreadyImporting() {
    -    $this->_serviceIds = [];
    -    $vars = get_object_vars($this);
    -    foreach ($vars as $key => $value) {
    -      if (is_object($value) && isset($value->_serviceId)) {
    -        $this->$key = \Drupal::service($value->_serviceId);
    -      }
    -    }
    +    // When rebuilding the container,
    +    // \Drupal\Core\DrupalKernel::initializeContainer() saves the hashes of the
    +    // old container and passes them to the new one. So __sleep() will
    +    // recognize the old services and then __wakeup() will restore them from
    +    // the new container.
    +    $this->__sleep();
    +    $this->__wakeup();
    

    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.

  8. +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php
    @@ -1,28 +0,0 @@
    -class DependencySerializationTraitPass implements CompilerPassInterface {
    

    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.

  9. +++ b/core/modules/views/src/ViewExecutable.php
    @@ -2488,10 +2489,11 @@ public function getDependencies() {
    -      'views_data' => $this->viewsData->_serviceId,
    -      'route_provider' => $this->routeProvider->_serviceId,
    +      'views_data' => $serviceIdMapping[DrupalKernel::generateServiceIdHash($this->viewsData)],
    +      'route_provider' => $serviceIdMapping[DrupalKernel::generateServiceIdHash($this->routeProvider)],
    

    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 :(

cburschka’s picture

Does the hashing buy us anything over than shorter keys which are meaningless and more compute time?

No 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.

cburschka’s picture

StatusFileSize
new34.71 KB
new5.53 KB

This one addresses 2), 4), 6) and 8) of #144.

neclimdul’s picture

There's at least one outstanding review I don't think has really been addressed which kept me from RTBCing this.
from #129

This increases the surface area quite a bit: Previously we had mostly just two places, now we have 3 places plus a public API. One thing I see directly is that the kernel and the container is aware of things now. Is this something which could be improved? Could we move getServiceIdMapping into the service container.

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.

cburschka’s picture

(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.)

neclimdul’s picture

Issue summary: View changes
StatusFileSize
new29.15 KB
new29.15 KB

Ok 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\Container needs 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:

Exception:
/app/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php:514
...

After:

Exception: Serialize should not be called.
/app/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php:514
...

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.

neclimdul’s picture

StatusFileSize
new28.84 KB

bah, I clicked the wrong file I guess. here's the interdiff.

neclimdul’s picture

Those 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 #1
3) 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 :(

cburschka’s picture

Oh 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?

neclimdul’s picture

Pretty 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.

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.

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.

bradjones1’s picture

neclimdul’s picture

I 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

andypost’s picture

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

As I see voting will be Yes, so it needs reroll/revamp

neclimdul’s picture

Yes, 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.

andypost’s picture

Related issues: +#2016679: Expand Entity Type interfaces to provide methods, protect the properties

related plan has number of issues to clean-up and cover by phpunit the missing/undefined properties

andypost’s picture

andypost’s picture

bradjones1’s picture

The dynamic properties RFC is approved and going into PHP 8.2, so this is definitely affected. Adding related issue which depends on this property.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new20.9 KB
new40.91 KB

Man 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.

neclimdul’s picture

StatusFileSize
new2.78 MB
new5.81 KB
FILE: /app/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 13 | WARNING | The @see url '\Drupal\Core\DependencyInjection\DependencySerializationTrait' does not match the standard: http(s)://www.drupal.org/node/n or http(s)://www.drupal.org/project/aaa/issues/n
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

That'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.

neclimdul’s picture

StatusFileSize
new40.4 KB

oh jeees I diffed against 10. fingers... come on.

neclimdul’s picture

StatusFileSize
new39.73 KB

I am running test's locally. ugh.

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.

andypost’s picture

StatusFileSize
new39.67 KB
new39.69 KB

Here'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

[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\sqlite\Driver\Database\sqlite\Connection::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Logger\LogMessageParser::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\dblog\Logger\DbLog::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Logger\LoggerChannelFactory::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Session\AccountProxy::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Site\Settings::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Cache\CacheFactory::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Cache\ChainedFastBackendFactory::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Cache\DatabaseCacheTagsChecksum::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Cache\DatabaseBackendFactory::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Cache\ApcuBackendFactory::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Cache\ChainedFastBackend::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Config\CachedStorage::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Config\ExtensionInstallStorage::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Cache\ChainedFastBackend::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Cache\ChainedFastBackend::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Extension\ModuleHandler::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\DependencyInjection\ClassResolver::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property ArrayObject::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Validation\ConstraintManager::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Config\TypedConfigManager::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
[24-Jun-2022 15:55:56] WARNING: [pool app] child 218 said into stderr: "NOTICE: PHP message: PHP Deprecated:  Creation of dynamic property Drupal\Core\Config\ConfigFactory::$_serviceId is deprecated in /var/www/html/web/core/lib/Drupal/Component/DependencyInjection/Container.php on line 288"
172.30.1.3 -  24/Jun/2022:15:55:56 +0000 "POST /index.php" 200

andypost’s picture

StatusFileSize
new3.59 KB
new39.89 KB
  1. +++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
    @@ -432,6 +432,7 @@ protected function dumpValue($value) {
    +      @trigger_error('_serviceId is deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. See https://www.drupal.org/project/drupal/issues/2531564.', E_USER_DEPRECATED);
    
    +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php
    @@ -8,6 +8,9 @@
    + * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. The _serviceId property is no longer part of the container.
    + * @see https://www.drupal.org/project/drupal/issues/2531564
    

    updated to 9.5 and filed CR https://www.drupal.org/node/3292540

  2. +++ b/core/modules/views/src/ViewExecutable.php
    @@ -2490,10 +2490,12 @@ public function getDependencies() {
    +    $container = \Drupal::getContainer();
    +    $serviceIdMapping = \Drupal::service('kernel')->getServiceIdMapping();
    ...
    -      'views_data' => $this->viewsData->_serviceId,
    -      'route_provider' => $this->routeProvider->_serviceId,
    +      'views_data' => $serviceIdMapping[$container->generateServiceIdHash($this->viewsData)],
    +      'route_provider' => $serviceIdMapping[$container->generateServiceIdHash($this->routeProvider)],
    

    changed to approach from #2987089: Remove views.module's direct use of the _serviceId property

Status: Needs review » Needs work

The last submitted patch, 170: 2531564-170.patch, failed testing. View results

andypost’s picture

Status: Needs work » Needs review
StatusFileSize
new3.73 KB
new39.94 KB

Fixed deprecation message, meantime it means that \Drupal\Tests\Component\DrupalComponentTest is fragile

znerol’s picture

I 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):

      $container = \Drupal::getContainer();
      $mapping = \Drupal::service('kernel')->getServiceIdMapping();
      foreach ($vars as $key => $value) {
        [...]
            $id = $container->generateServiceIdHash($value);
            if (isset($mapping[$id])) {
              $service_id = $mapping[$id];
            }
          }
          if ($service_id) {
        [...]

I'd prefer something like this:

      $container = \Drupal::getContainer();
      foreach ($vars as $key => $value) {
        [...]
            $service_id = \Drupal::service('kernel')->getServiceId($value);
          }
          if ($service_id) {
        [...]

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 _serviceId property before.

znerol’s picture

Another approach would be:

      $container = \Drupal::getContainer();
      $lookup = \Drupal::service('kernel')->getReverseServiceLookup();
      foreach ($vars as $key => $value) {
        [...]
            $service_id = $lookup->getServiceId($value);
          }
          if ($service_id) {
        [...]

Like this it is still possible to update the lookup table in getReverseServiceLookup (i.e. outside the for loop).

andypost’s picture

IMO #174 looks better

andypost’s picture

alexpott’s picture

Status: Needs review » Needs work

Are 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.

andypost’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record
StatusFileSize
new861 bytes
new38.79 KB

Re-roll after #2987089: Remove views.module's direct use of the _serviceId property

If we need _serviceId on 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)

andypost’s picture

StatusFileSize
new710 bytes
new40.3 KB

Patch for 10.0.x to test #[\AllowDynamicProperties]

andypost’s picture

StatusFileSize
new665 bytes
new40.25 KB
andypost’s picture

StatusFileSize
new764 bytes
new40.25 KB

Not clear why phpStan throws on php 8.2 - trying without \

  Line   core/lib/Drupal/Core/Extension/Extension.php             
 ------ --------------------------------------------------------- 
  13     Class AllowDynamicProperties is not an Attribute class.  

The last submitted patch, 180: 2531564-180-10.0.x.patch, failed testing. View results

andypost’s picture

StatusFileSize
new2.53 KB
new42.09 KB

Filed 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

andypost’s picture

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

The 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

Status: Needs review » Needs work

The last submitted patch, 183: 2531564-183-10.0.x.patch, failed testing. View results

berdir’s picture

The 8.2 run seems to be stuck again at a kernel test? No new output for > 1h hour now.

andypost’s picture

Status: Needs work » Needs review
StatusFileSize
new2.51 KB
new40.82 KB

Fix 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

andypost’s picture

@Berdir I used to run the test locally and get following output, OTOH I'm using php82 from alpinelinux

Runtime:       PHP 8.2.0alpha3
Configuration: /var/www/html/web/core/phpunit.xml.dist

Testing /var/www/html/web/core/modules/views/tests/src/Kernel/EventSubscriber


Time: 02:06.852, Memory: 12.00 MB

OK (12 tests, 951 assertions)

Unsilenced deprecation notices (18182)

  8700x: Creation of dynamic property Drupal\Core\Extension\Extension::$subpath is deprecated
    725x in ViewsEntitySchemaSubscriberIntegrationTest::testDeleteEntityType from Drupal\Tests\views\Kernel\EventSubscriber
    725x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    725x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    725x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    725x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  8700x: Creation of dynamic property Drupal\Core\Extension\Extension::$origin is deprecated
    725x in ViewsEntitySchemaSubscriberIntegrationTest::testDeleteEntityType from Drupal\Tests\views\Kernel\EventSubscriber
    725x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    725x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    725x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    725x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  362x: Creation of dynamic property Drupal\Core\Config\Entity\Query\Query::$conjunction is deprecated
    40x in ViewsEntitySchemaSubscriberIntegrationTest::testVariousTableUpdates from Drupal\Tests\views\Kernel\EventSubscriber
    32x in ViewsEntitySchemaSubscriberIntegrationTest::testVariousTableUpdatesForRevisionView from Drupal\Tests\views\Kernel\EventSubscriber
    31x in ViewsEntitySchemaSubscriberIntegrationTest::testDeleteEntityType from Drupal\Tests\views\Kernel\EventSubscriber
    30x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    30x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  132x: Creation of dynamic property Drupal\Core\Datetime\Entity\DateFormat::$original is deprecated
    11x in ViewsEntitySchemaSubscriberIntegrationTest::testDeleteEntityType from Drupal\Tests\views\Kernel\EventSubscriber
    11x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    11x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    11x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    11x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  74x: Creation of dynamic property Drupal\views\Entity\View::$original is deprecated
    16x in ViewsEntitySchemaSubscriberIntegrationTest::testVariousTableUpdates from Drupal\Tests\views\Kernel\EventSubscriber
    8x in ViewsEntitySchemaSubscriberIntegrationTest::testVariousTableUpdatesForRevisionView from Drupal\Tests\views\Kernel\EventSubscriber
    7x in ViewsEntitySchemaSubscriberIntegrationTest::testDeleteEntityType from Drupal\Tests\views\Kernel\EventSubscriber
    6x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    6x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  60x: Creation of dynamic property Drupal\system\Entity\Menu::$original is deprecated
    5x in ViewsEntitySchemaSubscriberIntegrationTest::testDeleteEntityType from Drupal\Tests\views\Kernel\EventSubscriber
    5x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    5x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    5x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    5x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  60x: Creation of dynamic property Drupal\Core\Entity\Query\Sql\Query::$conjunction is deprecated
    31x in ViewsEntitySchemaSubscriberIntegrationTest::testVariousTableUpdates from Drupal\Tests\views\Kernel\EventSubscriber
    6x in ViewsEntitySchemaSubscriberIntegrationTest::testVariousTableUpdatesForRevisionView from Drupal\Tests\views\Kernel\EventSubscriber
    4x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionDisabling from Drupal\Tests\views\Kernel\EventSubscriber
    3x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    3x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  34x: Creation of dynamic property Drupal\views\Entity\View::$_updated is deprecated
    13x in ViewsEntitySchemaSubscriberIntegrationTest::testVariousTableUpdates from Drupal\Tests\views\Kernel\EventSubscriber
    5x in ViewsEntitySchemaSubscriberIntegrationTest::testVariousTableUpdatesForRevisionView from Drupal\Tests\views\Kernel\EventSubscriber
    3x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    3x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    3x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  24x: Creation of dynamic property org\bovigo\vfs\vfsStreamWrapper::$context is deprecated
    2x in ViewsEntitySchemaSubscriberIntegrationTest::testDeleteEntityType from Drupal\Tests\views\Kernel\EventSubscriber
    2x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    2x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    2x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    2x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  12x: Creation of dynamic property Drupal\Core\Config\Entity\Query\QueryFactory::$keyValueFactory is deprecated
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testDeleteEntityType from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  12x: Creation of dynamic property Drupal\Core\Config\Entity\Query\QueryFactory::$configManager is deprecated
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testDeleteEntityType from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

  12x: Creation of dynamic property Drupal\Core\Entity\EntityResolverManager::$entityTypes is deprecated
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testDeleteEntityType from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionBaseTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    1x in ViewsEntitySchemaSubscriberIntegrationTest::testRevisionDataTableRename from Drupal\Tests\views\Kernel\EventSubscriber
    ...

Process finished with exit code 1
catch’s picture

Overall this looks really encouraging.

  1. +++ b/core/lib/Drupal/Component/DependencyInjection/ContainerInterface.php
    @@ -0,0 +1,39 @@
    +
    +/**
    + * Documents the existing getServiceIds method.
    + */
    

    This doesn't seem right for the interface docs.

  2. +++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
    @@ -429,6 +429,7 @@ protected function dumpValue($value) {
         elseif (is_object($value)) {
           // Drupal specific: Instantiated objects have a _serviceId parameter.
    +      @trigger_error('_serviceId is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. Use DrupalKernelInterface::getServiceIdMapping() instead. See https://www.drupal.org/node/3292540', E_USER_DEPRECATED);
           if (isset($value->_serviceId)) {
             return $this->getReferenceCall($value->_serviceId);
    

    Could the trigger_error() be inside the isset()? Should we put the value of _serviceId in the deprecation message?

  3. +++ b/core/lib/Drupal/Component/DependencyInjection/ServiceIdHashTrait.php
    @@ -0,0 +1,37 @@
    +    $mapping = [];
    +    foreach ($this->getServiceIds() as $service_id) {
    +      if ($this->initialized($service_id) && $service_id != 'service_container') {
    +        $mapping[$this->generateServiceIdHash($this->get($service_id))] = $service_id;
    +      }
    

    Could use !== here I think.

  4. +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php
    @@ -8,6 +8,11 @@
      *
    + * @deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. The _serviceId
    + *   property is no longer part of the container. Use
    + *   DrupalKernelInterface::getServiceIdMapping() instead.
    + *
    

    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.

  5. +++ b/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php
    @@ -30,33 +31,50 @@ trait DependencySerializationTrait {
    +          // If a class member is an entity storage, only store the entity type
    +          // ID the storage is for so it can be used to get a fresh object on
    +          // unserialization. By doing this we prevent possible memory leaks
    

    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.

andypost’s picture

Addressed #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() vs getServiceId() is wrong as container should give a hash (runtime object hash)

/cc @znerol

andypost’s picture

StatusFileSize
new3.68 KB
new41.08 KB

The patch & interdiff

mondrake’s picture

Priority: Major » Critical
Issue tags: +PHP 8.2

This 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

andypost’s picture

StatusFileSize
new679 bytes
new41.08 KB

fix cspell

mondrake’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 193: 2531564-192.patch, failed testing. View results

neclimdul’s picture

Man 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.

+++ b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php
@@ -564,6 +556,7 @@ public function testGetServiceDefinitionForExpression() {
+     * @group legacy

@@ -575,9 +568,41 @@ public function testGetServiceDefinitionForObject() {
       $this->expectException(RuntimeException::class);
+      $this->expectDeprecation('_serviceId is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. Use DrupalKernelInterface::getServiceIdMapping() instead. See https://www.drupal.org/node/3292540');
       $this->dumper->getArray();

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.

andypost’s picture

Status: Needs work » Needs review
StatusFileSize
new1.18 KB
new798 bytes
new40.43 KB
new40.42 KB

@neclimdul thank you a lot! Yes, no reason in 2 legacy tests!

neclimdul’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Component/DependencyInjection/ContainerInterface.php
    @@ -0,0 +1,42 @@
    +   * @return string
    +   *   A unique hash identifying the object.
    
    +++ b/core/lib/Drupal/Component/DependencyInjection/ServiceIdHashTrait.php
    @@ -0,0 +1,37 @@
    +    return hash('sha256', get_class($object) . spl_object_hash($object));
    

    So 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.

  2. +++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
    @@ -430,6 +430,7 @@ protected function dumpValue($value) {
           if (isset($value->_serviceId)) {
    +        @trigger_error('_serviceId is deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. Use DrupalKernelInterface::getServiceIdMapping() instead. See https://www.drupal.org/node/3292540', E_USER_DEPRECATED);
    

    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.

andypost’s picture

Re #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

geek-merlin’s picture

Great 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.

geek-merlin’s picture

Another 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.

znerol’s picture

I 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 $instance is coming from the $container, then we just loop over $service_ids in the container and select the $service_id if $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.

andypost’s picture

SplObjectHash 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

neclimdul’s picture

yeah, 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?

andypost’s picture

Re #204-2 I think we should throw fatal in 10.0.x if we deprecate in 9.5.x

geek-merlin’s picture

Again, 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).

berdir’s picture

Do 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.

neclimdul’s picture

Merlin 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.

function __sleep() {
  $vars = parent::__sleep();
  $this->_service_ids['myProp'] = 'noneSharedServiceId';
  unset($vars['myProp']);
  return $vars;
}
geek-merlin’s picture

@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.

andypost’s picture

as 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?

andypost’s picture

Related issues:
ghost of drupal past’s picture

Based on some recent work I did for bricks, I would recommend maybe looking into SplObjectStorage instead of spl_object_hash. It's sort of cheating because while it uses spl_object_hash internally but it keeps a reference to the object so it can't disappear unlike with spl_object_hash.

andypost’s picture

@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

andypost’s picture

neclimdul’s picture

Don'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.

ghost of drupal past’s picture

StatusFileSize
new767 bytes

Just 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:

    $cache = $this->container->get('cache.render');
    $this->container->set('cache.render', NULL);
    $mapping = $this->container->getServiceIdMappings();
    $this->assertTrue(isset($mapping[$this->container->generateServiceIdHash($cache)]));

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.

neclimdul’s picture

I'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.

geek-merlin’s picture

andypost’s picture

FYI 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

neclimdul’s picture

@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.

andypost’s picture

Trying to deprecate settings service is just 40k patch, looking for reviews #3299828: Stop storing Settings singleton in object properties

xjm’s picture

Issue tags: +Drupal 10 beta blocker
xjm’s picture

Switching up the tag since this needs to be fixed in 9.5 too.

catch’s picture

Issue tags: +Needs reroll

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.

I 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.

andypost’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new2.46 KB
new40.38 KB
new40.39 KB

The #216 got reply, and it has follow-up

neclimdul’s picture

+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php
@@ -8,6 +8,11 @@
+ * @deprecated in drupal:9.5.0 and is removed from drupal:10.0.0. The _serviceId

Missed one.

Sad we'll hold this deprecation through a release but I guess that's the reality.

andypost’s picture

StatusFileSize
new769 bytes
new40.39 KB
new40.38 KB

Thanks, fixed it

Status: Needs review » Needs work

The last submitted patch, 228: 2531564-228-9.patch, failed testing. View results

andypost’s picture

Status: Needs work » Needs review

Flaky test

longwave’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Component/DependencyInjection/Container.php
    @@ -536,10 +537,7 @@ protected function getParameterAlternatives($name) {
    +   * {@inheritDoc}
    

    inheritDoc -> inheritdoc

  2. +++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php
    @@ -430,6 +430,7 @@ protected function dumpValue($value) {
    +        @trigger_error('_serviceId is deprecated in drupal:9.5.0 and is removed from drupal:11.0.0. Use DrupalKernelInterface::getServiceIdMapping() instead. See https://www.drupal.org/node/3292540', E_USER_DEPRECATED);
    

    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

  3. +++ b/core/lib/Drupal/Core/DrupalKernel.php
    @@ -1535,6 +1568,17 @@ protected function addServiceFiles(array $service_yamls) {
    +      foreach ($this->container->getServiceIdMappings() as $hash => $service_id) {
    +        $this->serviceIdMapping[$hash] = $service_id;
    +      }
    

    Why do we need a loop here? Just set the property directly or with array_merge()?

  4. +++ b/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php
    @@ -115,8 +116,7 @@ public function testIsLocked() {
    +    TestKernel::setContainerWithKernel();
    

    This test succeeds for me locally without this line at all.

  5. +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTest.php
    @@ -51,10 +49,8 @@ public function testSerialization() {
       public function testSerializationWithMissingService() {
    

    testSerializationWithMissingService seems redundant, it looks to be testing the same as the previous one now the service ID has been removed.

  6. +++ b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php
    @@ -188,7 +188,8 @@ public function providerTestGetMatchingContexts() {
    -      $expected_context_definition = (new ContextDefinition('string'))->setConstraints(['Blank' => []]);
    +      $expected_context_definition = new ContextDefinition('string');
    +      $expected_context_definition->setConstraints(['Blank' => []]);
    

    Out of scope change?

neclimdul’s picture

re 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 🤣

longwave’s picture

#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 inheritdoc across core but only 85 of inheritDoc. 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.

neclimdul’s picture

#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, 🤷

longwave’s picture

> 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.

andypost’s picture

WeakMap 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

spokje’s picture

Assigned: Unassigned » spokje
catch’s picture

I think we should address WeakMap and/or different hashing or non-hashing solutions in a follow-up.

spokje’s picture

spokje’s picture

Issue tags: +Needs change record updates
StatusFileSize
new6.23 KB
new40.25 KB

Addressed #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

Also the change record needs more detail - this doesn't tell me what I have to do

Added Needs change record updates tag for someone with an actual Big Brain to do.

3. Left as-is.

4. Done.

5. Removed testSerializationWithMissingService, which is now indeed the same as testSerialization except for an extra \Drupal::setContainer($container);. Also testSerialization has more assertions so that one seems the one to keep.

6. Reversed

longwave’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record updates

Thank 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.

spokje’s picture

Status: Needs review » Needs work
StatusFileSize
new6.91 KB
new40.3 KB
spokje’s picture

StatusFileSize
new6.44 KB
new40.04 KB
spokje’s picture

StatusFileSize
new7.13 KB
new40.08 KB
spokje’s picture

Assigned: spokje » Unassigned
spokje’s picture

Sorry, ran out of available time.

Failing test should be easily fixed (#FamousLastWords) by ignoring "lines" containing E_USER_DEPRECATED or expectDeprecation.

neclimdul’s picture

Status: Needs work » Needs review
StatusFileSize
new40.89 KB
new830 bytes
new40.85 KB

Well that's fun. This isn't a bullet proof or complete solution but it fits the current need within the current DrupalComponentTest test framework.

longwave’s picture

Status: Needs review » Needs work

This all looks good to me, except two nits with the most recent fix:

  1. +++ b/core/tests/Drupal/Tests/Component/DrupalComponentTest.php
    @@ -98,7 +98,7 @@ protected function assertNoCoreUsage(string $class_path): void {
           // Filter references to @see as they don't really matter.
    

    We should update this comment to say "@see and deprecations".

  2. +++ b/core/tests/Drupal/Tests/Component/DrupalComponentTest.php
    @@ -98,7 +98,7 @@ protected function assertNoCoreUsage(string $class_path): void {
    +      return 0 === preg_match('/@see|E_USER_DEPRECATED|expectDeprecation/', $line);
    

    We don't usually use Yoda conditions in Drupal (although Symfony does), we should probably switch this round or just say return !preg_match(...).

neclimdul’s picture

StatusFileSize
new40.99 KB
new40.95 KB
new971 bytes

1. 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.

neclimdul’s picture

Status: Needs work » Needs review
longwave’s picture

Status: Needs review » Reviewed & tested by the community

Thanks. I've re-reviewed the whole patch and have no further points to make, therefore this is RTBC.

  • catch committed 19200d5 on 10.0.x
    Issue #2531564 by neclimdul, andypost, chx, cburschka, Spokje,...
  • catch committed ef54768 on 10.1.x
    Issue #2531564 by neclimdul, andypost, chx, cburschka, Spokje,...

  • catch committed 667d8b0 on 9.5.x
    Issue #2531564 by neclimdul, andypost, chx, cburschka, Spokje,...
catch’s picture

Version: 10.0.x-dev » 9.5.x-dev
Status: Reviewed & tested by the community » Fixed
+++ b/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php
@@ -30,33 +31,50 @@ trait DependencySerializationTrait {
+      $container = \Drupal::getContainer();
+      $mapping = \Drupal::service('kernel')->getServiceIdMapping();
+      foreach ($vars as $key => $value) {
+        if ($value instanceof EntityStorageInterface) {
+          // If a class member is an entity storage, only store the entity type
+          // ID the storage is for, so it can be used to get a fresh object on
+          // unserialization. By doing this we prevent possible memory leaks
+          // when the storage is serialized and it contains a static cache of
+          // entity objects. Additionally we ensure that we'll not have multiple
+          // storage objects for the same entity type and therefore prevent
+          // returning different references for the same entity.
+          $this->_entityStorages[$key] = $value->getEntityTypeId();
+          unset($vars[$key]);
+        }

I'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.

neclimdul’s picture

💙 Wow, that was a journey. Thanks everyone!

andypost’s picture

phenaproxima’s picture

Status: Fixed » Needs work
Issue tags: +Needs release note

I 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.

longwave’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs release note

Added a short release note snippet.

longwave’s picture

phenaproxima’s picture

Status: Needs review » Fixed

Beautiful.

samuel.mortenson’s picture

Running into serialization issues following this patch. Quickest way to replicate is to run this code in 9.5.x:

>>> \Drupal::getContainer()->get('app.root')
<warning>PHP Deprecated:  The "app.root" service is deprecated in drupal:9.0.0 and is removed from drupal:10.0.0. Use the app.root parameter instead. See https://www.drupal.org/node/3080612 in /home/mortenson/repos/drupal/core/lib/Drupal/Component/DependencyInjection/Container.php on line 134</warning>
=> "/home/mortenson/repos/drupal"
>>> \Drupal::getContainer()->getServiceIdMappings()
<warning>PHP Deprecated:  The "app.root" service is deprecated in drupal:9.0.0 and is removed from drupal:10.0.0. Use the app.root parameter instead. See https://www.drupal.org/node/3080612 in /home/mortenson/repos/drupal/core/lib/Drupal/Component/DependencyInjection/Container.php on line 134</warning>
TypeError: Argument 1 passed to Drupal\Component\DependencyInjection\Container::generateServiceIdHash() must be an object, string given, called in /home/mortenson/repos/drupal/core/lib/Drupal/Component/DependencyInjection/ServiceIdHashTrait.php on line 19
catch’s picture

OK 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.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

alexpott’s picture

There's some suspicion that this issue has caused a performance regression - see #3327856: Performance regression introduced by container serialization solution