📄 Viewing: SynchronizationUtils.php
<?php
class ABJ_404_Solution_SynchronizationUtils {
/** A prefix for keys used for synchronization methods.
* @var string */
const SYNC_KEY_PREFIX = 'SYNC_';
static $usingFileMode = null;
private static $instance = null;
public static function getInstance() {
if (self::$instance == null) {
self::$instance = new ABJ_404_Solution_SynchronizationUtils();
}
return self::$instance;
}
private function getFileModePath() {
return abj404_getUploadsDir() . 'sync_mode_file.txt';
}
private function getOptionsModePath() {
return abj404_getUploadsDir() . 'sync_mode_options.txt';
}
private function isFileMode() {
if (self::$usingFileMode == null) {
$fileModePath = $this->getFileModePath();
$optionsModePath = $this->getOptionsModePath();
if (file_exists($fileModePath) && file_exists($optionsModePath)) {
$fileUtils = ABJ_404_Solution_Functions::getInstance();
$fileUtils->safeUnlink($fileModePath);
$fileUtils->safeUnlink($optionsModePath);
}
if (file_exists($fileModePath)) {
$usingFileMode = true;
} else if (file_exists($optionsModePath)) {
$usingFileMode = false;
} else {
// initialize
$pass = true;
$keyForTesting = ABJ404_PP . "_" . self::SYNC_KEY_PREFIX . 'testing';
$uniqueID = $this->createUniqueID('testing');
// test saving.
update_option($keyForTesting, $uniqueID);
$result = get_option($keyForTesting);
if ($result != $uniqueID) {
$pass = false;
}
// test deleting.
delete_option($keyForTesting);
$result = get_option($keyForTesting);
if ($result != null && $result != '') {
$pass = false;
}
$f = ABJ_404_Solution_Functions::getInstance();
$f->createDirectoryWithErrorMessages(dirname($optionsModePath));
if ($pass) {
$usingFileMode = false;
touch($optionsModePath);
} else {
$usingFileMode = true;
touch($fileModePath);
}
}
self::$usingFileMode = $usingFileMode;
}
return self::$usingFileMode;
}
function switchToFileSyncMode() {
$f = ABJ_404_Solution_Functions::getInstance();
$fileUtils = ABJ_404_Solution_Functions::getInstance();
self::$usingFileMode = true;
$optionsModePath = $this->getOptionsModePath();
$fileUtils->safeUnlink($optionsModePath);
$fileModePath = $this->getFileModePath();
$f->createDirectoryWithErrorMessages(dirname($fileModePath));
touch($fileModePath);
}
private function createInternalKey($keyFromUser) {
return ABJ404_PP . "_" . self::SYNC_KEY_PREFIX . $keyFromUser;
}
private function createUniqueID($keyFromUser) {
return microtime(true) . "_" . $keyFromUser . '_' . $this->uniqidReal() . uniqid('', true);
}
/** Returns an empty string if the lock is not acquired.
* @param string $synchronizedKeyFromUser
* @return string the unique ID that was used. This is needed to release the lock. Or an empty string if
* the lock wasn't acquired.
*/
function synchronizerAcquireLockTry($synchronizedKeyFromUser) {
$uniqueID = $this->createUniqueID($synchronizedKeyFromUser);
$internalSynchronizedKey = $this->createInternalKey($synchronizedKeyFromUser);
// don't let anyone hold the lock for too long.
$this->fixAnUnforeseenIssue($synchronizedKeyFromUser);
// acquire the lock.
$currentOwner = $this->readOwner($internalSynchronizedKey);
// only write the value if it's empty.
if (empty($currentOwner)) {
$this->writeOwner($internalSynchronizedKey, $uniqueID);
}
// give a different thread that ran at the same time a chance to overwrite our value.
time_nanosleep(0, 10000000 * 30); // 10000000 is 1/100 of a second.
// check and see if we're the owner yet.
$currentOwner = $this->readOwner($internalSynchronizedKey);
if ($currentOwner == $uniqueID) {
return $uniqueID;
}
return '';
}
/** Remove the lock if it's been in place for too long.
* @param string $synchronizedKeyFromUser
*/
function fixAnUnforeseenIssue($synchronizedKeyFromUser) {
$internalSynchronizedKey = $this->createInternalKey($synchronizedKeyFromUser);
$uniqueID = $this->readOwner($internalSynchronizedKey);
if (empty($uniqueID)) {
return;
}
$uniqueIDInfo = explode("_", $uniqueID);
$createTime = $uniqueIDInfo[0];
$timePassed = microtime(true) - (float)$createTime;
$maxExecutionTime = ini_get('max_execution_time');
if (empty($maxExecutionTime) || $maxExecutionTime < 1) {
$maxExecutionTime = 60;
} else {
$maxExecutionTime *= 2;
}
// it should have been released by now.
if ($timePassed > $maxExecutionTime) {
$this->deleteOwner($uniqueID, $internalSynchronizedKey);
$valueAfterDelete = $this->readOwner($internalSynchronizedKey);
// if options mode failed for some reason then switch to file sync mode.
if ($valueAfterDelete != null && $valueAfterDelete != '' &&
!$this->isFileMode()) {
$this->switchToFileSyncMode();
return;
}
$uniqueIDForDebugging = $this->createUniqueID('DEBUG_KEY');
$logger = ABJ_404_Solution_Logging::getInstance();
$logger->errorMessage("Forcibly removed synchronization after " .
$timePassed . " seconds for the " . "key " . $internalSynchronizedKey .
" with value: " . $uniqueID . ', value after delete: ' . $valueAfterDelete .
", microtime: " . microtime(true) . ", unique ID for debugging: " .
$uniqueIDForDebugging . ", File sync mode: " . json_encode($this->isFileMode()));
}
}
/** Waits until the lock can be acquired and then returns the unique ID.
* @param string $synchronizedKeyFromUser
* @return string the unique ID that was used. This is needed to release the lock.
*/
function synchronizerAcquireLockWithWait($synchronizedKeyFromUser) {
$uniqueID = $this->createUniqueID($synchronizedKeyFromUser);
$internalSynchronizedKey = $this->createInternalKey($synchronizedKeyFromUser);
$this->fixAnUnforeseenIssue($synchronizedKeyFromUser);
$iterations = 0;
// acquire the lock.
$currentOwner = $this->readOwner($internalSynchronizedKey);
while ($currentOwner != $uniqueID) {
// only write the value if it's empty.
if (empty($currentOwner)) {
$this->writeOwner($internalSynchronizedKey, $uniqueID);
}
// give a different thread that ran at the same time a chance to overwrite our value.
time_nanosleep(0, 500000000); // 10000000 is 1/100 of a second. 500000000 is 1/2 of a second.
// check and see if we're the owner yet.
$currentOwner = $this->readOwner($internalSynchronizedKey);
$iterations++;
if ($iterations % 500 == 0) {
$this->fixAnUnforeseenIssue($synchronizedKeyFromUser);
}
}
return $uniqueID;
}
/** Release the lock for a synchronized block. Should be done in a finally block.
* @param string $uniqueID
* @param string $synchronizedKeyFromUser
* @throws Exception
*/
function synchronizerReleaseLock($uniqueID, $synchronizedKeyFromUser) {
$internalSynchronizedKey = $this->createInternalKey($synchronizedKeyFromUser);
$currentLockHolder = $this->readOwner($internalSynchronizedKey);
if ($uniqueID == $currentLockHolder) {
$this->deleteOwner($uniqueID, $internalSynchronizedKey);
} else {
// Fail silently instead of throwing fatal exception.
$logger = ABJ_404_Solution_Logging::getInstance();
$logger->debugMessage("Synchronization lock release mismatch. " .
"Synchronized key: $synchronizedKeyFromUser, current holder: $currentLockHolder, " .
"attempted release by: $uniqueID");
}
}
function readOwner($key) {
$owner = '';
if ($this->isFileMode()) {
$fileSync = ABJ_404_Solution_FileSync::getInstance();
$owner = $fileSync->getOwnerFromFile($key);
} else {
$owner = get_option($key);
}
return $owner;
}
function writeOwner($key, $owner) {
if ($this->isFileMode()) {
$fileSync = ABJ_404_Solution_FileSync::getInstance();
$fileSync->writeOwnerToFile($key, $owner);
} else {
update_option($key, $owner);
}
}
function deleteOwner($owner, $key) {
if ($this->isFileMode()) {
$fileSync = ABJ_404_Solution_FileSync::getInstance();
$fileSync->releaseLock($owner, $key);
} else {
delete_option($key);
}
}
/**
* @return string a random string of characters.
* @throws Exception
*/
function uniqidReal() {
$bytes = null;
if (function_exists("random_bytes")) {
try {
$bytes = random_bytes((int)ceil(13 / 2));
} catch (Exception $e) {
$bytes = null; // don't care.
}
}
if ($bytes == null && function_exists("openssl_random_pseudo_bytes")) {
try {
$bytes = openssl_random_pseudo_bytes((int)ceil(13 / 2));
} catch (Exception $e) {
$bytes = null;
}
if ($bytes === false) {
$bytes = null;
}
}
if ($bytes != null) {
return bin2hex($bytes);
}
return uniqid("", true);
}
}
🌑 DarkStealth — WP Plugin Edition
Directory: /home/httpd/html/matrixmodels.com/public_html/wp-content/plugins/404-solution/includes