--- includes/lock.inc.orig 1969-12-31 19:00:00.000000000 -0500 +++ includes/lock.inc 2005-04-28 23:38:58.000000000 -0500 @@ -0,0 +1,214 @@ + + * + * NOTE: For an rlock to do any good, all instances where the data is + * actually modified also needs to be wrapped in a wrlock. + * + * @param $name + * The name of the lock. + * @param $block + * Specify whether or not to block when trying to obtain a lock. If blocking, + * the function will sleep until it has the lock. If not blocking, the + * function will try to obtain the lock once and if it fails it will return + * FALSE. Expects LOCK_BLOCK or LOCK_NO_BLOCK. + * @param $attributes + * Optional array of attributes to pass to the locking functions. (currently + * unused) + * @return + * Key for the lock if successful, or FALSE if failed to obtain lock. + */ +function lock_rlock($name, $block = 1, $attributes = array()) { + $type = variable_get('lock_type', 'file'); + $function = "lock_$type"."_rlock"; + if (variable_get('lock', 0) && isset($name)) { + return $function($name, $block, $attributes); + } + else { + // locking disabled, act as though we obtained the lock + return TRUE; + } +} + +/** + * Obtain a write lock. + * + * A write lock is an exclusive lock that only one process at a time can own. + * The idea behind a write lock is to obtain it before writing data that + * another process could be reading. When properly implemented this + * guarantees that you will not change data while another process is reading + * it. + * + * Additionally, a write lock can be used to prevent two processes from trying + * to update data at the same time. + * + * Sample usage: + * + * + * @param $name + * The name of the lock. + * @param $block + * Specify whether or not to block when trying to obtain a lock. If blocking, + * the function will sleep until it has the lock. If not blocking, the + * function will try to obtain the lock once and if it fails it will return + * FALSE. Expects LOCK_BLOCK or LOCK_NO_BLOCK. + * @param $attributes + * Optional array of attributes to pass to the locking functions. (currently + * unused) + * @return + * Key for the lock if successful, or FALSE if failed to obtain lock. + */ +function lock_rwlock($name, $block = 1, $attributes = array()) { + $type = variable_get('lock_type', 'file'); + $function = "lock_$type"."_rwlock"; + if (variable_get('lock', 0) && isset($name)) { + return $function($name, $block, $attributes); + } + else { + // locking disabled, act as though we obtained the lock + return TRUE; + } +} + +/** + * Release a lock. + * + * This function will release both an rlock and a rwlock. There is usually + * no need to check the return from this function, as a failure usually + * just indicates that the lock was already released. + * + * @param $name + * The name of the lock. + * @param $key + * The key that was returned when obtaining the lock. This is necessary + * for the unlock function to be able to release the lock. + * @return + * TRUE on success, FALSE on failure. + */ +function lock_unlock($name, $key = 0, $attributes = array()) { + $type = variable_get('lock_type', 'file'); + $function = "lock_$type"."_unlock"; + if (variable_get('lock', 0) && isset($name)) { + return $function($name, $block, $attributes); + } + else { + // locking disabled, act as though we released the lock + return TRUE; + } +} + + +/** + * Internal functions + */ +function lock_file_rlock($name, $block, $attributes) { + // SH = shared lock, NB = no blocking + $flags = ($block ? LOCK_SH : LOCK_SH|LOCK_NB); + // file locking ignores $attributes + return lock_file_lock($name, $flags); +} + +function lock_file_rwlock($name, $block, $attributes) { + // EX = exclusive lock, NB = no blocking + $flags = ($block ? LOCK_EX : LOCK_EX|LOCK_NB); + // file locking ignores $attributes + return lock_file_lock($name, $flags); +} + +function lock_file_lock($name, $flags) { + if ($path = _lock_file_get_path($name)) { + // file contents don't matter, open for writing + if ($fd = @fopen($path, 'w')) { + if (!file_exists($path)) { + // only bother to actually write something when the file doesn't exist + fwrite($fd, '0'); + } + if (flock($fd, $flags)) { + // we have the lock, return the file descriptor + return $fd; + } + @fclose($fd); + } + } + return FALSE; +} + +function lock_file_unlock($name, $fd, $attributes) { + if ($fd) { + if (flock($fd, LOCK_UN)) { + // successfully unlocked the lock + return TRUE; + } + } + return FALSE; +} + +function _lock_file_get_path($name = NULL) { + $path = file_create_path(variable_get('lock_path', 'locks')); + if ($name) { + $path = "$path/". _lock_file_sanitize_name($name); + } + return $path; +} + +/** + * Convert string to only contain alphanumerics, replacing everything else + * with the '_' character. + */ +function _lock_file_sanitize_name($name) { + return preg_replace('/[^0-9a-zA-Z]/', '_', $name); +} + +/** + * Walk through lock directory, deleting lock files that haven't been used + * recently. We default to slightly more than 24 hours, as some events are + * known to happen once every 24 hours. + */ +function _lock_file_gc() { + $files = file_scan_directory(_lock_file_get_path(), '.'); + foreach ($files as $file) { + if ($stat = stat($file->filename)) { + // compare last modify time for lock against current time + if ($stat[9] < (time() - variable_get('lock_file_gc', 88400))) { + // lock file hasn't been used recently, delete it + file_delete($file->filename); + } + } + } +} + +?> --- includes/bootstrap.inc.orig 2005-04-28 23:22:50.000000000 -0500 +++ includes/bootstrap.inc 2005-04-28 23:11:35.000000000 -0500 @@ -171,9 +171,12 @@ function variable_get($name, $default) { function variable_set($name, $value) { global $conf; - db_query("DELETE FROM {variable} WHERE name = '%s'", $name); - db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, serialize($value)); - cache_clear_all('variables'); + if ($key = lock_rwlock($name, LOCK_NO_BLOCK)) { + db_query("DELETE FROM {variable} WHERE name = '%s'", $name); + db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, serialize($value)); + cache_clear_all('variables'); + lock_unlock($name, $key); + } $conf[$name] = $value; } @@ -255,9 +258,12 @@ function cache_get($key) { function cache_set($cid, $data, $expire = CACHE_PERMANENT, $headers = NULL) { $data = db_encode_blob($data); - db_query("UPDATE {cache} SET data = '%s', created = %d, expire = %d, headers = '%s' WHERE cid = '%s'", $data, time(), $expire, $headers, $cid); - if (!db_affected_rows()) { - @db_query("INSERT INTO {cache} (cid, data, created, expire, headers) VALUES ('%s', '%s', %d, %d, '%s')", $cid, $data, time(), $expire, $headers); + if ($key = lock_rwlock($cid, LOCK_NO_BLOCK)) { + db_query("UPDATE {cache} SET data = '%s', created = %d, expire = %d, headers = '%s' WHERE cid = '%s'", $data, time(), $expire, $headers, $cid); + if (!db_affected_rows()) { + @db_query("INSERT INTO {cache} (cid, data, created, expire, headers) VALUES ('%s', '%s', %d, %d, '%s')", $cid, $data, time(), $expire, $headers); + } + lock_unlock($cid, $key); } } @@ -695,6 +701,8 @@ function drupal_get_messages() { include_once "$config/settings.php"; include_once 'includes/database.inc'; +include_once 'includes/file.inc'; +include_once 'includes/lock.inc'; include_once 'includes/session.inc'; include_once 'includes/module.inc'; --- includes/common.inc.orig 2005-04-28 23:58:36.000000000 -0500 +++ includes/common.inc 2005-04-28 23:58:44.000000000 -0500 @@ -1878,7 +1878,6 @@ function drupal_get_path($type, $name) { include_once 'includes/pager.inc'; include_once 'includes/menu.inc'; include_once 'includes/tablesort.inc'; -include_once 'includes/file.inc'; include_once 'includes/xmlrpc.inc'; include_once 'includes/image.inc'; --- modules/system.module.orig 2005-04-28 20:21:27.000000000 -0500 +++ modules/system.module 2005-04-28 23:20:13.000000000 -0500 @@ -173,6 +173,20 @@ function system_user($type, $edit, &$use } } +/** + * Implementation of hook_cron(). + * + * Perform file locking garbage collection. + */ +function system_cron() { + if (variable_get('lock', LOCK_DISABLED) == LOCK_ENABLED) { + // if enabled, clean up old lock files a little less than once per day + if (variable_get('lock_file_gc', 0) < (time() - variable_get('lock_file_gc', 88400))) { + _lock_file_gc(); + } + } +} + function _system_zonelist() { $timestamp = time(); $zonelist = array(-11, -10, -9.5, -9, -8, -7, -6, -5, -4, -3.5, -3, -2, -1, 0, 1, 2, 3, 3.5, 4, 5, 5.5, 5.75, 6, 6.5, 7, 8, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.75, 13, 14); @@ -232,6 +246,14 @@ function system_view_general() { $group .= form_radios(t('Download method'), 'file_downloads', variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC), array(FILE_DOWNLOADS_PUBLIC => t('Public - files are available using http directly.'), FILE_DOWNLOADS_PRIVATE => t('Private - files are transferred by Drupal.')), t('If you want any sort of access control on the downloading of files, this needs to be set to private. You can change this at any time, however all download URLs will change and there may be unexpected problems so it is not recommended.')); $output .= form_group(t('File system settings'), $group); + // Locking: + $lock_path = "$directory_path/". variable_get('lock_path', 'locks'); + if (variable_get('lock', LOCK_DISABLED) == LOCK_ENABLED) { + file_check_directory($lock_path, FILE_CREATE_DIRECTORY, 'locks'); + } + $group = form_radios(t('Locking'), 'lock', variable_get('lock', LOCK_DISABLED), array(LOCK_DISABLED => t('Disabled'), LOCK_ENABLED => t('Enabled')), t("On busy websites, users may begin to see errors during page creation. Most errors just show up in the database, but some errors can happen early enough that they make it to the screen. These errors come for race conditions, when multiple users trigger the same logic at the same time. To prevent these errors from happening, you can enable locking. For locking to work, your file system path has to be properly configured. Drupal uses PHP's built in flock() function to provide locking, which will not work on NFS or FAT filesystems.")); + $output .= form_group(t('Lock settings'), $group); + // Image handling: $group = ''; $toolkits_available = image_get_available_toolkits(); --- modules/user.module.orig 2005-04-28 23:12:00.000000000 -0500 +++ modules/user.module 2005-04-28 23:13:30.000000000 -0500 @@ -767,9 +767,12 @@ function user_set_authmaps($account, $au foreach ($authmaps as $key => $value) { $module = explode('_', $key, 2); if ($value) { - db_query("UPDATE {authmap} SET authname = '%s' WHERE uid = %d AND module = '%s'", $value, $account->uid, $module['1']); - if (!db_affected_rows()) { - db_query("INSERT INTO {authmap} (authname, uid, module) VALUES ('%s', %d, '%s')", $value, $account->uid, $module[1]); + if ($key = lock_rwlock('auth'. $account->uid, LOCK_NO_BLOCK)) { + db_query("UPDATE {authmap} SET authname = '%s' WHERE uid = %d AND module = '%s'", $value, $account->uid, $module['1']); + if (!db_affected_rows()) { + db_query("INSERT INTO {authmap} (authname, uid, module) VALUES ('%s', %d, '%s')", $value, $account->uid, $module[1]); + } + lock_unlock('auth'. $account->uid, $key); } } else { --- modules/statistics.module.orig 2005-04-28 23:11:58.000000000 -0500 +++ modules/statistics.module 2005-04-28 23:14:54.000000000 -0500 @@ -61,12 +61,15 @@ function statistics_exit() { if (variable_get('statistics_count_content_views', 0)) { // We are counting content views. if ((arg(0) == 'node') && arg(1)) { - // A node has been viewed, so update the node's counters. - db_query('UPDATE {node_counter} SET daycount = daycount + 1, totalcount = totalcount + 1, timestamp = %d WHERE nid = %d', time(), arg(1)); - // If we affected 0 rows, this is the first time viewing the node. - if (!db_affected_rows()) { - // We must create a new row to store counters for the new node. - db_query('INSERT INTO {node_counter} (nid, daycount, totalcount, timestamp) VALUES(%d, 1, 1, %d)', arg(1), time()); + if ($key = lock_rwlock('node'. arg(1), LOCK_BLOCK)) { + // A node has been viewed, so update the node's counters. + db_query('UPDATE {node_counter} SET daycount = daycount + 1, totalcount = totalcount + 1, timestamp = %d WHERE nid = %d', time(), arg(1)); + // If we affected 0 rows, this is the first time viewing the node. + if (!db_affected_rows()) { + // We must create a new row to store counters for the new node. + db_query('INSERT INTO {node_counter} (nid, daycount, totalcount, timestamp) VALUES(%d, 1, 1, %d)', arg(1), time()); + } + lock_unlock('node'. arg(1), $key); } } }