From 1cd41cc1596f2bb0d399a0c52ff92e8c3c5f4cca Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 27 Sep 2020 21:51:55 +0200 Subject: [PATCH] Dark magic to allow serializing session to database on shutdown --- src/danog/MadelineProto/API.php | 2 +- src/danog/MadelineProto/MTProto.php | 37 ++++++++++++++++++- src/danog/MadelineProto/Magic.php | 5 ++- .../MadelineProto/Settings/Connection.php | 12 ++++++ src/danog/MadelineProto/SettingsAbstract.php | 2 +- src/danog/MadelineProto/Shutdown.php | 1 + 6 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/danog/MadelineProto/API.php b/src/danog/MadelineProto/API.php index 43dbd8b7..6503d4b0 100644 --- a/src/danog/MadelineProto/API.php +++ b/src/danog/MadelineProto/API.php @@ -255,7 +255,7 @@ class API extends InternalDoc $this->API->destructing = true; $this->API->unreference(); } - if (isset($this->wrapper)) { + if (isset($this->wrapper) && !Magic::$signaled) { $this->logger->logger('Prompting final serialization...'); Tools::wait($this->wrapper->serialize()); $this->logger->logger('Done final serialization!'); diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index a12509ca..8b7f7dd3 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -204,6 +204,20 @@ class MTProto extends AsyncConstruct implements TLCallback 'msg_resend_ans_req', ]; const DEFAULT_GETUPDATES_PARAMS = ['offset' => 0, 'limit' => null, 'timeout' => 100]; + /** + * Array of references to all instances of MTProto. + * + * This seems like a recipe for memory leaks, but this is actually required to allow saving the session on shutdown. + * When using a network I/O-based database+the EvDriver of AMPHP, calling die(); causes premature garbage collection of the event loop. + * This garbage collection happens always, even if a reference to the event handler is already present elsewhere (probably ev dark magic). + * + * Finally, this causes the process to hang on shutdown, since the database driver cannot receive a reply from the server, because the event loop is down. + * + * To avoid this, we store each MTProto instance in here (unreferencing on shutdown in unreference()), and call serialize() on all instances before calling die; in Magic. + * + * @var self[] + */ + public static array $references = []; /** * Instance of wrapper API. * @@ -505,9 +519,23 @@ class MTProto extends AsyncConstruct implements TLCallback return $data; } yield $this->session->offsetSet('data', $data); - var_dump("Saved!"); return $this->session; } + /** + * Serialize all instances. + * + * CALLED ONLY ON SHUTDOWN. + * + * @return void + */ + public static function serializeAll(): void + { + Logger::log('Prompting final serialization (SHUTDOWN)...'); + foreach (self::$references as $instance) { + Tools::wait($instance->wrapper->serialize()); + } + Logger::log('Done final serialization (SHUTDOWN)!'); + } /** * Constructor function. @@ -519,6 +547,7 @@ class MTProto extends AsyncConstruct implements TLCallback */ public function __magic_construct(SettingsAbstract $settings, APIWrapper $wrapper) { + self::$references[\spl_object_hash($this)] = $this; $this->wrapper = $wrapper; $this->setInitPromise($this->__construct_async($settings)); } @@ -1023,6 +1052,8 @@ class MTProto extends AsyncConstruct implements TLCallback */ public function wakeup(SettingsAbstract $settings, APIWrapper $wrapper): \Generator { + // Set reference to itself + self::$references[\spl_object_hash($this)] = $this; // Set API wrapper $this->wrapper = $wrapper; // BC stuff @@ -1132,6 +1163,9 @@ class MTProto extends AsyncConstruct implements TLCallback public function unreference(): void { $this->logger->logger("Will unreference instance"); + if (isset(self::$references[\spl_object_hash($this)])) { + unset(self::$references[\spl_object_hash($this)]); + } $this->stopLoops(); if (isset($this->seqUpdater)) { $this->seqUpdater->signal(true); @@ -1153,7 +1187,6 @@ class MTProto extends AsyncConstruct implements TLCallback $datacenter->disconnect(); } $this->logger->logger("Unreferenced instance"); - } /** * Destructor. diff --git a/src/danog/MadelineProto/Magic.php b/src/danog/MadelineProto/Magic.php index 051a6a58..f28940f4 100644 --- a/src/danog/MadelineProto/Magic.php +++ b/src/danog/MadelineProto/Magic.php @@ -59,7 +59,7 @@ class Magic */ public static $isFork = false; /** - * Whether this is an IPC worker + * Whether this is an IPC worker. */ public static bool $isIpcWorker = false; /** @@ -234,7 +234,7 @@ class Magic // Setup error reporting \set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); \set_exception_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionHandler']); - self::$isIpcWorker = defined(\MADELINE_WORKER_TYPE::class) ? \MADELINE_WORKER_TYPE === 'madeline-ipc' : false; + self::$isIpcWorker = \defined(\MADELINE_WORKER_TYPE::class) ? \MADELINE_WORKER_TYPE === 'madeline-ipc' : false; if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { try { \error_reporting(E_ALL); @@ -412,6 +412,7 @@ class Magic $driver->unreference($key); } } + MTProto::serializeAll(); Loop::stop(); die($code); } diff --git a/src/danog/MadelineProto/Settings/Connection.php b/src/danog/MadelineProto/Settings/Connection.php index 545131f4..5fa87b98 100644 --- a/src/danog/MadelineProto/Settings/Connection.php +++ b/src/danog/MadelineProto/Settings/Connection.php @@ -425,6 +425,18 @@ class Connection extends SettingsAbstract return $this; } + /** + * Set proxies. + * + * @param array $proxies Proxies + * + * @return self + */ + public function setProxy(array $proxies): self + { + $this->proxy = $proxies; + return $this; + } /** * Clear proxies. * diff --git a/src/danog/MadelineProto/SettingsAbstract.php b/src/danog/MadelineProto/SettingsAbstract.php index 27c472db..e0130584 100644 --- a/src/danog/MadelineProto/SettingsAbstract.php +++ b/src/danog/MadelineProto/SettingsAbstract.php @@ -46,7 +46,7 @@ abstract class SettingsAbstract !isset($defaults[$name]) || $other->{$name} !== $defaults[$name] // Isn't equal to the default value ) - && $other->{$name} !== $this->{"get$uc"} + && $other->{$name} !== $this->{$name} ) { $this->{"set$uc"}($other->{$name}); $this->changed = true; diff --git a/src/danog/MadelineProto/Shutdown.php b/src/danog/MadelineProto/Shutdown.php index 16923aeb..ab5b56e0 100644 --- a/src/danog/MadelineProto/Shutdown.php +++ b/src/danog/MadelineProto/Shutdown.php @@ -52,6 +52,7 @@ class Shutdown foreach (self::$callbacks as $callback) { $callback(); } + Magic::shutdown(0); } /** * Add a callback for script shutdown.