It seems that having your site in 'maintenance mode' (drush vset maintenance_mode 1) means that hook_init is still fired across all your modules, this can cause deadlocks due to many modules causing an indirect update on cache_field

In my case it was caused when

- I put the site in maintenance mode
- There was active traffic to the website that hits one or more uncached pages
- Used features to revert/update a new version of a feature that had a new field

If there is a request for a page that is NOT in the cache, it causes Drupal to execute all hook_init implementations across your active modules, many commonly used modules use this opportunity to update 'cache_field' either directly on indirectly

My 'hack' was to implement a hook_boot that checked if there was a request at /node/ or whatever and returned a 503 Service Unavailable - When this was inplace I was able to revert features without worrying about public request screwing up the cache_field table and causing deadlocks whilst features was doing its business.

// Implements HOOK_BOOT
function sadhack_boot() {
  if (variable_get('maintenance_mode', FALSE)) {
    if(check_some_things_that_identify_a_public_request_and_then_bail()) {
      header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable', TRUE, 503);
      exit();
    }
  }
}

It seems to me that Drupal (7) is only protecting against hook_init running whilst update.php is running and does not really take into account other situations

From common.inc line 5205

  // Let all modules take action before the menu system handles the request.
  // We do not want this while running update.php.
  if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
    // Prior to invoking hook_init(), initialize the theme (potentially a custom
    // one for this page), so that:
    // - Modules with hook_init() implementations that call theme() or
    //   theme_get_registry() don't initialize the incorrect theme.
    // - The theme can have hook_*_alter() implementations affect page building
    //   (e.g., hook_form_alter(), hook_node_view_alter(), hook_page_alter()),
    //   ahead of when rendering starts.
    menu_set_custom_theme();
    drupal_theme_initialize();
    module_invoke_all('init');
  }

MAINTENANCE_MODE is only defined in authorize, errors, install and update, the define() is pretty much limited to that process (wont make bootstrap do something different unless you hit it from those methods below)

authorize.php:define('MAINTENANCE_MODE', 'update');
includes/errors.inc:      define('MAINTENANCE_MODE', 'error');
install.php:define('MAINTENANCE_MODE', 'install');
update.php:define('MAINTENANCE_MODE', 'update');

I thought about that perhaps drush should be defining that MAINTENANCE_MODE, but that would only mean that requests are safe from hook_init dirtyness from drush..

Solutions I can think of

- Make update.php store your IP address and only allow that module_invoke_all('init') to occur for your IP address for the duration of the update.php batch process
- Make that extra check around module_invoke_all('init'); be aware when drush is running and in maintenance mode, and only allow requests when drush is completed (by unsetting that variable? seems risky)

So hmm, unsure, but it solved our deadlocks!

Comments

dgtlmoon’s picture

Issue summary: View changes
dgtlmoon’s picture

Title: Maintenance mode firing hook_init on uncached page requests, causing deadlocks on cache_field » Maintenance mode not protecting against uncached page requests triggering hook_init, causes deadlocks on cache_field when running features revert and others
bmunslow’s picture

Thank you for shedding some light on this issue @dgtlmoon.

This is a very annoying issue. In my case, turning off the crontab while reverting features helps mitigate it a little, although it still happens when someone hits an uncached page, like you mentioned.

Could you expand a little bit on the function check_some_things_that_identify_a_public_request_and_then_bail()?

What parameters have you found useful checking?

dgtlmoon’s picture

check_some_things_that_identify_a_public_request_and_then_bail()

Is entirely up to you :) In my case the Drupal install only served JSON pages so I made it give that 503 error for those requests

Perhaps for you, you could make it check for anonymous users

bmunslow’s picture

I ended up avoiding Drupal's maintenance mode entirely.

I wrote a post which discusses the way around it.

I basically decided to create a small .htaccess snippet which bypasses Drupal entirely when active and displays a static .html file.

leksat’s picture

From my experience the issue happens on Drupal 8 as well.

Once we had a deployment problem on a D8 website that has a lot of traffic. It was decided to restore the database from a backup. Database was around 5gb. The SQL import worked really slowly and in the end failed with a deadlock even if we used maintenance mode. Same happened on the second attempt. We were able to import the database only after we shut down the traffic.

The problem is that in maintenance mode Drupal still needs to do a full bootstrap.

Disabling the traffic is also not very good thing, because sometimes you need an access to UI to say create new content during the deployment.

My idea would be to have a JSON file in the webroot. Example contents:

{
  "allowed sessions": {
    "SSESS1b55a620123gqw35g": "gq345gq34gq34_gjZL1oFww",
    "SSESS2qg34gq3434g9b053": "sgnhq5amj64uyata5hq35h5"
  },
  "maintenance page content": {
    "x-default": {
      "title": "Maintenance mode",
      "content": "We should be back shortly."
    },
    "ru": {
      "title": "На обслуживании",
      "content": "Скоро вернемся."
    }
  }
}

Overall:
- if file presents in the webroot => maintenance mode is on
- if user has a cookie listed in "allowed sessions" => do the full bootstrap as usual
- otherwise => show maintenance mode page with content from "maintenance page content" (user language is taken from browser request)

For the maintenance page we would need no database and very limited amount of includes/libraries. The page template can be a simple .tpl.php file.

Any thoughts?

leksat’s picture

Title: Maintenance mode not protecting against uncached page requests triggering hook_init, causes deadlocks on cache_field when running features revert and others » Maintenance mode not protecting against deadlocks
pub497’s picture

The hack seems to work for me - I had to add a check for drush_main otherwise drush commands may fail to execute with the exit() function.

if (!function_exists('drush_main')) {
        exit();
}

Status: Active » Closed (outdated)

Automatically closed because Drupal 7 security and bugfix support has ended as of 5 January 2025. If the issue verifiably applies to later versions, please reopen with details and update the version.