Problem/Motivation

When attempting to migrate image files from an external legacy website, I encountered the following error after processing about 1000 files:

[warning] include(.../docroot/core/modules/migrate/src/MigrateException.php): failed to open stream: Too many open files ClassLoader.php:444

Steps to reproduce

In my implementation, I used the transform method on the "download" process plugin from within another custom process plugin like so:

$uri = $this->downloadPlugin->transform([
$source,
$destination,
], $migrate_executable, $row, $destination_property);

The error should trigger when the Download plugin's transform method is called about 1000 times.

Proposed resolution

It appears the Download process plugin fails to close the stream it opens when it downloads the file. This eventually results in triggering the error from the ClassLoader.

For testing purposes, I tried modifying the transform method within the Download plugin class to close the stream before exiting:

public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {

...

@fclose($destination_stream);
return $final_destination;
}

By doing this the error was no longer triggered. As such, I think this issue might be resolved by simply closing the open stream. However, since the stream is saved to the configuration as $this->configuration['guzzle options']['sink'] I'm not sure how this change would effect code, if any, that might later require the stream resource.

Issue fork drupal-3163663

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

rzan created an issue. See original summary.

larowlan’s picture

Category: Bug report » Support request
Status: Active » Closed (works as designed)
Issue tags: +Bug Smash Initiative

Thanks, this is an issue in your local environment - you need to configure ulimits

See https://serverfault.com/questions/15564/where-are-the-default-ulimits-sp... for example

rzan’s picture

Thank you for the response.

I understand the nature of limits within the local environment, but I created this issue to ask why the file stream is not closed after a download is complete. Increasing environment limits does resolve the issue, but an arbitrarily large file migration will inevitably overcome any finite limit.

If no limit is set, then a sufficiently large migration could eventually monopolize local resources. It is also good coding practice to close file streams after you are finished so that other processes may access them.

Is there any particular reason why the streams are kept open for the entire length of the migration?

larowlan’s picture

Category: Support request » Bug report
Status: Closed (works as designed) » Active

That is a good question. Sorry, I didn't see the proposed resolution last time

Back to active

chrisolof’s picture

Status: Active » Needs review
StatusFileSize
new559 bytes

I just hit this myself today and rzan's solution of closing the open file pointer after downloading the remote file appears to work. I'm attaching a one-line patch against Drupal 8.9.x for review.

This bug also affects the file_copy plugin when used with remote source files, and the patch fixes that scenario too.

Status: Needs review » Needs work

The last submitted patch, 5: 3163663-migrate-too-many-open-files-bug-5.patch, failed testing. View results

chrisolof’s picture

Status: Needs work » Needs review
StatusFileSize
new601 bytes

New patch checks that $destination_stream is not FALSE before attempting to close it.

Status: Needs review » Needs work

The last submitted patch, 7: 3163663-migrate-too-many-open-files-bug-7.patch, failed testing. View results

mpp made their first commit to this issue’s fork.

mpp’s picture

Status: Needs work » Needs review

+ if ($destination_stream) {
+ fclose($destination_stream);
+ }

Imo, since the current code is doing @fopen we could silence the @close as well.

mpp’s picture

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

chrisolof’s picture

+ if ($destination_stream) {
+ @fclose($destination_stream);
+ }

I'm finding that on some systems $destination_stream is already closed by the time we get here. Presumably on these environments the bug we're discussing here would not present. Also on these systems, the if ($destination_stream) check is insufficient. A closed resource would evaluate to TRUE, so we'll attempt to close it again, which will raise a PHP warning (which the @ will suppress). I think this is what was tripping up the test result in #7.

While the @ certainly silences the warning on these systems, I think we might want to replace the plain if statement with if (is_resource($destination_stream)). is_resource() will return FALSE for a closed resource, thus avoiding any attempts to close a resource that was already closed.

At any rate it's a suggestion. The current patch / merge request in #14 certainly works well in my tests across both types of systems, but I do believe we're still trying to close an already-closed resource on certain systems.

mpp’s picture

Thanks for the input @chrisolof. Not sure where in Download::transform the stream could have been closed prior to the fclose statement?

I applied both suggestions:

    if (is_resource($destination_stream)) {
      fclose($destination_stream);
    }

Let's see what the testbot has to say.

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.

robertoperuzzo’s picture

Patch #7 works for me on D9.1.5 deployed in Platform.sh

mikelutz’s picture

Status: Needs review » Reviewed & tested by the community

This seems obvious, and I can't think of any reasonable way to add a test around the fix, so RTBC

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests

We can use get_resources() to determine the number of open resources. And therefore this is testable.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new768 bytes

Here's the test... this will fail. Someone can improve the comments around the test and add it to the MR and then we should be good to go.

alexpott’s picture

StatusFileSize
new872 bytes

Opps coding standards...

Status: Needs review » Needs work

The last submitted patch, 22: 3163663-22.test-only.patch, failed testing. View results

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new2.45 KB
new2.73 KB

@alexpott, thanks for writing a test.

Added a comment to the new test code. Shortened the additional comment because I think there is enough information in context. And added the fix.

quietone’s picture

Issue tags: -Needs tests

Meant to add that I removed the message from the assertion because of #3131946: [policy] Remove PHPUnit assertion messages when possible, and standardize remaining messages .

mikelutz’s picture

Status: Needs review » Reviewed & tested by the community

Ha, I did a real quick Google on seeing if there was a method to see how many resources were open, and the result I saw didn't mention this method. Thanks, @alexpott I wanted there to be a method to make the test this easy, lol. Back to Rtbc!

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 76b866462d to 9.4.x and 32c93c8b98 to 9.3.x. Thanks!

Backported to 9.3.x as this is a low-risk bug fix.

  • alexpott committed 76b8664 on 9.4.x
    Issue #3163663 by mpp, chrisolof, alexpott, quietone: Too many open...

  • alexpott committed 32c93c8 on 9.3.x
    Issue #3163663 by mpp, chrisolof, alexpott, quietone: Too many open...

Status: Fixed » Closed (fixed)

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