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
Comment #1
dgtlmoon commentedComment #2
dgtlmoon commentedComment #3
bmunslow commentedThank 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?
Comment #4
dgtlmoon commentedIs 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
Comment #5
bmunslow commentedI 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.
Comment #6
leksat commentedFrom 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:
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?
Comment #7
leksat commentedComment #8
pub497 commentedThe 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.