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

Basic Extension Dependency Support #2188

Merged
merged 32 commits into from
Oct 2, 2020
Merged

Conversation

askvortsov1
Copy link
Sponsor Member

@askvortsov1 askvortsov1 commented May 28, 2020

Refs #2093
Refs #1318

Changes proposed in this pull request:

  • Don't enable an extension if its dependencies are not enabled
  • Don't disable an extension if its dependencies are not disabled

Confirmed

  • Backend changes: tests are green (run composer test).
  • Frontend changes: tested locally

Screenshots
Error when disabling with dependent: https://i.imgur.com/X2npKFt.png
Error when enabling with missing dependencies: https://i.imgur.com/rioQMXc.png

Lang English PR: flarum/lang-english#172

Copy link
Member

@luceos luceos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments. But I'd like to discuss the need or maybe the implementation first. This has to be looked at by @franzliedke.

src/Extension/Exception/DependentExtensionsException.php Outdated Show resolved Hide resolved
src/Extension/Exception/DependentExtensionsException.php Outdated Show resolved Hide resolved
@franzliedke
Copy link
Contributor

Why would we need a separate field for declaring extensions we depend on? Composer already has require... 🤔

Anyway, I won't look at this in more detail until we've made more progress with the frontend rewrite, sorry.

@askvortsov1
Copy link
Sponsor Member Author

Why would we need a separate field for declaring extensions we depend on? Composer already has require ... 🤔

Composers require doesn't tell us what is and isn't a flarum extension, so if something isn't installed, we don't know if it's needed. Also, we'd need to do a lot more unnecessary computations to scan every extensions composer json whenever we add an extension. Overall this will just result in a clearer codebase imo.

Anyway, I won't look at this in more detail until we've made more progress with the frontend rewrite, sorry.

Sorry I should have made it clearer in the description: this is very much a "I wanna code something useful but don't want to add to Franz's workload so I'm gonna explore this cool idea" PR.

@franzliedke
Copy link
Contributor

Composers require doesn't tell us what is and isn't a flarum extension, so if something isn't installed, we don't know if it's needed. Also, we'd need to do a lot more unnecessary computations to scan every extensions composer json whenever we add an extension.

Ah, but we have the information about all Composer-installed packages in memory already, just before the Extension objects are instantiated. That includes require keys as well as the type key (which needs to be checked to equal flarum-extension).

@askvortsov1
Copy link
Sponsor Member Author

I see your point, I still think the code would be a bit messy. But let's table this for now, and revisit after frontend / extenders?

@luceos luceos marked this pull request as draft June 4, 2020 13:36
@askvortsov1
Copy link
Sponsor Member Author

It now uses dependencies from require, and I was able to do it without significantly messing up big O complexity or code convolution!

@askvortsov1 askvortsov1 marked this pull request as ready for review July 24, 2020 19:26
@luceos
Copy link
Member

luceos commented Aug 2, 2020

What's the memory footprint increase of this? I'm not going to approve this PR unless we know the downside in terms of performance. Having said so, I'm not sure this is the right approach. This code is executed on each request, even though it is only needed during the installation of an extension. I would vote to move this to a composer lifecycle hook (post update/install cmd) instead if possible. That would be my first attempt.

@askvortsov1
Copy link
Sponsor Member Author

The process for figuring out which extensions are dependent on which has an O(n) time complexity, and a O(n) space complexity (for creating the hash table of php package names to composer.json files, ie $associativeInstalled).

The process of checking whether dependencies are violated have an O(n * m) time compexity (n being number of extensions, m being average number of dependencies per extension). This is, however, inconsequential as it's only run when enabling / disabling an extension, not with every request.

The resolution of extension dependency order has an O(n + v) time complexity (as it's just Kahn's algorithm), with n being the number of extensions, and v being the number of vertices in the dependency graph (ie the sum of each extension's number of dependencies).

None of these are especially high, and all of these scale well. That being said, this code doesn't just check for whether something should be enabled/disabled, but also ensures that extensions are booted in the proper order, every time. That's why I think it should run on every request.

Also, does PHP really boot up the entire system on every request? There isn't a way to boot the application and have it await requests as they come in as a process running in the background?

@tankerkiller125
Copy link
Contributor

@askvortsov1 Standard PHP does not have a way to keep the application running, there are of course PHP work arounds for this using special libraries and stuff but that's not for Flarum to get into. Of course any production environment will have OPCache or APC cache enabled which does store the compiled PHP files so they execute faster. Once PHP 8 is out we'll also get to see how JIT does with performance.

@askvortsov1 askvortsov1 force-pushed the as/extension_dependencies branch 2 times, most recently from 7825b50 to a1f2e84 Compare September 6, 2020 21:33
@askvortsov1 askvortsov1 changed the base branch from master to mithril-2-update September 6, 2020 22:08
@askvortsov1 askvortsov1 added this to the 0.1.0-beta.14 milestone Sep 6, 2020
Copy link
Member

@clarkwinkelmann clarkwinkelmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a bunch of unrelated commits which I guess come from different PRs. It doesn't help to figure out what's part of this PR... Two things below.

js/src/admin/components/ExtensionsPage.js Outdated Show resolved Hide resolved
@@ -296,7 +338,9 @@ public function getEnabledExtensions()
*/
public function extend(Container $app)
{
foreach ($this->getEnabledExtensions() as $extension) {
$enabled = static::resolveExtensionOrder($this->getEnabledExtensions());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about doing this whenever an extension is enabled, and save the new order in the database? That way there's no need to do any computation when Flarum boots.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we could... But this way, it'll always be right (even if someone messes with the database), and decouples the storage of enabled extensions from the order in which they're booted. Plus, topological sort runs really fast, it'd hardly be a bottleneck compared to other aspects of the site.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to have to agree with Sasha here, although it's rare that the database gets modified directly it does happen sometimes. especially during some of our more complex troubleshooting processes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I am going to agree with @clarkwinkelmann here. This is all superfluous code that, by itself, might not seriously increase loading times (first-byte) of our forum. But each and every of these kind of decisions will impede performance over time. This scenario, with this code, is especially true with forums that have a ton of extensions - and trust me that will sadly be the reality.

Honestly I would even make this a post update/install hook by saving some kind of order map of all installed extensions to a file or the database. Then during extension enable/disable retrieve this order and store the order of enabled extensions to the database. If anyone decides to manually override that order from the database, they have good reason to do so and don't particularly would hope to see something override that order magically..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further review into this, I'm going to agree with @luceos and @clarkwinkelmann, I also agree with luceos idea of making this a post update/install hook for composer to store the order map. Further the database edits by people are incredibly rare and if someone did do it on accident (or purpose) we could simply inform them to run the same command that composer does to resolve it.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spent some more time thinking about this, and just the dependency checks on enabling / disabling will be a HUGE improvement over what we have now (and much, much simpler to review). And that should also result in a working order.

I'll remove the algorithms stuff and move it over to the extension management commands PR (we'll need it if we're trying to enable/disable extensions in a working order). No need to get everything done in a single PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying this PR will only contain logic to check whether depending extensions are installed? Sounds like a smart move. The composer hook should be quite simple too, but needs some work.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! That way, we can split this into separate PRs and have the composer hook later.

As noted earlier somewhere, due to our current system, if we have this check in place before enabling/disabling extensions, that'll prevent the vast majority of issues (since load order currently depends on order of enabling, and this way, you won't be able to enable anything.

The composer hook stuff would be in a separate PR, definitely not aiming for beta 14.

@askvortsov1
Copy link
Sponsor Member Author

I see a bunch of unrelated commits which I guess come from different PRs. It doesn't help to figure out what's part of this PR...

Yeah that was a messy rebase... sorry about that. All cleaned up now!

luceos
luceos previously approved these changes Oct 2, 2020
@luceos luceos dismissed their stale review October 2, 2020 19:55

changing key in collection is not the way to go

Copy link
Contributor

@tankerkiller125 tankerkiller125 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than the note from luceos I'm good with these changes

@luceos
Copy link
Member

luceos commented Oct 2, 2020

We just discovered on discord that the change to use the full package name as key is a breaking change and will impact the getExtension method amongst other. So reverted my approval.

askvortsov1 and others added 3 commits October 2, 2020 16:22
- Make extensionDependencies protected, call through encapsulated method
- The dependency exceptions now store arrays of extensions, not arrays of extension ids.
- Improve documentation
[ci skip] [skip ci]
Copy link
Member

@clarkwinkelmann clarkwinkelmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see I have been mentioned previously, but I can't find the message that was notified by email. In the current state of the PR, I'm not seeing any issue with other extensions.

I was about to comment on the enabled**Ids` thing, but I see a commit was just pushed fixing it.

@clarkwinkelmann
Copy link
Member

When the commits stop flowing in I might approve 😛

Copy link
Member

@clarkwinkelmann clarkwinkelmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shared some ideas regarding the error message on Discord, suggesting to maybe show title together with ID. But the current implementation is fine for the time being.

@askvortsov1 askvortsov1 merged commit 84d14f4 into master Oct 2, 2020
@askvortsov1 askvortsov1 deleted the as/extension_dependencies branch October 2, 2020 21:54
@askvortsov1 askvortsov1 mentioned this pull request Oct 26, 2020
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants