Problem/Motivation

After upgrading to the version 3.5 which uses MR from this issue: #3524748: Full table scan against table router, we have started experiencing issue when image styles are not being generated at all, instead returning 404.

Comments

elaman created an issue. See original summary.

leo pitt’s picture

This is the fasts 404 config we have:

$settings['fast404_exts'] = '/^(?!\/robots)^(?!\/system\/files).*\.(txt|pdf|docx?|csv|xlsx?|webp|avif|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i';
$settings['fast404_not_found_exception'] = TRUE;
$settings['fast404_path_check'] = TRUE;
$settings['fast404_HTML_error_page'] = './sites/default/misc/404.html';
$settings['fast404_respect_redirect'] = TRUE;
pobster’s picture

Please install devel_php (or whatever you'd use for debug), and type:

// This is identical to fast404s version:
function getCandidateOutlines(array $parts) {
    $number_parts = count($parts);
    $ancestors = [];
    $length = $number_parts - 1;
    $end = (1 << $number_parts) - 1;
    // The highest possible mask is a 1 bit for every part of the path. We will
    // check every value down from there to generate a possible outline.
    if ($number_parts == 1) {
      $masks = [
        1,
      ];
    }
    elseif ($number_parts <= 3 && $number_parts > 0) {
      // Optimization - don't query the state system for short paths. This also
      // insulates against the state entry for masks going missing for common
      // user-facing paths since we generate all values without checking state.
      $masks = range($end, 1);
    }
    elseif ($number_parts <= 0) {
      // No path can match, short-circuit the process.
      $masks = [];
    }
    else {
      // Get the actual patterns that exist out of state.
      $masks = (array) \Drupal::state()
        ->get('routing.menu_masks.router', []);
    }
    // Only examine patterns that actually exist as router items (the masks).
    foreach ($masks as $i) {
      if ($i > $end) {
        // Only look at masks that are not longer than the path of interest.
        continue;
      }
      elseif ($i < 1 << $length) {
        // We have exhausted the masks of a given length so decrease the length.
        --$length;
      }
      $current = '';
      for ($j = $length; $j >= 0; $j--) {
        // Check the bit on the $j offset.
        if ($i & 1 << $j) {
          // Bit one means the original value.
          $current .= $parts[$length - $j];
        }
        else {
          // Bit zero means wildcard.
          $current .= '%';
        }
        // Unless we are at offset 0, add a slash.
        if ($j) {
          $current .= '/';
        }
      }
      $ancestors[] = '/' . $current;
    }
    return $ancestors;
  }

// Edit this path with an actual filepath which isn't working for you.
$path = '/sites/default/files/styles/this-is-a-style/something.png';
$parts = preg_split('@/+@', mb_strtolower($path), -1, PREG_SPLIT_NO_EMPTY);
dsm($parts);

$ancestors = getCandidateOutlines($parts);
dsm($ancestors);

$sql = "SELECT 1 FROM {router} WHERE [pattern_outline] IN ( :patterns[] ) AND [number_parts] >= :count_parts";
$result = \Drupal::database()->query($sql, [
  ':patterns[]' => $ancestors,
  ':count_parts' => count($parts),
])->fetchField();
dsm($result);

For reference, I see this:

array:6 [
  0 => "sites"
  1 => "default"
  2 => "files"
  3 => "styles"
  4 => "this-is-a-style"
  5 => "something.png"
]
array:40 [
  0 => "/sites/default/files/styles/this-is-a-style/something.png"
  1 => "/sites/default/files/styles/this-is-a-style/%"
  2 => "/sites/default/files/styles/%/something.png"
  3 => "/sites/default/files/styles/%/%"
  4 => "/sites/default/files/%/this-is-a-style/something.png"
  5 => "/sites/default/files/%/this-is-a-style/%"
  6 => "/sites/default/files/%/%/something.png"
  7 => "/sites/default/files/%/%/%"
  8 => "/sites/default/%/styles/this-is-a-style/%"
  9 => "/sites/default/%/styles/%/something.png"
  10 => "/sites/default/%/%/%/something.png"
  11 => "/sites/%/files/styles/this-is-a-style/something.png"
  12 => "/sites/%/files/styles/%/something.png"
  13 => "/sites/%/files/%/this-is-a-style/something.png"
  14 => "/sites/%/files/%/this-is-a-style/%"
  15 => "/sites/%/%/%/%/%"
  16 => "/sites/default/files/styles/this-is-a-style"
  17 => "/sites/default/files/styles/%"
  18 => "/sites/default/files/%/this-is-a-style"
  19 => "/sites/default/files/%/%"
  20 => "/sites/default/%/styles/this-is-a-style"
  21 => "/sites/default/%/styles/%"
  22 => "/sites/default/%/%/%"
  23 => "/sites/%/files/styles/this-is-a-style"
  24 => "/sites/%/files/styles/%"
  25 => "/sites/%/files/%/this-is-a-style"
  26 => "/sites/default/files/styles"
  27 => "/sites/default/files/%"
  28 => "/sites/default/%/styles"
  29 => "/sites/default/%/%"
  30 => "/sites/%/files/styles"
  31 => "/sites/%/files/%"
  32 => "/sites/%/%/styles"
  33 => "/sites/%/%/%"
  34 => "/sites/default/files"
  35 => "/sites/default/%"
  36 => "/sites/%/files"
  37 => "/sites/default"
  38 => "/sites/%"
  39 => "/sites"
]
"1"

Where "1" is that this path is found in my router table.

edit: for me, it's very likely finding:

name: image.style_public
path: /sites/default/files/styles/{image_style}/{scheme}
pattern_outline: /sites/default/files/styles/%/%
andmor’s picture

OK here is the output:

array:60 [
  0 => "/sites/default/files/styles/thumbnail/public/test.jpg"
  1 => "/sites/default/files/styles/thumbnail/public/%"
  2 => "/sites/default/files/styles/thumbnail/%/test.jpg"
  3 => "/sites/default/files/styles/thumbnail/%/%"
  4 => "/sites/default/files/styles/%/public/test.jpg"
  5 => "/sites/default/files/styles/%/public/%"
  6 => "/sites/default/files/%/thumbnail/public/test.jpg"
  7 => "/sites/default/files/%/thumbnail/public/%"
  8 => "/sites/default/files/%/thumbnail/%/test.jpg"
  9 => "/sites/default/files/%/thumbnail/%/%"
  10 => "/sites/default/files/%/%/public/test.jpg"
  11 => "/sites/default/files/%/%/%/test.jpg"
  12 => "/sites/default/files/%/%/%/%"
  13 => "/sites/default/%/%/%/%/%"
  14 => "/sites/%/files/styles/thumbnail/public/test.jpg"
  15 => "/sites/%/files/styles/thumbnail/public/%"
  16 => "/sites/%/files/styles/%/public/test.jpg"
  17 => "/sites/%/files/styles/%/%/%"
  18 => "/sites/%/%/styles/%/%/%"
  19 => "/sites/default/files/styles/thumbnail/public"
  20 => "/sites/default/files/styles/thumbnail/%"
  21 => "/sites/default/files/styles/%/public"
  22 => "/sites/default/files/styles/%/%"
  23 => "/sites/default/files/%/thumbnail/public"
  24 => "/sites/default/files/%/thumbnail/%"
  25 => "/sites/default/files/%/%/public"
  26 => "/sites/default/files/%/%/%"
  27 => "/sites/default/%/styles/thumbnail/%"
  28 => "/sites/default/%/styles/%/public"
  29 => "/sites/default/%/%/%/public"
  30 => "/sites/%/files/styles/thumbnail/public"
  31 => "/sites/%/files/styles/%/public"
  32 => "/sites/%/files/%/thumbnail/public"
  33 => "/sites/%/files/%/thumbnail/%"
  34 => "/sites/%/%/styles/thumbnail/%"
  35 => "/sites/%/%/%/%/%"
  36 => "/sites/default/files/styles/thumbnail"
  37 => "/sites/default/files/styles/%"
  38 => "/sites/default/files/%/thumbnail"
  39 => "/sites/default/files/%/%"
  40 => "/sites/default/%/styles/thumbnail"
  41 => "/sites/default/%/%/%"
  42 => "/sites/%/files/styles/thumbnail"
  43 => "/sites/%/files/styles/%"
  44 => "/sites/%/files/%/thumbnail"
  45 => "/sites/%/%/styles/%"
  46 => "/sites/default/files/styles"
  47 => "/sites/default/files/%"
  48 => "/sites/default/%/styles"
  49 => "/sites/default/%/%"
  50 => "/sites/%/files/styles"
  51 => "/sites/%/files/%"
  52 => "/sites/%/%/styles"
  53 => "/sites/%/%/%"
  54 => "/sites/default/files"
  55 => "/sites/default/%"
  56 => "/sites/%/files"
  57 => "/sites/default"
  58 => "/sites/%"
  59 => "/sites"
]

No "1" in the end and Status message is false

It looks like the problem might be that in your case, your file path is:

/sites/default/files/styles/this-is-a-style/something.png

For us this is more like:

/sites/default/files/styles/this-is-a-style/public/something.png

If I run the above code setting $path = /sites/default/files/styles/thumbnail/test.jpg, I do get the "1" on the output but the file actually does not exist on that path

andmor’s picture

Other settings that might be relevant:
- Under admin/config/media/file-system what do you select for "Default download method"? We have "Public local files served by the webserver."
- On our settings.php we set:

$settings['file_public_path'] = 'sites/default/files';
$settings['file_private_path'] = '/var/www/private';
pobster’s picture

The thing is ...

/sites/default/files/styles/this-is-a-style/public/something.png

Should have still matched on:

  22 => "/sites/default/files/styles/%/%"

It's in your output, so that's strange.

Can you use drush sqlc (or whatever you use to connect to your database). And run:

mysql> SELECT name, path, pattern_outline, number_parts FROM router WHERE pattern_outline LIKE '/sites/default/files/styles/%'\G
*************************** 1. row ***************************
           name: image.style_public
           path: /sites/default/files/styles/{image_style}/{scheme}
pattern_outline: /sites/default/files/styles/%/%
   number_parts: 6
1 row in set (0.01 sec)

See, the query in fast404 does a "more than or equals" on number_parts so as long as this exists at all in your router table - then it should match.

andmor’s picture

Strange indeed. It seems I get the same output as you:

MariaDB [drupal_db]> SELECT name, path, pattern_outline, number_parts FROM router WHERE pattern_outline LIKE '/sites/default/files/styles/%'\G
*************************** 1. row ***************************
           name: image.style_public
           path: /sites/default/files/styles/{image_style}/{scheme}
pattern_outline: /sites/default/files/styles/%/%
   number_parts: 6
1 row in set (0.001 sec)

BTW, as expected the images work when using the new v8.x-3.6

andmor’s picture

on your code posted above to run on php_devel, if I remove the number of parts from the SQL query, I do get a match:

$sql = "SELECT 1 FROM {router} WHERE [pattern_outline] IN ( :patterns[] )";
$result = \Drupal::database()->query($sql, [
  ':patterns[]' => $ancestors,
])->fetchField();

This gives me status=1

andmor’s picture

Same if I subtract "1" or any greater number

$sql = "SELECT 1 FROM {router} WHERE [pattern_outline] IN ( :patterns[] ) AND [number_parts] >= :count_parts";
$result = \Drupal::database()->query($sql, [
  ':patterns[]' => $ancestors,
  ':count_parts' => (count($parts) - 1),
])->fetchField();

Also gives me status=1

pobster’s picture

That's more than just bizarre; logically, that's impossible!

See, the comparison is:

[number_parts] >= :count_parts

So, more than or equals ... it makes no sense at all that subtracting one from your $parts would then cause a match? Because:
1. If it were 6 and not working - it'd be 5 and so below the number_parts value - but it should have matched on 6 anyway
2. If it were 7 and not working - it'd be 6 and so equal to the number_parts value - but it would have been greater anyway
3. Anything above this isn't relevant, as it's always higher than the number_parts value
4. Anything below 6 isn't relevant, as it's always lower than the number_parts value - and shouldn't match

Subtracting shouldn't work; only adding one should have made any difference.

BTW, the issue with removing that clause completely is that then you'll match on:

  59 => "/sites"

Which is way too loose, and kind of defeats the purpose of having fast404 anyway...

I'm wondering what you get if you use:

mysql> SELECT name, path, pattern_outline, number_parts FROM router WHERE pattern_outline IN ('/sites/default/files/styles/%/%') AND number_parts >= 6\G
*************************** 1. row ***************************
           name: image.style_public
           path: /sites/default/files/styles/{image_style}/{scheme}
pattern_outline: /sites/default/files/styles/%/%
   number_parts: 6
1 row in set (0.01 sec)

If that does work, maybe MariaDB is imposing a limit on the number of values in your IN statement?

Incidentally, version 3.6 works, as this change has been reverted. Obviously not ideal, as it still performs the full table scan we were trying to avoid... Also, Drupal core uses this as-is! So it's very strange that it's not working as expected.

andmor’s picture

For our example path /sites/default/files/styles/this-is-a-style/public/something.png: count($parts) returns 7.
The router table entry in our database for /sites/default/files/styles/%/% has number_parts: 6

MariaDB [drupal_db]> SELECT name, path, pattern_outline, number_parts FROM router WHERE pattern_outline LIKE '/sites/default/files/styles/%/%'\G
*************************** 1. row ***************************
           name: image.style_public
           path: /sites/default/files/styles/{image_style}/{scheme}
pattern_outline: /sites/default/files/styles/%/%
   number_parts: 6
1 row in set (0.000 sec)

So the condition

[number_parts] >= :count_parts

would compare as

6 >= 7

So no match on /sites/default/files/styles/%/%

pobster’s picture

Apologies, been away this week for my birthday. Okay, thank you - this explains everything, I'll have a think about what the best thing to do is... It is a bit strange that this must affect Drupal core as well, but I'll work out what the best approach is—no worries.

andmor’s picture

No problem. Thank you for your efforts on this. Let me know when you need further testing.

joseph.olstad’s picture

@pobster, if you find a solution for this it would be great and we'll try again for version 3.7!

oligerd’s picture

I have the same issue after update Drupal core to 11.3.2 version.
Fast 404 version is 3.6.
With $settings['fast404_path_check'] = TRUE;
all /admin/* pages have 404
and image styles are not being generated.

joseph.olstad’s picture

@oligerd, this was reverted for 3.6. No guarantees it goes back in.
If you're still seeing this with 3.6 it's due to an incorrect upgrade or that you're storing contrib module code in your git project rather than using composer and you performed a botched update (not recommended).

jhiver’s picture

Hello,

I had the exact same issue and the patch proposed in this new issue resolved the problem.

joseph.olstad’s picture

Thanks @jhiver for getting my attention.

https://www.drupal.org/project/fast_404/releases/8.x-3.7

joseph.olstad’s picture

Category: Bug report » Support request
Status: Active » Fixed

Marking this as a support request.

Calling it fixed.

Creditting all above.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

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