Serialization and database improvements

This commit is contained in:
Daniil Gentili 2020-09-22 17:30:07 +02:00
parent 30fdd20ff5
commit 967472f671
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
11 changed files with 57 additions and 88 deletions

View File

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

View File

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

View File

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

View File

@ -31,7 +31,7 @@ abstract class DriverArray implements DbArray
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

View File

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

View File

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

View File

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

View File

@ -34,12 +34,6 @@ class Magic
* @var array
*/
public static $storage = [];
/**
* Whether has threads.
*
* @var boolean
*/
public static $has_thread = false;
/**
* Whether this system is bigendian.
*
@ -292,10 +286,9 @@ class Magic
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::$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);
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);

View File

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

View File

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

View File

@ -783,6 +783,27 @@ abstract class Tools extends StrTools
{
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.
*