Problem/Motivation

While working on #1932652: Add image uploading to WYSIWYGs through editor.module, I wanted to implement a hook_uninstall() hook to remove all file usages by editor.module upon uninstallation.
I also noticed that image.module erroneously does not yet do this. (file.module doesn't need to do this, because it owns/provides the file usage service.)
This is necessary for the file usage service to be of any use, because without this "delete all file usages by a module upon its uninstallation", it will not be updated accordingly, and hence be completely inaccurate.

The problem is two-fold:

  1. There is no explicit support for uninstalling a module in FileUsageInterface: you have to call FileUsageInterface::delete() for every file that is used by the module being uninstalled. This means potentially millions of things needing to be loaded.
  2. Performing a DB query to do the above is also impossible, because the file usage service has a swappable storage back-end…

Proposed resolution

Two options:

  1. Add a new FileUsageInterface::moduleUninstalled($module) method. This would essentially do something like DELETE * FROM file_usage WHERE module = $module.
  2. Make the first parameter of FileUsageInterface::delete(), $file, optional. The behavior would then be the same as the above.

Remaining tasks

TBD

User interface changes

None.

API changes

TBD.

#1932652: Add image uploading to WYSIWYGs through editor.module
https://drupal.org/node/1969502#comment-7295942

Comments

wim leers’s picture

Issue tags: +API change

This would be an API change.

swentel’s picture

Well, files can reused no ? So as long as the delete query doesn't blindly delete them, I guess we're ok, unless I'm missing something.

gábor hojtsy’s picture

I'm not sure how the criticality of this is justified. Drupal 7 file module did not do any uninstallation of its files either: http://drupalcode.org/project/drupal.git/blob/refs/heads/7.x:/modules/fi... - although D7 had the file_usage table managed by system module. So does seem to be an existing issue in Drupal 7.

dave reid’s picture

Priority: Critical » Major

Not sure if the best route is some kind of FileUsageInterface::deleteByModule() or FileUsageInterface::deleteByConditions(array('module' => 'image')), but I would probably say this is major.

I would guess that file module should implement hook_modules_uninstalled() and take care of this for any module that uses file usage so they don't have to worry about it.

wim leers’s picture

Priority: Major » Critical

#2: the fact that you're missing is that the file usage service is supposed to register how many times something is being used correctly. By not cleaning up file usages by modules that no longer are in use, it is painting an inaccurate picture.
I think you misunderstood it somewhere along the way? :)

wim leers’s picture

Priority: Critical » Major

Cross-post.

wim leers’s picture

I would guess that file module should implement hook_modules_uninstalled() and take care of this for any module that uses file usage so they don't have to worry about it.

+9000

I had no idea hook_modules_uninstalled() existed! You're absolutely right. That simplifies things a lot. Thanks :)

Nope, that still doesn't solve the problem :/ The code for this must live in the specific storage back-end, because of

Performing a DB query to do the above is also impossible, because the file usage service has a swappable storage back-end…

. We can't simply perform DB queries, because then we break this abstraction…

dave reid’s picture

Yes, we would need both parts, but at least each module wouldn't have to implement hook_uninstall to remove file usage.

swentel’s picture

#5 sure - I know there's a gap in the cleaning up of files, I've seen that a lot in D7 sites. However, I thought that another module could also re-use a file from a module that's disabled, but looking at the api, it seems like we're actually safe. So carry on .. :)

wim leers’s picture

#8: okay, agreed :)

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new4.45 KB

Here we go.

Status: Needs review » Needs work
Issue tags: -API change, -Spark

The last submitted patch, module_uninstall_delete_file_usage-2060405-11.patch, failed testing.

wim leers’s picture

Status: Needs work » Needs review
Issue tags: +API change, +Spark
spleshka’s picture

Good catch! Patch looks good to me, mark its as RTBC.

spleshka’s picture

Status: Needs review » Reviewed & tested by the community

Oh, forgot to change issue status.

wim leers’s picture

Thanks :)

But shouldn't there also be test coverage for File module's new hook_modules_uninstalled() hook? I didn't add that yet because I wasn't sure what the best way to do that would be.

spleshka’s picture

I thought that your test covers functionality in hook_modules_uninstalled()

wim leers’s picture

Yes, the test covers functionality used by file_modules_uninstalled(), but not file_modules_uninstalled() itself. I also think it should be sufficient, but I've seen lots of disagreements lately on what needs test coverage and what not, hence I'm asking.

spleshka’s picture

This hook covered by tests in system_test_modules_installed() and module_test_modules_uninstalled(), all other modules don't provide test coverage for it. So it seems that no needs in that.

dries’s picture

I reviewed this patch and it looks good. It looks like it needs a re-roll though. Asking for a re-test.

dries’s picture

alexpott’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

The last submitted patch, module_uninstall_delete_file_usage-2060405-11.patch, failed testing.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new4.45 KB

Straight reroll of #11, to apply cleanly.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC as per #15 and #20.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 858674f and pushed to 8.x. Thanks!

Fixed spelling mistake during commit

--- a/core/modules/file/lib/Drupal/file/Tests/UsageTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/UsageTest.php
@@ -87,7 +87,7 @@ function testDeleteByModule() {

     file_usage()->deleteByModule('testing');

-    $this->assertIdentical(array('file' => array('bar' => array(2 => '2'))), file_usage()->listUsage($file), 'All recors for the "testing" module have been deleted.');
+    $this->assertIdentical(array('file' => array('bar' => array(2 => '2'))), file_usage()->listUsage($file), 'All records for the "testing" module have been deleted.');
   }

   /**
wim leers’s picture

Heh … thanks :)

berdir’s picture

Not exactly sure what the goal of this issue is/was :)

Only *temporary* files with no usage are deleted. And files are marked temporary implicitly when all usages to it are deleted in FileUsageBase::delete(). The patch removes the file_usage entries but obviously doesn't mark them as temporary, so they will stick around forever, without usage information.

Some other things to consider:
- It's not possible to uninstall image.module when there are still any image fields around, removing image fields should also remove the corresponding usages correctly, so the situation for image.module is quite different.
- the file usage stuff used to be in system.module, which couldn't be uninstalled. Now you can uninstall file.module, which means it drops all database tables but doesn't actually delete the files...
- What happens with the embed tags in the content? Unlike image.module, you can uninstall editor.module and they will still be around. If the filter is enabled for some text formats, it will right now explode, with an RTBC issue the behavior will change to not print anything and output watchdog errors, after re-saving the format it will probably just print them out.

I have no idea how to actually solve this. Given the last point, you could argue that the file usage is in fact still there ;)

wim leers’s picture

Not exactly sure what the goal of this issue is/was :)

Precisely what the title says: allow modules to declare they're no longer around and hence clean up their corresponding file usage counters :)

You're right that deleteByModule() which was introduced here deviates from delete(), but sadly nobody noticed that :(

  public function delete(File $file, $module, $type = NULL, $id = NULL, $count = 1) {
    // If there are no more remaining usages of this file, mark it as temporary,
    // which result in a delete through system_cron().
    $usage = file_usage()->listUsage($file);
    if (empty($usage)) {
      $file->setTemporary();
      $file->save();
    }
  }

Of course, calling this for every file being unreferenced because a module is being uninstalled is again equally unscalable as the original issue. I think the whole system is designed backwards: a file should not be explicitly marked as temporary or permanent; rather, a file should be implied to be permanent if there's >0 usages!

What happens with the embed tags in the content? Unlike image.module, you can uninstall editor.module and they will still be around. If the filter is enabled for some text formats, it will right now explode, with an RTBC issue the behavior will change to not print anything and output watchdog errors, after re-saving the format it will probably just print them out.

Except that no filter is used for #1932652: Add image uploading to WYSIWYGs through editor.module, so all those consequences won't actually happen. A review of that issue is definitely welcome :)

berdir’s picture

Status: Fixed » Active

Well, the system wasn't really designed, file status existed long before the file usage API did I think :) In 7.x, you have to *manually* set the file status when removing/adding file usages. Took me a lot of time & arguments to automate that process in 8.x.

The file usage API right now is kind of optional but not really, when you use the file_managed element #type then it will fail validation when trying to re-save something when there's still file usages.

And the file and file usage tables are now two detached pluggable systems, it's not possible and never would have been performant to replace the status column with an implicit check, this information needs to be materialized. Changing is I guess too much now.

The only idea that I have right now would be that we don't delete immediately (because you can't query something that's not there) but only initiate a process that will then do the cleanup, two possible ways, not sure if they will actually work.

- Instead of deleting the records, set the count to 0. Then implement some code in file_cron() that will look for those and delete them separately, in batches. Would need the ability to search for these records and keep the API introduced here.
- Use the queue API to set-off a self-re-enqueueing process that will search by module, delete N records and if there are more than that, re-enqueue itself. Doesn't need deleteByModule(), only findByModule()

Or, we could of course also remove the ability to uninstall modules :)

Re-opening for now, still not sure if it should be reverted or not.

damien tournoud’s picture

One possible easy way out:

Only *temporary* files with no usage are deleted.

Is there a good reason for this? If we delete all files with no usage, we are good.

berdir’s picture

We have no way to query that.

What we do is query for temporary files with a changed date older than 6h (soon configurable), load 100 of them, check if they have any usages, if yes, log a watchdog and skip, otherwise delete.

We can't query for files without usages, because they aren't listed in the file_usage table. And we can't join those two tables as they are both pluggable now.

damien tournoud’s picture

@Berdir, any reason the file usage service is not a field? Inventing our own storage for everything is so 1999.

berdir’s picture

Well reason, I guess file usage was added be before files were entities and they're still not fieldable in core, although that's just a flag. Making it a field sounds like a nice idea but too late for that :)

damien tournoud’s picture

It's not too late for anything. Fixing design issues requires breaking things. That's normal and expected. The API freeze was just premature anyway, given the very sad current state of core.

alexpott’s picture

Reverted with commit 0e47ce3 and pushed to 8.x.

We still need a solution but there is no point leaving a broken solution in.

dave reid’s picture

Issue tags: +Media Initiative
jhedstrom’s picture

The issue summary needs updating given that parts of this were committed.

slashrsm’s picture

Issue tags: +sprint
slashrsm’s picture

Issue tags: +D8Media

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.

  • alexpott committed 0e47ce3 on 8.3.x
    Revert "Issue #2060405 by Wim Leers: Fixed Impossible to uninstall any...
  • alexpott committed 858674f on 8.3.x
    Issue #2060405 by Wim Leers: Fixed Impossible to uninstall any module...

  • alexpott committed 0e47ce3 on 8.3.x
    Revert "Issue #2060405 by Wim Leers: Fixed Impossible to uninstall any...
  • alexpott committed 858674f on 8.3.x
    Issue #2060405 by Wim Leers: Fixed Impossible to uninstall any module...
gábor hojtsy’s picture

Issue tags: -Media Initiative

Remove obsolete media tag.

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

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should 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.

  • alexpott committed 0e47ce3 on 8.4.x
    Revert "Issue #2060405 by Wim Leers: Fixed Impossible to uninstall any...
  • alexpott committed 858674f on 8.4.x
    Issue #2060405 by Wim Leers: Fixed Impossible to uninstall any module...

  • alexpott committed 0e47ce3 on 8.4.x
    Revert "Issue #2060405 by Wim Leers: Fixed Impossible to uninstall any...
  • alexpott committed 858674f on 8.4.x
    Issue #2060405 by Wim Leers: Fixed Impossible to uninstall any module...

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

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should 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.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

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

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

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

catch’s picture

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

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

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

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.