Problem/Motivation

GD toolkit and operations fail with WSOD in case of errors or exceptions.

Proposed resolution

Catch the \Throwable error and log its description in the image logger, fail gracefully.

Remaining tasks

User interface changes

None.

API changes

None.

Data model changes

None

Original issue

Executing something like this

      $test = \Drupal::service('image.factory')->get(NULL, 'gd');
      $test->createNew(20000, 20000);

leads to a fatal error

PHP Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 80000 bytes) in /[...]/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/CreateNew.php on line 94

and WSOD.

This is because GD imagexxx functions do not return gracefully in case of memory allocation failures.

Proposed resolution (no longer valid, kept for history reasons)

Take cues from the preliminary memory availability check done in the color module, and from this comment on PHP site, and introduce a memory checking method that can be called on load, create_new, and rotate operations before invoking imagexxx functions.

CommentFileSizeAuthor
#88 interdiff_84-88.txt1.88 KBmondrake
#88 2583041-88.patch5.55 KBmondrake
#88 2583041-88-test-only.patch1.36 KBmondrake
#84 2583041-84.patch5.25 KBmondrake
#84 2583041-84-test-only.patch1.05 KBmondrake
#76 interdiff_74-76.txt2.35 KBmondrake
#76 2583041-76.patch29.13 KBmondrake
#74 interdiff_72-74.txt2.35 KBmondrake
#74 2583041-74.patch29.13 KBmondrake
#72 2583041-72.patch28.98 KBmondrake
#66 2583041-66.patch41.32 KBmondrake
#66 2583041-66-do-not-test.patch29.35 KBmondrake
#57 2583041-57.patch38.11 KBmondrake
#57 interdiff_54-57.txt749 bytesmondrake
#56 2583041-56.patch30.38 KBmondrake
#56 interdiff_54-56.txt749 bytesmondrake
#54 2583041-54.patch38.11 KBmondrake
#52 2583041-52.patch38.48 KBmondrake
#52 interdiff_50-52.txt2.76 KBmondrake
#50 2583041-50.patch38.34 KBmondrake
#50 interdiff_49-50.txt11.44 KBmondrake
#49 2583041-49.patch36.36 KBmondrake
#49 interdiff_47-49.txt1.85 KBmondrake
#47 2583041-47.patch36.35 KBmondrake
#47 interdiff_34-47.txt10.63 KBmondrake
#34 interdiff_32-34.txt10.71 KBmondrake
#34 2583041-34.patch36.98 KBmondrake
#32 interdiff_31-32.txt9.18 KBmondrake
#32 2583041-32.patch36.26 KBmondrake
#31 interdiff_29-31.txt495 bytesmondrake
#31 2583041-31.patch36.42 KBmondrake
#29 interdiff_26-29.txt7.87 KBmondrake
#29 2583041-29.patch36.75 KBmondrake
#26 2583041-26.patch35.78 KBmondrake
#26 interdiff_23-26.txt1.69 KBmondrake
#23 2583041-23.patch35.78 KBmondrake
#23 interdiff_21-23.txt1.59 KBmondrake
#21 2583041-21.patch35.56 KBmondrake
#21 interdiff_19-21.txt586 bytesmondrake
#19 2583041-19.patch35.55 KBmondrake
#19 interdiff_18-19.txt1.94 KBmondrake
#18 2583041-18.patch35.87 KBmondrake
#18 interdiff_16-18.txt12.07 KBmondrake
#16 2583041-16.patch33.39 KBmondrake
#16 interdiff_13-16.txt31.2 KBmondrake
#13 2583041-13.patch26.7 KBmondrake
#13 interdiff_10-13.txt9.99 KBmondrake
#10 2583041-10.patch23.23 KBmondrake
#10 interdiff_8-10.txt7.48 KBmondrake
#8 2583041-8.patch23.61 KBmondrake
#8 interdiff_6-8.txt14.15 KBmondrake
#6 2583041-6.patch16.66 KBmondrake
#6 interdiff_2-6.txt12.81 KBmondrake
#2 2583041-2.patch7.73 KBmondrake

Issue fork drupal-2583041

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

mondrake created an issue. See original summary.

mondrake’s picture

mondrake’s picture

Status: Active » Needs review
mondrake’s picture

Issue summary: View changes
fietserwin’s picture

Status: Needs review » Needs work

I think that returning a catchable exception is preferable over a fatal error. So this issue is worth implementing.

Below first some remarks about the patch as is, after that some remarks about some design questions.

  1. +++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php
    @@ -138,6 +138,10 @@ public function apply($operation, array $arguments = array()) {
    +    catch (\RuntimeException $e) {
    +      $this->logger->warning($e->getMessage(), []);
    

    This exception type should be docuemnted with a @throws in core\lib\Drupal\Core\ImageToolkit\ImageToolkitOperationInterface.php::apply().

  2. +++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
    @@ -181,6 +201,11 @@ protected function load() {
    +      throw new \RuntimeException("Not enough memory (required: " . format_size($this->getRequiredMemory()) . ") to load image '{$path}'");
    +    }
    

    $path is unknown, did you want to use $this->getSource()?

  3. +++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
    @@ -436,4 +461,41 @@ public function extensionToImageType($extension) {
    +   * @return bool
    +   *   TRUE if there is sufficient memory to allow the operation.
    +   *
    +   * @throws \RuntimeException
    +   *   When the available memory is not sufficient.
    +   *
    

    When there is insufficient memory, this method will return FALSE, not throw an exception

  4. +++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
    @@ -436,4 +461,41 @@ public function extensionToImageType($extension) {
    +    $this->memoryRequired = (int) ($width * $height * ($bits_per_pixel / 8) * $tweak_factor);
    +    $usage = memory_get_usage(TRUE);
    +    $memory_limit = ini_get('memory_limit');
    +    $size = Bytes::toInt($memory_limit);
    +    return Environment::checkMemoryLimit($usage + $this->memoryRequired, $memory_limit);
    +  }
    

    - memoryRequired is not a defined property, whereas requiredMeory is ...
    - $size is not used: remove
    - $memory_limit does the same as Environment::checkMemoryLimit() already does for you: remove and do not pass

  5. +++ b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/CreateNew.php
    @@ -84,6 +84,11 @@ protected function validateArguments(array $arguments) {
    +      throw new \RuntimeException("Not enough memory (required: " . format_size($this->getToolkit()->getRequiredMemory()) . ") to perform the image '{$this->getPluginId()}' operation");
    +    }
    

    format_size() can do a translation while exception messages should not be translated. In this case doing unformatted output of the number might be better.

  6. +++ b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/Rotate.php
    @@ -98,6 +98,16 @@ protected function execute(array $arguments) {
    +    // can never exceed the lenght of the diagonal of the rectangle represented
    +    // by the image.
    

    typo: length

  7. +++ b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/Rotate.php
    @@ -98,6 +98,16 @@ protected function execute(array $arguments) {
    +    $diagonal = (int) sqrt(pow($this->getToolkit()->getWidth(), 2) + pow($this->getToolkit()->getHeight(), 2)) + 1;
    +    if (!$this->getToolkit()->checkAvailableMemory($diagonal, $diagonal)) {
    

    You could use floor() instead of adding 1 yourself.

  8. +++ b/core/modules/system/src/Tests/Image/ToolkitGdTest.php
    @@ -433,6 +433,15 @@ public function testResourceDestruction() {
    +    $image->createNew(20000, 20000);
    +    $new_res = $image->getToolkit()->getResource();
    

    Shouldn't checking the return value (FALSE) be part of the test?

These remarks are more at the design/architectural level:

  1. We have this utility class Environment that checks the memory limit. If I look at the usages, it should also contain a method checkAvailableMemory() (or isMemoryAvailable()?), because most usages (including ours) check that, not the limit as is.
  2. GDToolkit now has a property requiredMemory. To me that doesn't feel good: it is not a property of a toolkit but of an operation. We should strive to refactor that property away, by e.g. returning the missing amount of memory instead of just TRUE/FALSE (or allowing to pass a parameter by reference that gets that value; or by having checkAvailableMemory() throw the exception (and thus passing in an operation name or something like that.
mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new12.81 KB
new16.66 KB

Here, adding some PHP Unit tests to simulate lack of memory and ensure the exception is properly thrown in such case.

#5 not addressed yet, coming next.

mondrake’s picture

Status: Needs review » Needs work
mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new14.15 KB
new23.61 KB

#5 thanks for review @fietserwin.

Part 1 -
1) done
2) yes, remainder from earlier work, now changed
3) returns an int now, if positive means not enough memory to accommodate the required memory
4) completely changed, see below,
5) good catch. Now we do not have a method that can do that w/o involving transation. Added a Bytes::toString method + tests
6) OK
7) see todo, will have to be removed eventually, did not touch
8) yes, done

Part 2-
1) added a Environment::checkAvailableMemory method + tests
2) done, removed the property and now GDToolkit::checkAvailableMemory returns -1 in case of success, the required memory in bytes if fail.

Status: Needs review » Needs work

The last submitted patch, 8: 2583041-8.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new7.48 KB
new23.23 KB

Fixes for unit tests.

fietserwin’s picture

Status: Needs review » Needs work

Thanks for processing the remarks from my st review. This version is almost there, mostly minor remarks remain.

  1. +++ b/core/lib/Drupal/Component/Utility/Bytes.php
    @@ -44,4 +44,33 @@ public static function toInt($size) {
    +   *   A string size expressed as a number of bytes or multiples.
    +   */
    

    Document that this one is untranslated (and refer to format_size() for a translating version of this feature).

  2. +++ b/core/lib/Drupal/Component/Utility/Bytes.php
    @@ -44,4 +44,33 @@ public static function toInt($size) {
    +    if ($size < Bytes::KILOBYTE) {
    +      return $size . ' B';
    +    }
    +    else {
    +      $size = $size / Bytes::KILOBYTE;
    +      $units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    +      foreach ($units as $unit) {
    +        if (round($size, 2) >= Bytes::KILOBYTE) {
    +          $size = $size / Bytes::KILOBYTE;
    +        }
    +        else {
    +          $size = round($size, 2);
    +          break;
    +        }
    +      }
    +      return $size . ' ' . $unit;
    +    }
    +  }
    

    This can be simplified to something like:

        $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
        foreach ($units as $unit) {
          if (round($size, 2) < Bytes::KILOBYTE) {
            break;
          }
          $size = $size / Bytes::KILOBYTE;
        }
        return $size . ' ' . $unit;
     

    Advantages:
    - less cases (= less clutter).
    - a value in the YB range will also be rounded to 2 digits.

    (However: if this is a mere copy of format_size(), you can leave it as is).

  3. +++ b/core/lib/Drupal/Component/Utility/Bytes.php
    @@ -44,4 +44,33 @@ public static function toInt($size) {
    +      $size = $size / Bytes::KILOBYTE;
    +      $units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    

    self::KILOBYE (or even static::KILOBYTE) like in the method above? (4 usages).

  4. +++ b/core/lib/Drupal/Component/Utility/Environment.php
    @@ -42,4 +42,33 @@ public static function checkMemoryLimit($required, $memory_limit = NULL) {
    +   * @param string $required
    +   *   The memory required for the operation, expressed as a number of bytes with
    +   *   optional SI or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8bytes,
    +   *   9mbytes).
    +   * @param string $memory_limit
    

    2 lines with 81 characters.

  5. +++ b/core/lib/Drupal/Component/Utility/Environment.php
    @@ -42,4 +42,33 @@ public static function checkMemoryLimit($required, $memory_limit = NULL) {
    +  public static function checkAvailableMemory($required, $memory_limit = NULL, $used_memory = NULL) {
    +    if (!$used_memory) {
    

    To me this is a wrapper around checkMemoryLimit() for a special case: check if there is enough memory available within the current process space. So remove the optional parameters: if you need one, use checkMemoryLimit() directly.

  6. +++ b/core/tests/Drupal/Tests/Component/Utility/EnvironmentTest.php
    @@ -64,4 +65,49 @@ public function providerTestCheckMemoryLimit() {
    +  public function testCheckAvailableMemory($custom_memory_limit, $used_memory, $required, $expected) {
    +    $actual = Environment::checkAvailableMemory($required, $custom_memory_limit, $used_memory);
    +    $this->assertEquals($expected, $actual);
    +  }
    +
    

    If the parameters are removed, you can test this by using ini_get('memory_limit') and memory_get_usage(TRUE) and test a value above and below the difference.

mondrake’s picture

#11

1, 2) Yes it's a copy of format_size(). However at this stage I find better introduce another Bytes::toUnitAndSize() method that does the calculation and returns an array [unit, size]. Then change both format_size() and Bytes::toString() so that they both call Bytes::toUnitAndSize() in the first place and return, respectively, a translation object and a plain string that can be used in exception messages.
The calculation logic remains the one of format_size() - the one proposed in #11.2 fails on Drupal\system\Tests\Common\SizeUnitTest.
Documented that Bytes::toString() returns a plain string and added a pointer to format_size() for translatable objects.

3) self:: to be consistent.

4) OK.

5, 6) I need to think more. Initially I was hoping to introduce a Environment::getMemoryInUse() wrapper to memory_get_usage(TRUE) and mock it in PHPUnit to simulate memory close to capacity. But unfortunately you cannot mock static methods so I ended up taking cues from the existing method checkMemoryLimit() where the optional parameter allows PHPUnit to bypass ini_get('memory_limit').

mondrake’s picture

StatusFileSize
new9.99 KB
new26.7 KB
mondrake’s picture

Status: Needs work » Needs review
fietserwin’s picture

Status: Needs review » Needs work

1. The failed tests was because I forgot to round the value that will be returned. This code does pass:

    $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    foreach ($units as $unit) {
      if (round($size, 2) < Bytes::KILOBYTE) {
        break;
      }
      $size = $size / Bytes::KILOBYTE;
    }
    return ['unit' => $unit, 'size' => round($size, 2)];

Note that theoretically this code is better as the current code does rounding on rounding on rounding which may lead to false results: add 4053371676 as a test, this is 3,774996545 GB but 3,78 is currently returned...

2. Now that we are touching common.inc: format_size() has the same unnecessary if - else, this can be simplified to:

function format_size($size, $langcode = NULL) {
  $options = ['langcode' => $langcode];
  $val = Bytes::toUnitAndSize($size);
  $args = ['@size' => $val['size']];
  switch ($val['unit']) {
    case 'B':
      return \Drupal::translation()->formatPlural($val['size'], '1 byte', '@count bytes', [], $options);
    case 'KB':
      return new TranslatableMarkup('@size KB', $args, $options);
    case 'MB':
      return new TranslatableMarkup('@size MB', $args, $options);
    case 'GB':
      return new TranslatableMarkup('@size GB', $args, $options);
    case 'TB':
      return new TranslatableMarkup('@size TB', $args, $options);
    case 'PB':
      return new TranslatableMarkup('@size PB', $args, $options);
    case 'EB':
      return new TranslatableMarkup('@size EB', $args, $options);
    case 'ZB':
      return new TranslatableMarkup('@size ZB', $args, $options);
    case 'YB':
      return new TranslatableMarkup('@size YB', $args, $options);
  }
}

3. Regarding 11.5/6 and your 12 5/6:
What you are testing in method providerTestCheckAvailableMemory() is not checkAvailableMemory() but checkMemoryLimit(). As checkAvailableMemory() is just a wrapper, the test should check that checkMemoryLimit() does get called with the correct values. If you can't mock this, do a test like:

$available = ini_get('memory_limit') - memory_get_usage(TRUE);
// Function call, parameter stacking, etc may snoop away some memory: subtract a few KB.
$this->assertTrue(Environment::checkAvailableMemory($available - 4000));
$this->assertFalse(Environment::checkAvailableMemory($available + 1));

BTW: Does http://stackoverflow.com/questions/2357001/phpunit-mock-objects-and-stat... give some useful insights on mocking statics?
BTW2: You could: override Environment for testing purposes; change self:: into static:: (:)); and override checkMemoryLimit() to log/return its parameters. This way you can test checkAvailableMemory() in isolation.

4. testToString() has the same error: it actually tests testToUnitAndSize(): an error in testToUnitAndSize() will lead to 2 test failures, 1 of them in testToString(), even while toString() may be correct. But we have the same problem here as well: mocking statics.

EDIT: So I followed the link myself and read a bit about mocking statics. The 3 comments starting at https://github.com/sebastianbergmann/phpunit-documentation/issues/77#iss... explain everything. That last comment is what I suggested above, but we will have to use static (late static binding) instead of self ...

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new31.2 KB
new33.39 KB

So, #15.1 and #15.2 are in. Added @fietserwin to commit credit message.

#15.3 - my understanding is that with PHP Unit 4+ there's no way to mock static methods, period. So here I am changing approach and introducing a EnvironmentMemory class that needs to be instantiated. That also allows for proper mocking. With this Environment is kind of redundant, we may think about deprecating it, but that will have to be a follow up issue. In any case, this issue is for not earlier than 8.1.x I think.

Added tests also for the point

add 4053371676 as a test, this is 3,774996545 GB but 3,78 is currently returned...

and test for formatting a zero bytes quantity.

Also, I removed Bytes::toString that was introduced in the earlier patches, to avoid possible misuse vs. translatable strings; ::toUnitAndSize gives what's needed anyway.

fietserwin’s picture

Status: Needs review » Needs work

Yes, we are almost there. 1 medium and the rest minor.

  1. +++ b/core/includes/common.inc
    @@ -249,40 +249,28 @@ function check_url($uri) {
    +  $options = ['langcode' => $langcode];
    

    I did some digging into where this is used and found out that if $langcode is null, we end up doing this in core\lib\Drupal\Core\StringTranslation\Translator\StaticTranslation.php, getStringTranslation(), line 40:
    $this->translations[NULL] = $this->getLanguage(NULL);
    So only assign if not null, otherwise make it an empty array.

  2. +++ b/core/lib/Drupal/Component/Utility/EnvironmentMemory.php
    @@ -0,0 +1,115 @@
    +    $this->total = (!$size || $size == -1) ? -1 : Bytes::toInt($size);
    +  }
    

    Minor: Not sure here if any coding standards apply who tell the same, but personally I prefer === (over ==) whenever possible. (4 times in this file.)

  3. +++ b/core/lib/Drupal/Component/Utility/EnvironmentMemory.php
    @@ -0,0 +1,115 @@
    +    return ($memory_limit == -1 || $memory_limit >= Bytes::toInt($required));
    

    Minor: I don't like too many brackets, certainly not if they are surrounding the whole expression, as they don't add any clarity in that case. (Several times in this file.)

  4. +++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
    @@ -436,4 +457,46 @@ public function extensionToImageType($extension) {
    +   * @return null|array
    +   *   NULL if there is sufficient memory to allow the operation. Otherwise, an
    

    Minor: I think it would be more intuitive to return TRUE or an array.

  5. +++ b/core/tests/Drupal/Tests/Component/Utility/EnvironmentMemoryTest.php
    @@ -0,0 +1,165 @@
    +      $free_memory = $limit - memory_get_usage(TRUE);
    +      $memory = new EnvironmentMemory();
    +      $this->assertTrue($memory->checkAvailable($free_memory - Bytes::toInt('4k')));
    

    Minor: revers these 2 lines, you are allocating memory after querying the amount of free memory, but before using it.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new12.07 KB
new35.87 KB

Added deprecation for Environment and EnvironmentTest.

#17:

1. Is this a bug? If so, that's in HEAD right now, the patches here do not touch that bit. That's not in scope of this issue, please file a separate one. (That may have a different priority/timeline than this, which is definitely heading towards 8.1.x or later)

2. OK, but needs to cast to int as $size can be a string in that context (either passed in or returned from ini_get).

3. OK

4. Yes, I do not like it either. Now GDToolkit::checkAvailableMemory returns a bool, but we need a separate property and getter to be able to get more information about the memory available/total/required.

5. OK

mondrake’s picture

StatusFileSize
new1.94 KB
new35.55 KB

Couple of glitches.

fietserwin’s picture

RE#18.1: Yes this is a "bug", but one that does not show as apparently PHP does not care too much when trying to set index NULL in an array. However it is the code in format_size() that sets the 'langcode" option to NULL. So, as we do touch that code, we can solve it now.
(AFAICS in Drupal the pattern is: $langcode as parameter may be null or absent, 'langcode' in $options parameter may be absent but not NULL.)

RE#18.2: I hadn't thought of -1 coming from ini, in which case a check for == would have been acceptable.

RE#18.4: I would have settled for returning array|true instead of array|null but this is even more strict.

I leave it up to you to change #17.1 here or in another issue. If the latter this one is RTBC.

+++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
@@ -488,15 +496,27 @@ public function checkAvailableMemory($width, $height, $bits_per_pixel = 32, $twe
+   * @return array
+   *   An associative array with information about required, free and total

As the property is typed as array[], we can type this @return as array[] as well.

+++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
@@ -436,4 +465,58 @@ public function extensionToImageType($extension) {
+  public function checkAvailableMemory($width, $height, $bits_per_pixel = 32, $tweak_factor = 1.7) {
+    $required = (int) ($width * $height * ($bits_per_pixel / 8) * $tweak_factor);
+    $memory = new EnvironmentMemory();

And while we are at it now anyway: in theory I guess we should floor ($bits_per_pixel / 8). GD uses 7 bits for transparency, giving 31 bits per pixels, but no way that GD does not use 4 bytes per pixel.

mondrake’s picture

Issue summary: View changes
StatusFileSize
new586 bytes
new35.56 KB

Updated issue summary.

#17.1 and following - separate issue please, I prefer not to touch here.

As the property is typed as array[], we can type this @return as array[] as well.

Done here.

in theory I guess we should floor ($bits_per_pixel / 8). GD uses 7 bits for transparency, giving 31 bits per pixels, but no way that GD does not use 4 bytes per pixel.

I am sorry I do not understand.

----------

I think we can leave this in NR for the time being - now the focus is on the release and this one will definitely not be there.

fietserwin’s picture

#17.1: I don't understand, a very simple local change.

RE last point#20/#21:
GD uses 31 bits per pixel (8 + 8 + 8 +7), but it will store these 31 bits in 4 bytes and not use the 32nd bit. So, if I would pass 31 as 3rd parameter, the calculation should still use 4 not 31/8.

Actually, you want to know the number of bytes used to store 1 pixel internally, not how many bits it uses for the channels.

mondrake’s picture

StatusFileSize
new1.59 KB
new35.78 KB

#22

re. #17.1 and following - I simply do not feel comfortable to do such a change here. That would need a test to show the failure, which I am not able to do. I think it would be better to do that in isolation from the changes in this issue, and get a separate review. It's not about the change you propose in itself, rather about the fact that it's something that is buggy in HEAD now regardless of this issue.

re. #20/#21 - I think I got the point now, made changes accordingly here

fietserwin’s picture

mondrake’s picture

Version: 8.0.x-dev » 8.1.x-dev
Status: Postponed » Needs review
mondrake’s picture

StatusFileSize
new1.69 KB
new35.78 KB

#1551686: Image rotate dimensions callback can calculate new dimensions for every angle has been committed. We can use the Rectangle class now to pre-determine h/w of the rotated image.

claudiu.cristea’s picture

Status: Needs review » Needs work
Issue tags: +Performance, +Security, +memory

@mondrake, nice work, really!

I would say this must go in 8.0.x because of some good reasons but it seems that Rectangle() is only in 8.1.x, right? Anyway, some points. Some of them maybe need comments.

  1. +++ b/core/includes/common.inc
    @@ -249,40 +249,28 @@ function check_url($uri) {
    +  $value = Bytes::toUnitAndSize($size);
    

    I would create Byte::formatSize() to deprecate format_size(). That can be wrapped in format_size(). Just an idea, opinions?

  2. +++ b/core/includes/common.inc
    @@ -249,40 +249,28 @@ function check_url($uri) {
    +      return \Drupal::translation()->formatPlural($value['size'], '1 byte', '@count bytes', [], $options);
    

    Not so sure what is better to use here: \Drupal::translation()->formatPlural() or new PluralTranslatableMarkup(). I see that \Drupal::translation()->formatPlural() doesn't wrap a service and calls the 2nd. I would go directly with new PluralTranslatableMarkup(). But it's up on you.

  3. +++ b/core/lib/Drupal/Component/Utility/Bytes.php
    @@ -44,4 +44,36 @@ public static function toInt($size) {
    +   * @return array
    +   *   An associative array with the following keys:
    +   *   - 'unit': unit of measurement (string)
    +   *   - 'size': size (float, rounded to 2 decimals).
    +   *   An empty array if the input value is negative.
    ...
    +      return [];
    ...
    +    return ['unit' => $unit, 'size' => round($size, 2)];
    

    I think in such cases we return stdClass. I don't have a strong opinion, but it seems to me more intuitive and DX.

  4. +++ b/core/lib/Drupal/Component/Utility/Environment.php
    @@ -9,6 +9,9 @@
    + * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0.
    + *   Use \Drupal\Component\Utility\EnvironmentMemory.
    
    @@ -28,18 +31,21 @@ class Environment {
    +   * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.0.
    +   *   Use \Drupal\Component\Utility\EnvironmentMemory:
    ...
       public static function checkMemoryLimit($required, $memory_limit = NULL) {
    

    I know that it's ugly but "Use" should go one line above.

  5. +++ b/core/lib/Drupal/Component/Utility/EnvironmentMemory.php
    @@ -0,0 +1,115 @@
    +   * Will be set to -1 when no memory limit is set or it is set to unlimited.
    ...
    +   *   The total memory, in bytes, available to PHP. Returns -1 when no memory
    ...
    +    $this->total = (!$size || (int) $size === -1) ? -1 : Bytes::toInt($size);
    ...
    +   *   The amount of memory, in bytes, free for use by PHP. Returns -1 when no
    ...
    +    return $this->getTotal() === - 1 ? -1 : $this->getTotal() - $this->getUsed();
    ...
    +    return $memory_limit === -1 || $memory_limit >= Bytes::toInt($required);
    

    Let's create a constant for -1. The code will be more readable and that can be used also outside to to test.

  6. +++ b/core/lib/Drupal/Component/Utility/EnvironmentMemory.php
    @@ -0,0 +1,115 @@
    +   * @param int|string $size
    

    It seems that we can pass also NULL? Then null should be added.

  7. +++ b/core/lib/Drupal/Component/Utility/EnvironmentMemory.php
    @@ -0,0 +1,115 @@
    +   *   bytes with optional SI or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G,
    ...
    +   *   with optional SI or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G, 6GiB,
    ...
    +   *   with optional SI or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G, 6GiB,
    

    "SI or IEC" can se add some @see links to the standards?

  8. +++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
    @@ -65,6 +67,13 @@ class GDToolkit extends ImageToolkitBase {
    +  protected $memoryInfo;
    
    @@ -436,4 +465,63 @@ public function extensionToImageType($extension) {
    +   * @return array[]
    +   *   An associative array with information about required, free and total
    +   *   memory.
    ...
    +  public function getMemoryInfo() {
    

    Maybe it's good to explain in docblock what keys holds this array (required, free, total).

  9. +++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
    @@ -436,4 +465,63 @@ public function extensionToImageType($extension) {
    +  public function checkAvailableMemory($width, $height, $bits_per_pixel = 31, $tweak_factor = 1.7) {
    
    +++ b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/CreateNew.php
    @@ -84,6 +84,12 @@ protected function validateArguments(array $arguments) {
    +    if (!$this->getToolkit()->checkAvailableMemory($arguments['width'], $arguments['height'])) {
    +      $memory = $this->getToolkit()->getMemoryInfo();
    

    Why not merging checkAvailableMemory() and getMemoryInfo(). The we can drop also the protected method. We return an empty array if ok (and that cast to FALSE) and the memory info array (cast to TRUE) when error. Then we need to reverse the semantics/naming of checkAvailableMemory()

mondrake’s picture

Assigned: Unassigned » mondrake

#27 thanks for review @claudiu.cristea.

Looking into it.

mondrake’s picture

Assigned: mondrake » Unassigned
Status: Needs work » Needs review
StatusFileSize
new36.75 KB
new7.87 KB

Re #27,

1. It's a nice idea but not for the Bytes class though - that's a 'component' and we should not call Drupal 'core' features from there. Since we need translation we cannot do that there. Maybe a separate class but I'd leave that to a follow up.
2. OK, gone for new PluralTranslatableMarkup.
3. I do not like stdClass - how about a namespaced class with just two public properties unit, size? Not changed yet.
4. Done
5. OK, added EnvironmentMemory::UNLIMITED
6. Done
7. Added @see
8. OK
9. I need to check because I think I did like that to allow unit testing.

So, points 3 and 9 still open.

Status: Needs review » Needs work

The last submitted patch, 29: 2583041-29.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new36.42 KB
new495 bytes

Whoops

mondrake’s picture

StatusFileSize
new36.26 KB
new9.18 KB

Addresses #27.9

Merged the two methods in one 'memoryCheck', and dropped the protected property.

In fact this is much better, thanks.

#27.3 still open, opinions?

claudiu.cristea’s picture

  1. +++ b/core/lib/Drupal/Component/Utility/EnvironmentMemory.php
    @@ -0,0 +1,127 @@
    +    $this->total = (!$size || (int) $size === -1) ? static::UNLIMITED : Bytes::toInt($size);
    

    Forgot a -1 :)

  2. +++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
    @@ -181,6 +185,24 @@ protected function load() {
    +    if (!empty($memory)) {
    
    +++ b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/CreateNew.php
    @@ -84,6 +84,12 @@ protected function validateArguments(array $arguments) {
    +    if (!empty($memory)) {
    
    +++ b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/Rotate.php
    @@ -98,6 +99,14 @@ protected function execute(array $arguments) {
    +    if (!empty($memory)) {
    

    if (!$memory) {...} ?

  3. +++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
    @@ -436,4 +458,54 @@ public function extensionToImageType($extension) {
    +  public function memoryCheck($width, $height, $bits_per_pixel = 31, $tweak_factor = 1.7) {
    

    Hm. We need something reversed semantically. Something that is TRUE when there's no enough memory. memoryExhausted(), memoryLimited()... I'm out of ideas.

mondrake’s picture

StatusFileSize
new36.98 KB
new10.71 KB

#33

1. That was on purpose, in my mind. We use the constant to track UNLIMITED within the class, but the result of ini_get() will still be -1 even if we change the value of the constant. Added a comment.

2, 3. Done

fietserwin’s picture

Please undo #33.3. IMO, this reduces readability. Method names should preferably be named positive, even if the result will always be checked with a ! in front of it. If you want to keep it this way, then I think, though I'm not native english, that unavailable is "uncommon", (i.e. not common), so then prefer not(Enough)Available. If you reverse then you could rename the less specific "memoryCheck" to a more explicit "isMemoryAvailable", that is IMO clearer.

claudiu.cristea’s picture

#27.3: Let's see how it looks with a dedicated class but that would need 2 setters (with chaining) and 2 getters, not public methods. It seems to me a little bit over-engeeniring.

#27.9: So, we agree with a single method? It's more simple and clear. The only thing left is the naming. This is tricky. When reveals a positive state (has memory) it returns nothing (empty array is casting to FALSE). When reveals a negative state (not enough memory) it returns something (a non-empty array that is casting to TRUE). What about memoryLimitReached()?

claudiu.cristea’s picture

Alternatively we can return mixed. We can return TRUE when there is memory and array when not. Then

if (($check = $this->getToolkit()->checkMemory(...)) !== TRUE) {
  ...
}
mondrake’s picture

So, re. #27.9 and following, before any coding: are we all OK on something like

the method to be called isMemoryAvailable, returning TRUE if memory is available, and the array with details if not

then it will be

   $memory = $this->isMemoryAvailable($this->getWidth(), $this->getHeight());
   if ($memory !== TRUE) {
     ... message/exception using $memory['free']['unit'] etc. ...
     return FALSE;
   }
claudiu.cristea’s picture

I'm OK with #38.

fietserwin’s picture

We could also return a bool and place the additional info in a(n optional) parameter that is passed by reference, as in preg_match()?

fietserwin’s picture

[REMOVED: same message, double clicked on the save button]

mondrake’s picture

#40: so something like

   if (!$this->isMemoryAvailable($this->getWidth(), $this->getHeight(), $memory_info)) {
     ... message/exception using $memory_info['free']['unit'] etc. ...
     return FALSE;
   }

I like it. @claudiu.cristea?

claudiu.cristea’s picture

Wait! Why not throwing the exception directly in isMemoryAvailable() and we're done? :)

claudiu.cristea’s picture

Issue tags: +#SprintLUX2016

I'm marking this to provide more review tomorrow, when I'm attending Drupal Global SprintWeekend Luxembourg.

fietserwin’s picture

isMemoryAvailable() is a helper function and should (IMO) therefore not log on its own, but leave that to the code that knows the context it's running in. E.g: I might be doing a batch conversion and only present/log overall results at the end, not 1 error message per failure. isMemoryAvailable() does not know this, but that calling code would.

claudiu.cristea’s picture

Issue tags: -#SprintLUX2016
mondrake’s picture

StatusFileSize
new10.63 KB
new36.35 KB

This implements #38. Both #40/42 and #43 would break PHP unit tests AFAICS.

Status: Needs review » Needs work

The last submitted patch, 47: 2583041-47.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new1.85 KB
new36.36 KB

Fixes.

mondrake’s picture

StatusFileSize
new11.44 KB
new38.34 KB

And this would be my idea for #27.3

Status: Needs review » Needs work

The last submitted patch, 50: 2583041-50.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new2.76 KB
new38.48 KB

Fixes for PHP unit tests.

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

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now 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.

mondrake’s picture

StatusFileSize
new38.11 KB

Rerolled.

Status: Needs review » Needs work

The last submitted patch, 54: 2583041-54.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new749 bytes
new30.38 KB

Fix for the kernel test.

mondrake’s picture

StatusFileSize
new749 bytes
new38.11 KB

Please disregard #56.

Fix for the kernel test.

Status: Needs review » Needs work

The last submitted patch, 57: 2583041-57.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review

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.

mondrake’s picture

Related issues: +#2157945: Deprecate format_size() and use Drupal\Core\StringTranslation\ByteSizeMarkup instead

I've split out part of the patch here into #2157945: Deprecate format_size() and use Drupal\Core\StringTranslation\ByteSizeMarkup instead. If that's committed then the patch here could be smaller and easier to 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.

xem8vfdh’s picture

+1, ran into this too.

mondrake’s picture

mondrake’s picture

Assigned: Unassigned » mondrake
Issue tags: +Needs reroll

Patch no longer applies.

mondrake’s picture

Title: GD toolkit & operations should check for available memory » [PP-1] GD toolkit & operations should check for available memory
Assigned: mondrake » Unassigned
Issue tags: -Needs reroll
StatusFileSize
new29.35 KB
new41.32 KB

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.

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.

lokapujya’s picture

I just got bit by this on a D7 site. The image wasn't uploaded through Drupal. The image url came from a web service, so there was no way the UI could have prevented the image. Can't catch the error because it's a memory issue, took a long time to figure out the root problem. Happy that I found this issue, but now I need to do something about it.

mondrake’s picture

Title: [PP-1] GD toolkit & operations should check for available memory » [PP-2] GD toolkit & operations should check for available memory
Status: Needs review » Postponed

I have spinned off the memory checking parts of the patch in #66 into an issue of its own, #3000050: Replace \Drupal\Component\Utility\Environment::checkMemoryLimit with a PHP memory checking helper class and service.

mondrake’s picture

Status: Postponed » Needs review
StatusFileSize
new28.98 KB

A reroll of #66, with the format_size refactoring expunged - that one is getting closer @ #2157945: Deprecate format_size() and use Drupal\Core\StringTranslation\ByteSizeMarkup instead

mondrake’s picture

Status: Needs review » Postponed
mondrake’s picture

Status: Postponed » Needs review
StatusFileSize
new29.13 KB
new2.35 KB
mondrake’s picture

Status: Needs review » Postponed
mondrake’s picture

Status: Postponed » Needs review
StatusFileSize
new29.13 KB
new2.35 KB
mondrake’s picture

Status: Needs review » Postponed

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.

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

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now 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.

kingdutch’s picture

Issue summary: View changes

Added postponed by issues to the issue summary since it's not readily apparent from the related issues.

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.

mondrake’s picture

Title: [PP-2] GD toolkit & operations should check for available memory » [PP-1] GD toolkit & operations should check for available memory
Status: Postponed » Active

#3000050: Replace \Drupal\Component\Utility\Environment::checkMemoryLimit with a PHP memory checking helper class and service will not happen.

In 2020 we need to see if this is still relevant as in 2015 - when there was no PHP 7 yet. Maybe now we can catch the memory error as a \Throwable.

mondrake’s picture

Title: [PP-1] GD toolkit & operations should check for available memory » GD toolkit & operations should catch \Throwable to fail gracefully in case of memory issues
Status: Active » Needs review
Issue tags: -Performance, -Security, -memory
StatusFileSize
new1.05 KB
new5.25 KB

Yes, \Throwable seems to do. No interdiff, new concept.

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

mondrake’s picture

#84 though works for kernel tests where, afaics, phunit handles warnings. In real sites the warning will be captured by drupal’s error handler and logged separately, and the memory failure leads to a warning in php 7. The effect is the same, anyway.

mondrake’s picture

StatusFileSize
new1.36 KB
new5.55 KB
new1.88 KB

Added a test that reduces the memory available before trying an operation.

Status: Needs review » Needs work

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

mondrake’s picture

So re #83 no, atm

Fatal error: Allowed memory size of 10487808 bytes exhausted (tried to allocate 81920 bytes) in /var/www/html/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/CreateNew.php on line 89

cannot be caught as a \Throwable. Not sure if it'd be possible by tampering with error handlers. If not, the only alternative I see is to go back to #76 and have a method that before trying a GD operation that allocates memory, pre-calculates the memory need and fails if not enough memory is available.

mondrake’s picture

mondrake’s picture

Issue summary: View changes
mondrake’s picture

Status: Needs work » Needs review

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.

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

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.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.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.

mondrake’s picture

Version: 9.5.x-dev » 10.1.x-dev
andypost’s picture

Curious if the method could be packaged to trait

mondrake’s picture

Rebased and added catching of errors thrown on image save - useful for #3202016: Let GDToolkit support AVIF image format.

mondrake’s picture

Title: GD toolkit & operations should catch \Throwable to fail gracefully in case of memory issues » GD toolkit & operations should catch \Throwable to fail gracefully in case of memory issues or other errors
andypost’s picture

Status: Needs review » Reviewed & tested by the community

Looks ready for commiters

alexpott’s picture

Issue tags: +Needs change record

I think we need a change record here to tell contrib modules that provide operations about the new functionality.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Needs work for #103 and @catch's and my code review.

mondrake’s picture

Status: Needs work » Needs review
mondrake’s picture

andypost’s picture

Status: Needs review » Reviewed & tested by the community

good idea with limit, hope it now ready for 10.0.x as CR states

mondrake’s picture

rebased

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

I'm really not sure about this change - or if we can make it in the way we're doing here. I've run some tests on memory usage for images on my local PHP 7.4...

vendor/bin/drush php
Psy Shell v0.10.12 (PHP 7.4.30 — cli) by Justin Hileman
Drush Site-Install (Drupal 9.5.0-dev)
>>> ini_set('memory_limit', '2G');
=> "4G"
>>> ini_get('memory_limit');
=> "2G"
>>> format_size(memory_get_usage());
=> Drupal\Core\StringTranslation\TranslatableMarkup {#4613
     markup: "29.24 MB",
   }
>>> format_size(\Drupal\system\Plugin\ImageToolkit\GDToolkit::getRequired(20000, 20000));
=> Drupal\Core\StringTranslation\TranslatableMarkup {#4619
     markup: "2.53 GB",
   }
>>>  $test = \Drupal::service('image.factory')->get(NULL, 'gd');
=> Drupal\Core\Image\Image {#4625}
>>> $a =  $test->createNew(20000, 20000);
=> true
>>> format_size(memory_get_usage());
=> Drupal\Core\StringTranslation\TranslatableMarkup {#4640
     markup: "29.68 MB",
   }
>>>

How sure are we that the function here is going to always prevent fails and do the right thing. For me this is better handled by max file upload sizes. PHP settings already handle that.

alexpott’s picture

I think maybe we should consider how we fail in image operations. Catching throwables, logging the error and returning FALSE seems like a great idea but doing memory calculations ourselves seems unnecessarily complex and handling the problem at the wrong point.

catch’s picture

Discussed this with @alexpott briefly in slack and after going around in circles a couple of times, I think we should do the throwables bit (i.e. what the issue title says), but not the memory calculation here. We could always move that to a follow-up if it needs more investigation/discussion.

mondrake’s picture

Assigned: Unassigned » mondrake
Status: Needs review » Needs work

OK

mondrake’s picture

Title: GD toolkit & operations should catch \Throwable to fail gracefully in case of memory issues or other errors » GD toolkit & operations should catch \Throwable to fail gracefully in case of errors
Issue tags: +Needs issue summary update

mondrake’s picture

Assigned: mondrake » Unassigned
Status: Needs work » Needs review

Closed MR11 to keep history and opened MR2860 for the new scope without available memory calculation.

smustgrave’s picture

Status: Needs review » Needs work

For the issue summary update requested in #113

mondrake’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs issue summary update

Updated issue summary.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

MR is up to date.
Tests are all green.
No build errors.
Tests were added for the bug.
Tests fail without the whole fix.

Looks good

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 23edd01 and pushed to 10.1.x. Thanks!

Only committed to 10.1.x as we're changing where errors are caught so this results in subtle code flow changes. Also I think we can delete the CR as I'm not sure one is necessary and the API talked about in the CR is not there.

  • alexpott committed 23edd01f on 10.1.x
    Issue #2583041 by mondrake, fietserwin, claudiu.cristea, alexpott, catch...

mondrake’s picture

Thanks - I do not think I can delete the CR, even in draft.

alexpott’s picture

Status: Fixed » Needs work

Unfortunately this change has introduced a random fail - see https://www.drupal.org/pift-ci-job/2556244 and https://www.drupal.org/pift-ci-job/2556296 - it seems to happen on postgres which is odd as I have no idea why this change would be DB related.

  • alexpott committed 9b44ffa4 on 10.1.x
    Revert "Issue #2583041 by mondrake, fietserwin, claudiu.cristea,...
andypost’s picture

Curious why memory is limited by 12MB as both failed runs reports

mondrake’s picture

#126 that is exactly what the test is doing, limiting memory

  /**
   * Tests resizing of an image that will exceed the memory available.
   */
  public function testInsufficientAvailableMemory(): void {
    $image = $this->imageFactory->get('core/tests/fixtures/files/image-test.png');

    $memory_in_use = memory_get_usage(TRUE);
    ini_set('memory_limit', $memory_in_use + 2048);
    $this->assertFalse($image->resize(200000, 200000));
  }

These failure tell us that catching a Throwable is (or may not always) be enough to prevent a fatal error when memory available is not sufficient for GD operations. Preventing that was the original intent of this issue. I start thinking this could be a problem with PHP builds, but very hard to debug.

I will drop these two tests (the first one is probably irrelevant in DrupalCI, it will be skipped if php.ini is not limiting memory), and add a new one with an operation throwing an exception. Hopefully that would be OK. But then we need a followup to keep trying to find a solution for insufficient memory issues.

mondrake’s picture

Status: Needs work » Needs review
daffie’s picture

Status: Needs review » Needs work

The testbot is not happy.

mondrake’s picture

Again an out-of-sync between test run and repo update, https://drupal.slack.com/archives/C51GNJG91/p1666607286832989

mondrake’s picture

Status: Needs work » Needs review
mondrake’s picture

Status: Needs review » Needs work

:( it was passing locally...

mondrake’s picture

Status: Needs work » Needs review

I don't know what I tested locally...but it could not have worked.

smustgrave’s picture

But then we need a followup to keep trying to find a solution for insufficient memory issues.

Does this still need a followup?

mondrake’s picture

smustgrave’s picture

Do you have a recommended way to manually test? Tried lowering my memory and using the snippet in the IS but couldn't trigger.

mondrake’s picture

@smustgrave I think we need to leave memory testing to the followup. The automated tests cover the base case of any throwable being thrown. Apparently the memory issues are not catchable, or at least not under some PHP configurations. See the commit that was reverted, #124, #127.

smustgrave’s picture

Can the issue summary be updated please. Since this is not about memory.

mondrake’s picture

Issue summary: View changes

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Thank you!

Tested this out by editing Drupal\system\Plugin\ImageToolkit\Operation\gd\Convert changing line 59 to

if ($this->getToolkit()->apply('creatfasde_new', $data)) {

Without the patch when I create a media object I get 2 errors in the logs

Image convert failed using the gd toolkit on and

The selected image handling toolkit 'gd' can not process operation 'creatfasde_new'.

Applied the fix

Created another media object

No errors logged.

mondrake’s picture

Status: Reviewed & tested by the community » Needs work

Mmmm that’s not right then. Missing plugin should be logged and fail the effect - we have regression here.

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

I repeated the steps in #141, but just saving an image style containing a convert effect through the configuration UI. Both without and with the patch, after clicking 'save' on the image style configuration form I get the two errors logged, which means to me the missing plugin is properly handled.

Back to RTBC.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 4e49b11 and pushed to 10.1.x. Thanks!

  • alexpott committed 4e49b11a on 10.1.x
    Issue #2583041 by mondrake, fietserwin, claudiu.cristea, alexpott,...

Status: Fixed » Closed (fixed)

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