diff --git a/src/PocketMine.php b/src/PocketMine.php index 5b36d2a15..c48c52498 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine { use pocketmine\thread\ThreadManager; + use pocketmine\utils\Filesystem; use pocketmine\utils\MainLogger; use pocketmine\utils\Process; use pocketmine\utils\ServerKiller; @@ -207,20 +208,12 @@ namespace pocketmine { mkdir($dataPath, 0777, true); } - define('pocketmine\LOCK_FILE', fopen($dataPath . 'server.lock', "a+b")); - if(!flock(\pocketmine\LOCK_FILE, LOCK_EX | LOCK_NB)){ - //wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the - //other server wrote its PID and released exclusive lock before we get our lock - flock(\pocketmine\LOCK_FILE, LOCK_SH); - $pid = stream_get_contents(\pocketmine\LOCK_FILE); + $lockFilePath = $dataPath . '/server.lock'; + if(($pid = Filesystem::createLockFile($lockFilePath)) !== null){ critical_error("Another " . \pocketmine\NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ")."); critical_error("Please stop the other server first before running a new one."); exit(1); } - ftruncate(\pocketmine\LOCK_FILE, 0); - fwrite(\pocketmine\LOCK_FILE, (string) getmypid()); - fflush(\pocketmine\LOCK_FILE); - flock(\pocketmine\LOCK_FILE, LOCK_SH); //prevent acquiring an exclusive lock from another process, but allow reading //Logger has a dependency on timezone Timezone::init(); diff --git a/src/utils/Filesystem.php b/src/utils/Filesystem.php index 4ccf01a30..27a222431 100644 --- a/src/utils/Filesystem.php +++ b/src/utils/Filesystem.php @@ -23,18 +23,33 @@ declare(strict_types=1); namespace pocketmine\utils; +use function fclose; +use function fflush; +use function flock; +use function fopen; +use function ftruncate; +use function fwrite; +use function getmypid; use function is_dir; use function is_file; use function ltrim; +use function preg_match; +use function realpath; use function rmdir; use function rtrim; use function scandir; use function str_replace; +use function stream_get_contents; use function strpos; use function unlink; +use const LOCK_EX; +use const LOCK_NB; +use const LOCK_SH; use const SCANDIR_SORT_NONE; final class Filesystem{ + /** @var resource[] */ + private static $lockFileHandles = []; private function __construct(){ //NOOP @@ -75,4 +90,39 @@ final class Filesystem{ } return $result; } + + public static function createLockFile(string $lockFilePath) : ?int{ + $resource = fopen($lockFilePath, "a+b"); + if($resource === false){ + throw new \InvalidArgumentException("Invalid lock file path"); + } + if(!flock($resource, LOCK_EX | LOCK_NB)){ + //wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the + //other server wrote its PID and released exclusive lock before we get our lock + flock($resource, LOCK_SH); + $pid = stream_get_contents($resource); + if(preg_match('/^\d+$/', $pid) === 1){ + return (int) $pid; + } + return -1; + } + ftruncate($resource, 0); + fwrite($resource, (string) getmypid()); + fflush($resource); + flock($resource, LOCK_SH); //prevent acquiring an exclusive lock from another process, but allow reading + self::$lockFileHandles[realpath($lockFilePath)] = $resource; //keep the resource alive to preserve the lock + return null; + } + + public static function releaseLockFile(string $lockFilePath) : void{ + $lockFilePath = realpath($lockFilePath); + if($lockFilePath === false){ + throw new \InvalidArgumentException("Invalid lock file path"); + } + if(isset(self::$lockFileHandles[$lockFilePath])){ + fclose(self::$lockFileHandles[$lockFilePath]); + unset(self::$lockFileHandles[$lockFilePath]); + @unlink($lockFilePath); + } + } }