menu_execute_active_handler() is smart enough to rebuild the menus if the menu_mask variable isn't already set due to some race condition.

    // Rebuild if we know it's needed, or if the menu masks are missing which
    // occurs rarely, likely due to a race condition of multiple rebuilds.
    if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
      menu_rebuild();
    }

However, earlier in the page generation process we might have already died from the call chain

drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 => _drupal_bootstrap_full();
 => menu_set_custom_theme();
 => menu_get_custom_theme(TRUE);
 => menu_get_item();

This throws errors like

PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')) ORDER BY fit DESC LIMIT 0, 1' at line 2: SELECT menu_router.* FROM {menu_router} menu_router WHERE (path IN ()) ORDER BY fit DESC LIMIT 0, 1; Array ( )  in menu_get_item() (line 404 of C:\wamp\www\drupal7\includes\menu.inc).

(as seen in #619542: Malformed theme .info files break menu_router generation from a different cause).

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

bfroehle’s picture

The solution is likely to be adding something like

if (!variable_get('menu_masks', array())) {
  menu_rebuild();
}

in the appropriate place which may be immediately before the menu_get_item() call in menu_get_custom_theme(), or perhaps in menu_get_ancestors() (which is the only function that actually uses the 'menu_masks' variable).

quartsize’s picture

Status: Active » Needs work
FileSize
1.46 KB

Issues for historical perspective: #261148: menu_masks variable is empty (race condition) and #202955: Access denied after install - menu_rebuild() calls.

My inclination is toward menu_get_ancestors(), since this issue has has the potential to manifest itself in any situation where menu_get_ancestors() is called, either directly, as it is a public function, or via menu_get_item(), and the menu needs (re)built (see also #1069922: Boost installation fails when using installation profile to install). Moreover, as witnessed by menu_get_item(), callers do not seem to expect an empty array as a possible return value from menu_get_ancestors() (menu_get_item() will generate an invalid SQL query). The trouble with doing that is that menu_get_ancestors() may be called indirectly by menu_rebuild() itself, causing an infinite loop, and if stale data is okay in some situations, it's a waste of cycles.

I have a patch which tries to avoid the former by checking for the lock. I don't like it because I really want to know if menu_rebuild() is higher in the call stack, not if it's running in another thread, but in the interest of helping this issue along, I'm attaching it.

sun’s picture

Version: 7.x-dev » 8.x-dev
Category: bug » task

Lacking clear steps to reproduce this issue, this is a task.

quartsize’s picture

Category: task » bug
FileSize
768 bytes
  1. Place the attached module in sites/all/modules.
  2. Add dependencies[] = repro1026474 to profiles/minimal/minimal.info.
  3. Install Drupal, selecting the Minimal installation profile.
  4. Receive something like
    An AJAX HTTP error occurred. HTTP Result Code: 500 Debugging information follows. Path: http://localhost/d7test/install.php?profile=minimal&locale=en&id=1&op=do StatusText: Service unavailable (with message) ResponseText: PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ') ORDER BY fit DESC LIMIT 0, 1' at line 1: SELECT * FROM {menu_router} WHERE path IN () ORDER BY fit DESC LIMIT 0, 1; Array ( ) in menu_get_item() (line 445 of /var/www/d7test/includes/menu.inc).

    when Drupal goes to enable repro1026474.

The Minimal profile is used because the Standard includes menu as dependency, the first action of which when enabling is to call menu_rebuild(). The fundamental issue is that it is possible for menu_get_item() to be called before the menu_masks are built, but menu_get_item() assumes, via menu_get_ancestors(), that the masks are not in need of building any time it is called.

sun’s picture

Issue tags: +Needs backport to D7

To safe others from the hazzle of downloading and extracting, here's the actual and only code of that module:

function repro1026474_install() {
  $item = menu_get_item('admin');
}
catch’s picture

Status: Needs work » Closed (duplicate)