Serialization and database improvements

This commit is contained in:
Daniil Gentili 2020-09-22 17:30:07 +02:00
parent 0001a45cd2
commit 89da6d5a45
11 changed files with 57 additions and 88 deletions

View File

@ -6,7 +6,8 @@
"homepage": "https://docs.madelineproto.xyz", "homepage": "https://docs.madelineproto.xyz",
"keywords": ["telegram", "mtproto", "protocol", "bytes", "messenger", "client", "PHP", "video", "stickers", "audio", "files", "GB"], "keywords": ["telegram", "mtproto", "protocol", "bytes", "messenger", "client", "PHP", "video", "stickers", "audio", "files", "GB"],
"conflict": { "conflict": {
"krakjoe/pthreads-polyfill": "*" "krakjoe/pthreads-polyfill": "*",
"ext-pthreads": "*"
}, },
"require": { "require": {
"php": ">=7.4.0", "php": ">=7.4.0",

View File

@ -19,7 +19,6 @@
namespace danog\MadelineProto; namespace danog\MadelineProto;
use Amp\Promise;
use danog\MadelineProto\Settings\Logger as SettingsLogger; use danog\MadelineProto\Settings\Logger as SettingsLogger;
/** /**
@ -31,13 +30,11 @@ class API extends InternalDoc
use \danog\MadelineProto\ApiWrappers\Start; use \danog\MadelineProto\ApiWrappers\Start;
use \danog\MadelineProto\ApiWrappers\Templates; use \danog\MadelineProto\ApiWrappers\Templates;
/** /**
* Session path. * Session paths.
* *
* @internal * @internal
*
* @var string
*/ */
public string $session = ''; public SessionPaths $session;
/** /**
* Instance of MadelineProto. * Instance of MadelineProto.
@ -95,7 +92,7 @@ class API extends InternalDoc
/** /**
* Global session unlock callback. * Global session unlock callback.
* *
* @var callable * @var ?callable
*/ */
private $unlock; private $unlock;
@ -111,10 +108,11 @@ class API extends InternalDoc
public function __magic_construct(string $session, $settings = []): void public function __magic_construct(string $session, $settings = []): void
{ {
$settings = Settings::parseFromLegacy($settings); $settings = Settings::parseFromLegacy($settings);
$this->session = new SessionPaths($session);
$this->wrapper = new APIWrapper($this, $this->exportNamespace()); $this->wrapper = new APIWrapper($this, $this->exportNamespace());
Magic::classExists(); Magic::classExists(true);
$this->setInitPromise($this->__construct_async($session, $settings)); $this->setInitPromise($this->internalInitAPI($settings));
foreach (\get_class_vars(APIFactory::class) as $key => $var) { foreach (\get_class_vars(APIFactory::class) as $key => $var) {
if (\in_array($key, ['namespace', 'API', 'lua', 'async', 'asyncAPIPromise', 'methods'])) { if (\in_array($key, ['namespace', 'API', 'lua', 'async', 'asyncAPIPromise', 'methods'])) {
continue; continue;
@ -127,21 +125,20 @@ class API extends InternalDoc
/** /**
* Async constructor function. * Async constructor function.
* *
* @param string $session Session name
* @param Settings|SettingsEmpty $settings Settings * @param Settings|SettingsEmpty $settings Settings
* *
* @return \Generator * @return \Generator
*/ */
public function __construct_async(string $session, SettingsAbstract $settings): \Generator private function internalInitAPI(SettingsAbstract $settings): \Generator
{ {
Logger::constructorFromSettings($settings instanceof SettingsEmpty Logger::constructorFromSettings($settings instanceof SettingsEmpty
? new SettingsLogger ? new SettingsLogger
: $settings->getLogger()); : $settings->getLogger());
$this->session = $session = Tools::absolute($session);
[$unserialized, $this->unlock] = yield from Serialization::legacyUnserialize($session); [$unserialized, $this->unlock] = yield from Serialization::legacyUnserialize($this->session);
if ($unserialized) { if ($unserialized) {
$unserialized->storage = $unserialized->storage ?? []; $unserialized->storage = $unserialized->storage ?? [];
$unserialized->session = $session; $unserialized->session = $this->session;
APIWrapper::link($this, $unserialized); APIWrapper::link($this, $unserialized);
APIWrapper::link($this->wrapper, $this); APIWrapper::link($this->wrapper, $this);
AbstractAPIFactory::link($this->wrapper->getFactory(), $this); AbstractAPIFactory::link($this->wrapper->getFactory(), $this);
@ -256,7 +253,7 @@ class API extends InternalDoc
* @param API[] $instances Instances of madeline * @param API[] $instances Instances of madeline
* @param string[]|string $eventHandler Event handler(s) * @param string[]|string $eventHandler Event handler(s)
* *
* @return Promise * @return void
*/ */
public static function startAndLoopMulti(array $instances, $eventHandler): void public static function startAndLoopMulti(array $instances, $eventHandler): void
{ {

View File

@ -35,10 +35,8 @@ final class APIWrapper
/** /**
* Session path. * Session path.
*
* @var string
*/ */
public string $session = ''; public SessionPaths $session;
/** /**
* Getting API ID flag. * Getting API ID flag.
@ -169,7 +167,7 @@ final class APIWrapper
*/ */
public function getIpcPath(): string public function getIpcPath(): string
{ {
return (new SessionPaths($this->session))->getIpcPath(); return $this->session->getIpcPath();
} }
/** /**
@ -179,37 +177,18 @@ final class APIWrapper
*/ */
public function serialize(): Promise public function serialize(): Promise
{ {
if (!$this->session) { if ($this->API === null && !$this->gettingApiId) {
Logger::log("Not serializing, no session"); return new Success(false);
return new Success();
}
if ($this->API instanceof FastAPI) {
Logger::log("Not serializing, IPC client");
return new Success();
} }
return Tools::callFork((function (): \Generator { return Tools::callFork((function (): \Generator {
if (isset($this->API->flushSettings) && $this->API->flushSettings) {
$this->API->flushSettings = false;
$this->API->__construct($this->API->settings);
}
if ($this->API === null && !$this->gettingApiId) {
return false;
}
if ($this->API) { if ($this->API) {
yield from $this->API->initAsynchronously(); yield from $this->API->initAsynchronously();
} }
$this->serialized = \time();
$realpaths = new SessionPaths($this->session); $wrote = yield put($this->session->getTempPath(), \serialize($this));
Logger::log('Waiting for exclusive lock of serialization lockfile...'); yield renameAsync($this->session->getTempPath(), $this->session->getSessionPath());
$unlock = yield Tools::flock($realpaths->getLockPath(), LOCK_EX);
Logger::log('Lock acquired, serializing'); Logger::log('Saved session!');
try {
$wrote = yield put($realpaths->getTempPath(), \serialize($this));
yield renameAsync($realpaths->getTempPath(), $realpaths->getSessionPath());
} finally {
$unlock();
}
Logger::log('Done serializing');
return $wrote; return $wrote;
})()); })());
} }

View File

@ -31,7 +31,7 @@ abstract class DriverArray implements DbArray
throw new \RuntimeException('Native isset not support promises. Use isset method'); throw new \RuntimeException('Native isset not support promises. Use isset method');
} }
abstract protected function initConnection($settings): \Generator; abstract public function initConnection($settings): \Generator;
/** /**
* @param self $new * @param self $new

View File

@ -188,7 +188,7 @@ class MysqlArray extends SqlArray
* @param DatabaseMysql $settings * @param DatabaseMysql $settings
* @return \Generator * @return \Generator
*/ */
protected function initConnection($settings): \Generator public function initConnection($settings): \Generator
{ {
if (!isset($this->db)) { if (!isset($this->db)) {
$this->db = yield from Mysql::getConnection($settings); $this->db = yield from Mysql::getConnection($settings);

View File

@ -28,7 +28,7 @@ class PostgresArray extends SqlArray
* @param DatabasePostgres $settings * @param DatabasePostgres $settings
* @return \Generator * @return \Generator
*/ */
protected function initConnection($settings): \Generator public function initConnection($settings): \Generator
{ {
if (!isset($this->db)) { if (!isset($this->db)) {
$this->db = yield from Postgres::getConnection($settings); $this->db = yield from Postgres::getConnection($settings);

View File

@ -49,7 +49,7 @@ class RedisArray extends SqlArray
* @param DatabaseRedis $settings * @param DatabaseRedis $settings
* @return \Generator * @return \Generator
*/ */
protected function initConnection($settings): \Generator public function initConnection($settings): \Generator
{ {
if (!isset($this->db)) { if (!isset($this->db)) {
$this->db = yield from Redis::getConnection($settings); $this->db = yield from Redis::getConnection($settings);

View File

@ -34,12 +34,6 @@ class Magic
* @var array * @var array
*/ */
public static $storage = []; public static $storage = [];
/**
* Whether has threads.
*
* @var boolean
*/
public static $has_thread = false;
/** /**
* Whether this system is bigendian. * Whether this system is bigendian.
* *
@ -292,10 +286,9 @@ class Magic
throw Exception::extension($extension); throw Exception::extension($extension);
} }
} }
self::$has_thread = \class_exists(\Thread::class) && \method_exists(\Thread::class, 'getCurrentThread');
self::$BIG_ENDIAN = \pack('L', 1) === \pack('N', 1); self::$BIG_ENDIAN = \pack('L', 1) === \pack('N', 1);
self::$bigint = PHP_INT_SIZE < 8; self::$bigint = PHP_INT_SIZE < 8;
self::$ipv6 = (bool) \strlen(@\file_get_contents('http://ipv6.google.com', false, \stream_context_create(['http' => ['timeout' => 1]]))) > 0; self::$ipv6 = (bool) \strlen(@\file_get_contents('http://ipv6.google.com/', false, \stream_context_create(['http' => ['timeout' => 1]]))) > 0;
\preg_match('/const V = (\\d+);/', @\file_get_contents('https://raw.githubusercontent.com/danog/MadelineProto/master/src/danog/MadelineProto/MTProto.php'), $matches); \preg_match('/const V = (\\d+);/', @\file_get_contents('https://raw.githubusercontent.com/danog/MadelineProto/master/src/danog/MadelineProto/MTProto.php'), $matches);
if (isset($matches[1]) && \danog\MadelineProto\MTProto::V < (int) $matches[1]) { if (isset($matches[1]) && \danog\MadelineProto\MTProto::V < (int) $matches[1]) {
throw new \danog\MadelineProto\Exception(\hex2bin(\danog\MadelineProto\Lang::$current_lang['v_error']), 0, null, 'MadelineProto', 1); throw new \danog\MadelineProto\Exception(\hex2bin(\danog\MadelineProto\Lang::$current_lang['v_error']), 0, null, 'MadelineProto', 1);

View File

@ -32,16 +32,15 @@ class Serialization
/** /**
* Unserialize legacy session. * Unserialize legacy session.
* *
* @param string $session Session name * @param SessionPaths $session Session name
* *
* @internal * @internal
* *
* @return \Generator * @return \Generator
*/ */
public static function legacyUnserialize(string $session): \Generator public static function legacyUnserialize(SessionPaths $session): \Generator
{ {
$realpaths = new SessionPaths($session); if (yield exists($session->getSessionPath())) {
if (yield exists($realpaths->getSessionPath())) {
Logger::log('Waiting for exclusive session lock...'); Logger::log('Waiting for exclusive session lock...');
$warningId = Loop::delay(1000, static function () use (&$warningId) { $warningId = Loop::delay(1000, static function () use (&$warningId) {
Logger::log("It seems like the session is busy."); Logger::log("It seems like the session is busy.");
@ -54,23 +53,17 @@ class Serialization
Loop::unreference($warningId); Loop::unreference($warningId);
}); });
Loop::unreference($warningId); Loop::unreference($warningId);
$unlockGlobal = yield Tools::flock($realpaths->getSessionLockPath(), LOCK_EX, 1); $unlock = yield Tools::flock($session->getLockPath(), LOCK_EX, 1);
Loop::cancel($warningId); Loop::cancel($warningId);
$tempId = Shutdown::addCallback($unlockGlobal = static function () use ($unlockGlobal) { $tempId = Shutdown::addCallback($unlock = static function () use ($unlock) {
Logger::log("Unlocking exclusive session lock!"); Logger::log("Unlocking exclusive session lock!");
$unlockGlobal(); $unlock();
Logger::log("Unlocked exclusive session lock!"); Logger::log("Unlocked exclusive session lock!");
}); });
Logger::log("Got exclusive session lock!"); Logger::log("Got exclusive session lock!");
Logger::log('Waiting for shared lock of serialization lockfile...'); $tounserialize = yield get($session->getSessionPath());
$unlock = yield Tools::flock($realpaths->getLockPath(), LOCK_SH);
Logger::log('Shared lock acquired, deserializing...');
try {
$tounserialize = yield get($realpaths->getSessionPath());
} finally {
$unlock();
}
Magic::classExists(); Magic::classExists();
try { try {
$unserialized = \unserialize($tounserialize); $unserialized = \unserialize($tounserialize);
@ -133,7 +126,7 @@ class Serialization
throw new Exception(\danog\MadelineProto\Lang::$current_lang['deserialization_error']); throw new Exception(\danog\MadelineProto\Lang::$current_lang['deserialization_error']);
} }
Shutdown::removeCallback($tempId); Shutdown::removeCallback($tempId);
return [$unserialized, $unlockGlobal]; return [$unserialized, $unlock];
} }
} }
} }

View File

@ -28,10 +28,6 @@ class SessionPaths
* Session path. * Session path.
*/ */
private string $sessionPath; private string $sessionPath;
/**
* Global session lock path.
*/
private string $slockPath;
/** /**
* Session lock path. * Session lock path.
*/ */
@ -53,7 +49,6 @@ class SessionPaths
{ {
$session = Tools::absolute($session); $session = Tools::absolute($session);
$this->sessionPath = $session; $this->sessionPath = $session;
$this->slockPath = "$session.slock";
$this->lockPath = "$session.lock"; $this->lockPath = "$session.lock";
$this->ipcPath = "$session.ipc"; $this->ipcPath = "$session.ipc";
$this->tempPath = "$session.temp.session"; $this->tempPath = "$session.temp.session";
@ -78,16 +73,6 @@ class SessionPaths
return $this->sessionPath; return $this->sessionPath;
} }
/**
* Get global session lock path.
*
* @return string
*/
public function getSessionLockPath(): string
{
return $this->slockPath;
}
/** /**
* Get lock path. * Get lock path.
* *

View File

@ -783,6 +783,27 @@ abstract class Tools extends StrTools
{ {
return Magic::$altervista; return Magic::$altervista;
} }
/**
* Checks private property exists in an object.
*
* @param object $obj Object
* @param string $var Attribute name
*
* @psalm-suppress InvalidScope
*
* @return bool
* @access public
*/
public static function hasVar($obj, string $var): bool
{
return \Closure::bind(
function () use ($var) {
return isset($this->{$var});
},
$obj,
\get_class($obj)
)->__invoke();
}
/** /**
* Accesses a private variable from an object. * Accesses a private variable from an object.
* *