Yet another read-modify-write race

Mon 17 November 2014

Race condition is likely to happen, especially when programmers don't know what scale means.

We're talking about a Wordpress website with a moderately high load. The scheduled post functionality is not functioning properly. All scheduled posts ends up in missed schedule state.

Initial investigation revealed cron was not set in the database. Typically, when a user schedules a post in Wordpress, the system will create a new cron entry in the Wordpress self-managed cron facility. Later, Wordpress will access wp-cron.php and run the cron task. The cron was serialized first and stored in wp-options as an option entry. However, in this setup, the entry never shows up in MySQL console, even if I observed other typical data you would expect in cron, such as, update checks.

Confusing as hell, I made multiple attempts in monitoring database transaction and PHP trace. Surprisingly, database query log indicates Wordpress is updating option_name=cron in wp_posts at 20 qps. Backtrace revealed that Wordpress will schedule cron task on every request ref.

Wordpress developers assumed most sites are not heavy loaded. Thus, the design decision (if they have any design) is to store all cron task entries in one database row and use a read-unserialize-append-serialize-write workflow. Such workflow works perfectly fine when we're taking about small to medium scaled websites with no more than 10 page visits per minute. Well, probably a few more. However, it implied no data would be changed between read and write. Unfortunately, when we're talking about heavy loaded site, you would expect tens or even hundreds of queries hitting your box, rendering the assumption unsafe. Yet another read-modify-write-race.

The fix? Disable the damn update check! The caveat? You will be managing updates manually. You want to do that anyway...

remove_action( 'init', 'wp_schedule_update_checks' );
remove_action( 'load-plugins.php', 'wp_update_plugins' );
remove_action( 'load-themes.php', 'wp_update_themes' );
remove_action( 'load-update-core.php', 'wp_update_plugins' );
remove_action( 'load-update-core.php', 'wp_update_themes' );
remove_action( 'load-update.php', 'wp_update_plugins' );
remove_action( 'load-update.php', 'wp_update_themes' );
remove_action( 'upgrader_process_complete', 'wp_update_plugins' );
remove_action( 'upgrader_process_complete', 'wp_update_themes' );
remove_action( 'upgrader_process_complete', 'wp_version_check' );
remove_action( 'wp_maybe_auto_update', 'wp_maybe_auto_update' );
remove_action( 'wp_update_plugins', 'wp_update_plugins' );
remove_action( 'wp_update_themes', 'wp_update_themes' );
remove_action( 'wp_version_check', 'wp_version_check' );

Comments