From 270e817954ea491cd7a85cca1d592e66666c1ae9 Mon Sep 17 00:00:00 2001 From: Philipp Holzer Date: Mon, 29 Oct 2018 10:16:07 +0100 Subject: [PATCH] Adding force to update routine - Introduced Cache::NEVER Lock (never expiring lock) - Force flag for dbstructure update - Moving the business logic to central place in Update class --- src/Core/Cache.php | 1 + src/Core/Cache/DatabaseCacheDriver.php | 20 ++-- src/Core/Cache/ICacheDriver.php | 2 +- src/Core/Cache/IMemoryCacheDriver.php | 2 +- src/Core/Console/DatabaseStructure.php | 35 +------ src/Core/Lock.php | 5 +- src/Core/Update.php | 126 +++++++++++++++++++++++-- src/Database/DBStructure.php | 52 ---------- update.php | 2 +- 9 files changed, 141 insertions(+), 104 deletions(-) diff --git a/src/Core/Cache.php b/src/Core/Cache.php index 0fb328aaee..0579ee0c8d 100644 --- a/src/Core/Cache.php +++ b/src/Core/Cache.php @@ -19,6 +19,7 @@ class Cache extends \Friendica\BaseObject const QUARTER_HOUR = 900; const FIVE_MINUTES = 300; const MINUTE = 60; + const NEVER = 0; /** * @var Cache\ICacheDriver diff --git a/src/Core/Cache/DatabaseCacheDriver.php b/src/Core/Cache/DatabaseCacheDriver.php index d90c6e4f18..f6f5b6486c 100644 --- a/src/Core/Cache/DatabaseCacheDriver.php +++ b/src/Core/Cache/DatabaseCacheDriver.php @@ -40,7 +40,7 @@ class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver */ public function get($key) { - $cache = DBA::selectFirst('cache', ['v'], ['`k` = ? AND `expires` >= ?', $key, DateTimeFormat::utcNow()]); + $cache = DBA::selectFirst('cache', ['v'], ['`k` = ? AND (`expires` >= ? OR `expires` = -1)', $key, DateTimeFormat::utcNow()]); if (DBA::isResult($cache)) { $cached = $cache['v']; @@ -62,11 +62,19 @@ class DatabaseCacheDriver extends AbstractCacheDriver implements ICacheDriver */ public function set($key, $value, $ttl = Cache::FIVE_MINUTES) { - $fields = [ - 'v' => serialize($value), - 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds'), - 'updated' => DateTimeFormat::utcNow() - ]; + if ($ttl > 0) { + $fields = [ + 'v' => serialize($value), + 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds'), + 'updated' => DateTimeFormat::utcNow() + ]; + } else { + $fields = [ + 'v' => serialize($value), + 'expires' => -1, + 'updated' => DateTimeFormat::utcNow() + ]; + } return DBA::update('cache', $fields, ['k' => $key], true); } diff --git a/src/Core/Cache/ICacheDriver.php b/src/Core/Cache/ICacheDriver.php index 2c04c59925..1188e51877 100644 --- a/src/Core/Cache/ICacheDriver.php +++ b/src/Core/Cache/ICacheDriver.php @@ -34,7 +34,7 @@ interface ICacheDriver * * @param string $key The cache key * @param mixed $value The value to store - * @param integer $ttl The cache lifespan, must be one of the Cache constants + * @param integer $ttl The cache lifespan, must be one of the Cache constants * * @return bool */ diff --git a/src/Core/Cache/IMemoryCacheDriver.php b/src/Core/Cache/IMemoryCacheDriver.php index a50e2d1d48..0c5146f439 100644 --- a/src/Core/Cache/IMemoryCacheDriver.php +++ b/src/Core/Cache/IMemoryCacheDriver.php @@ -28,7 +28,7 @@ interface IMemoryCacheDriver extends ICacheDriver * @param string $key The cache key * @param mixed $oldValue The old value we know from the cache * @param mixed $newValue The new value we want to set - * @param int $ttl The cache lifespan, must be one of the Cache constants + * @param int $ttl The cache lifespan, must be one of the Cache constants * * @return bool */ diff --git a/src/Core/Console/DatabaseStructure.php b/src/Core/Console/DatabaseStructure.php index 7b4c4c6cf1..f3badc1969 100644 --- a/src/Core/Console/DatabaseStructure.php +++ b/src/Core/Console/DatabaseStructure.php @@ -25,7 +25,7 @@ class DatabaseStructure extends \Asika\SimpleConsole\Console $help = << [-h|--help|-?] [-v] + bin/console dbstructure [-h|--help|-?] |-f|--force] [-v] Commands dryrun Show database update schema queries without running them @@ -36,14 +36,13 @@ Commands Options -h|--help|-? Show help information -v Show more debug information. + -f|--force Force the command in case of "update" (Ignore failed updates/running updates) HELP; return $help; } protected function doExecute() { - $a = get_app(); - if ($this->getOption('v')) { $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); @@ -70,34 +69,8 @@ HELP; $output = DBStructure::update(true, false); break; case "update": - $build = Core\Config::get('system', 'build'); - if (empty($build)) { - Core\Config::set('system', 'build', DB_UPDATE_VERSION); - $build = DB_UPDATE_VERSION; - } - - $stored = intval($build); - $current = intval(DB_UPDATE_VERSION); - - // run the pre_update_nnnn functions in update.php - for ($x = $stored; $x < $current; $x ++) { - $r = Update::runUpdateFunction($x, 'pre_update'); - if (!$r) { - break; - } - } - - $output = DBStructure::update(true, true); - - // run the update_nnnn functions in update.php - for ($x = $stored; $x < $current; $x ++) { - $r = Update::runUpdateFunction($x, 'update'); - if (!$r) { - break; - } - } - - Core\Config::set('system', 'build', DB_UPDATE_VERSION); + $force = $this->getOption(['f', 'force'], false); + $output = Update::run($force, true, false); break; case "dumpsql": ob_start(); diff --git a/src/Core/Lock.php b/src/Core/Lock.php index 4a737e3814..a5467c6442 100644 --- a/src/Core/Lock.php +++ b/src/Core/Lock.php @@ -111,12 +111,13 @@ class Lock * * @param string $key Name of the lock * @param integer $timeout Seconds until we give up + * @param integer $ttl The Lock lifespan, must be one of the Cache constants * * @return boolean Was the lock successful? */ - public static function acquire($key, $timeout = 120) + public static function acquire($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES) { - return self::getDriver()->acquireLock($key, $timeout); + return self::getDriver()->acquireLock($key, $timeout, $ttl); } /** diff --git a/src/Core/Update.php b/src/Core/Update.php index e749c7233a..4dc727b155 100644 --- a/src/Core/Update.php +++ b/src/Core/Update.php @@ -2,6 +2,7 @@ namespace Friendica\Core; +use Friendica\Database\DBA; use Friendica\Database\DBStructure; class Update @@ -38,9 +39,21 @@ class Update /** * Automatic database updates + * + * @param bool $force Force the Update-Check even if the lock is set + * @param bool $verbose Run the Update-Check verbose + * @param bool $sendMail Sends a Mail to the administrator in case of success/failure + * + * @return string Empty string if the update is successful, error messages otherwise */ - public static function run() + public static function run($force = false, $verbose = false, $sendMail = true) { + // In force mode, we release the dbupdate lock first + // Necessary in case of an stuck update + if ($force) { + Lock::release('dbupdate'); + } + $build = Config::get('system', 'build'); if (empty($build) || ($build > DB_UPDATE_VERSION)) { @@ -57,7 +70,8 @@ class Update Config::load('database'); // Compare the current structure with the defined structure - if (Lock::acquire('dbupdate')) { + // If the Lock is acquired, never release it automatically to avoid double updates + if (Lock::acquire('dbupdate', 120, Cache::NEVER)) { // run the pre_update_nnnn functions in update.php for ($x = $stored + 1; $x <= $current; $x++) { @@ -68,14 +82,17 @@ class Update } // update the structure in one call - $retval = DBStructure::update(false, true); + $retval = DBStructure::update($verbose, true); if ($retval) { - DBStructure::updateFail( - DB_UPDATE_VERSION, - $retval - ); + if ($sendMail) { + self::updateFailed( + DB_UPDATE_VERSION, + $retval + ); + } + Lock::release('dbcheck'); Lock::release('dbupdate'); - return; + return $retval; } else { Config::set('database', 'last_successful_update', $current); Config::set('database', 'last_successful_update_time', time()); @@ -89,10 +106,16 @@ class Update } } + if ($sendMail) { + self::updateSuccessfull($stored, $current); + } + Lock::release('dbupdate'); } } } + + return ''; } /** @@ -115,14 +138,14 @@ class Update // If the update fails or times-out completely you may need to // delete the config entry to try again. - if (Lock::acquire('dbupdate_function')) { + if (Lock::acquire('dbupdate_function', 120,Cache::NEVER)) { // call the specific update $retval = $funcname(); if ($retval) { //send the administrator an e-mail - DBStructure::updateFail( + self::updateFailed( $x, L10n::t('Update %s failed. See error logs.', $x) ); @@ -153,4 +176,87 @@ class Update return true; } } + + /** + * send the email and do what is needed to do on update fails + * + * @param int $update_id number of failed update + * @param string $error_message error message + */ + private static function updateFailed($update_id, $error_message) { + //send the administrators an e-mail + $admin_mail_list = "'".implode("','", array_map(['Friendica\Database\DBA', 'escape'], explode(",", str_replace(" ", "", Config::get('config', 'admin_email')))))."'"; + $adminlist = q("SELECT uid, language, email FROM user WHERE email IN (%s)", + $admin_mail_list + ); + + // No valid result? + if (!DBA::isResult($adminlist)) { + logger(sprintf('Cannot notify administrators about update_id=%d, error_message=%s', $update_id, $error_message), LOGGER_INFO); + + // Don't continue + return; + } + + // every admin could had different language + foreach ($adminlist as $admin) { + $lang = (($admin['language'])?$admin['language']:'en'); + L10n::pushLang($lang); + + $preamble = deindent(L10n::t(" + The friendica developers released update %s recently, + but when I tried to install it, something went terribly wrong. + This needs to be fixed soon and I can't do it alone. Please contact a + friendica developer if you can not help me on your own. My database might be invalid.", + $update_id)); + $body = L10n::t("The error message is\n[pre]%s[/pre]", $error_message); + + notification([ + 'uid' => $admin['uid'], + 'type' => SYSTEM_EMAIL, + 'to_email' => $admin['email'], + 'preamble' => $preamble, + 'body' => $body, + 'language' => $lang] + ); + L10n::popLang(); + } + + //try the logger + logger("CRITICAL: Database structure update failed: ".$error_message); + } + + private static function updateSuccessfull($from_build, $to_build) + { + //send the administrators an e-mail + $admin_mail_list = "'".implode("','", array_map(['Friendica\Database\DBA', 'escape'], explode(",", str_replace(" ", "", Config::get('config', 'admin_email')))))."'"; + $adminlist = q("SELECT uid, language, email FROM user WHERE email IN (%s)", + $admin_mail_list + ); + + if (DBA::isResult($adminlist)) { + // every admin could had different language + foreach ($adminlist as $admin) { + $lang = (($admin['language']) ? $admin['language'] : 'en'); + L10n::pushLang($lang); + + $preamble = deindent(L10n::t(" + The friendica database was successfully update from %s to %s.", + $from_build, $to_build)); + + notification([ + 'uid' => $admin['uid'], + 'type' => SYSTEM_EMAIL, + 'to_email' => $admin['email'], + 'preamble' => $preamble, + 'body' => $preamble, + 'language' => $lang] + ); + L10n::popLang(); + } + } + + //try the logger + logger("CRITICAL: Database structure update successful.", LOGGER_TRACE); + } } diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index b24c513336..4a9bc69f61 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -57,58 +57,6 @@ class DBStructure } } - /* - * send the email and do what is needed to do on update fails - * - * @param update_id (int) number of failed update - * @param error_message (str) error message - */ - public static function updateFail($update_id, $error_message) { - $a = get_app(); - - //send the administrators an e-mail - $admin_mail_list = "'".implode("','", array_map(['Friendica\Database\DBA', 'escape'], explode(",", str_replace(" ", "", Config::get('config', 'admin_email')))))."'"; - $adminlist = q("SELECT uid, language, email FROM user WHERE email IN (%s)", - $admin_mail_list - ); - - // No valid result? - if (!DBA::isResult($adminlist)) { - Logger::log(sprintf('Cannot notify administrators about update_id=%d, error_message=%s', $update_id, $error_message), Logger::INFO); - - // Don't continue - return; - } - - // every admin could had different language - foreach ($adminlist as $admin) { - $lang = (($admin['language'])?$admin['language']:'en'); - L10n::pushLang($lang); - - $preamble = deindent(L10n::t(" - The friendica developers released update %s recently, - but when I tried to install it, something went terribly wrong. - This needs to be fixed soon and I can't do it alone. Please contact a - friendica developer if you can not help me on your own. My database might be invalid.", - $update_id)); - $body = L10n::t("The error message is\n[pre]%s[/pre]", $error_message); - - notification([ - 'uid' => $admin['uid'], - 'type' => SYSTEM_EMAIL, - 'to_email' => $admin['email'], - 'preamble' => $preamble, - 'body' => $body, - 'language' => $lang] - ); - L10n::popLang(); - } - - //try the logger - Logger::log("CRITICAL: Database structure update failed: ".$error_message); - } - - private static function tableStructure($table) { $structures = q("DESCRIBE `%s`", $table); diff --git a/update.php b/update.php index 439dd04bd5..115f817d79 100644 --- a/update.php +++ b/update.php @@ -254,5 +254,5 @@ function update_1288() { DBA::e("UPDATE `item-activity` INNER JOIN `item` ON `item`.`iaid` = `item-activity`.`id` SET `item-activity`.`uri-id` = `item`.`uri-id` WHERE `item-activity`.`uri-id` IS NULL OR `item-activity`.`uri-id` = 0"); DBA::e("UPDATE `item-content` INNER JOIN `item` ON `item`.`icid` = `item-content`.`id` SET `item-content`.`uri-id` = `item`.`uri-id` WHERE `item-content`.`uri-id` IS NULL OR `item-content`.`uri-id` = 0"); - return UPDATE_SUCCESS; + return Update::SUCCESS; }