Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(dev/core#3660) Ensure that rebuildMenuAndCaches has current mixins+classloaders #23824

Merged
merged 1 commit into from
Jun 17, 2022

Conversation

totten
Copy link
Member

@totten totten commented Jun 17, 2022

Overview

This fixes an issue in transitioning from hook_managed to mixin/mgd-php@1, wherein managed-entities are briefly deleted (but later recreated).

See also: https://lab.civicrm.org/dev/core/-/issues/3660

ping @colemanw

Steps to Reproduce

  1. (Web) Install an extension with a revision that uses hook_managed to load *.mgd.php files
  2. (CLI) View contents of civicrm_managed
  3. (CLI) Switch the extension to a revision that uses mixin/mgd-php@1 to load *.mgd.php files
  4. (Web) Run civicrm/menu/rebuild
  5. (CLI) View contents of civicrm_managed
  6. (Web) Run civicrm/menu/rebuild
  7. (CLI) View contents of civicrm_managed

Before

While processing step 4 (civicrm/menu/rebuild), it fails to run the hooks for mgd-php.

Consequently, managed-entities are (temporarily) lost/destroyed - and will be missing at step 5.

After

While processing step 4 (civicrm/menu/rebuild), it activates the hooks for mgd-php.

Consequently, managed-entities are preserved.

Technical Details

When processing civicrm/menu/rebuild, there are a couple big substeps:

  • Civi\Core\Container::boot() - During this process, it loads extensions. As usual, this reads cached metadata, sets up classloaders, sets up mixins/hooks, etc.
  • CRM_Core_Invoke::rebuildMenuAndCaches() - During this process, it clears out caches and rebuilds several things (menus, managed-entities, etc).

The problem is this:

  • The cache used during boot() has stale metadata (specifically, civicrm_cache has old values of mixinScan and mixinBoot). So it doesn't setup any new mixins/hooks.
  • Then rebuildMenuAndCaches() depends on the mixins/hooks that are already setup. While the function does clear persistent caches, it assumes that the PHP runtime environment is up-to-spec. But it's not -- because our hooks were based on the old caches.

This patch uses the same refresh() mechanism as the extension-administration subsystem (which has to reset the classloaders+mixins after enabling or disabling an extension).

…lassloaders

Overview
--------

This fixes an issue in transitioning from `hook_managed` to `mixin/mgd-php@1`, wherein
managed-entities are briefly deleted (but later recreated).

Steps to Reproduce
------------------

1. (Web) Install an extension with a revision that uses `hook_managed`
2. (CLI) Switch the extension to a revision that uses `mixin/mgd-php@1`
3. (CLI) View contents of `civicrm_managed`
4. (Web) Run `civicrm/menu/rebuild`
5. (CLI) View contents of `civicrm_managed`
6. (Web) Run `civicrm/menu/rebuild`
7. (CLI) View contents of `civicrm_managed`

Before
------

While processing step 4 (`civicrm/menu/rebuild`), it fails to run the hooks for `mgd-php`.

Consequently, the list of managed-entities is lost and will disappear at step 5.

After
-----

While processing step 4 (`civicrm/menu/rebuild`), it activates the hooks for `mgd-php`.

Technical Details
------------------

When processing `civicrm/menu/rebuild`, there are a couple big substeps:

* `Civi\Core\Container::boot()` - During this process, it loads extensions. As usual,
  this reads cached metadata, sets up classloaders, sets up mixins/hooks, etc.
* `CRM_Core_Invoke::rebuildMenuAndCaches()` - During this process, it clears
  out caches and rebuilds several things (menus, managed-entities, etc).

The problem is this:

* The cache used during `boot()` has stale metadata (specifically,
  `civicrm_cache` has old values of `mixinScan` and `mixinBoot`).
  So it doesn't setup any new mixins/hooks.
* Then `rebuildMenuAndCaches()` depends on the mixins/hooks that are already setup.
  While the function does clear persistent caches, it assumes that the
  PHP runtime environment is up-to-spec. But it's not -- becuase our hooks
  were based on the caches.

The patch uses the same `refresh()` mechanism as the extension-administration subsystem (which
has to reset the classloaders+mixins after enabling or disabling an extension).
@civibot
Copy link

civibot bot commented Jun 17, 2022

(Standard links)

@civibot civibot bot added the 5.51 label Jun 17, 2022
@totten
Copy link
Member Author

totten commented Jun 17, 2022

Note: I considered an alternative - track timestamps for all active info.xml files - and clear the relevant cache if there was a change. That brings a trade-off:

  • It would fix this bug, and (bonus points) you would no longer need to do cv flush after editing info.xml.
  • Implementing would require a bigger patch, and every page-load would need to stat() every info.xml.

There is a hypothetical world where that would be worth entertaining, but (for now) the current PR seemed easier and more performant.

@colemanw
Copy link
Member

Tested r-run and this fixes the bug.

@colemanw colemanw merged commit b9fd3af into civicrm:5.51 Jun 17, 2022
@totten totten deleted the mixin-refresh branch June 17, 2022 03:29
totten added a commit to totten/civicrm-core that referenced this pull request Jun 29, 2022
…refreshes

Overview
--------

This is a follow-up to civicrm#23824 (c24dd7d) which addresses a
regressive edge-case.

Steps to Reproduce
------------------

* Write an extension like `wmf-civicrm` which (a) calls `System.flush` (`rebuildMenuAndCaches()`)
  during `hook_install` -- and then (b) loads some class from the same extension.
    ```php
    function foo_civicrm_install() {
      civicrm_api3('System', 'flush', []);
      CRM_Foo_Helper::doStuff();
    }
    ```
* Try to install the extension.

Before
------

Crashes on loading the class `CRM_Foo_Helper`

After
-----

Loads the class `CRM_Foo_Helper`.

Comments
--------

(1) To see what's happening, consider `CRM_Extension_Manager_Module::onPreInstall()`.
This registers the new classloader and then fires `hook_install` which eventually
fires `rebuildMenuAndCaches()`. With c24dd7d, this resets the classloader again.
But the extension isn't fully installed yet - so it forgets about the new extension.

(2) Is it safe to have some (temporarily) sticky items?  Ish.  You might
say: "Ah, but what if we need to remove an extension?  Won't this static
variable retain stale things?" Doesn't matter.  In PHP, classloading is a
one-way-street.  (You cannot unload.) So you'll still have old classes in
memory, regardless of whether the `ClassLoader` has some old metadata
about where to find classes.

(3) I'm on the fence about whether this patch is a good idea.  Calling
`System.flush` explicitly in this context seems like an invitation to
trouble.  OTOH, it worked before civicrm#23824, so it can be called a regression.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants