Dark magic to allow serializing session to database on shutdown

This commit is contained in:
Daniil Gentili 2020-09-27 21:51:55 +02:00
parent 5560b140f5
commit 1cd41cc159
6 changed files with 53 additions and 6 deletions

View File

@ -255,7 +255,7 @@ class API extends InternalDoc
$this->API->destructing = true; $this->API->destructing = true;
$this->API->unreference(); $this->API->unreference();
} }
if (isset($this->wrapper)) { if (isset($this->wrapper) && !Magic::$signaled) {
$this->logger->logger('Prompting final serialization...'); $this->logger->logger('Prompting final serialization...');
Tools::wait($this->wrapper->serialize()); Tools::wait($this->wrapper->serialize());
$this->logger->logger('Done final serialization!'); $this->logger->logger('Done final serialization!');

View File

@ -204,6 +204,20 @@ class MTProto extends AsyncConstruct implements TLCallback
'msg_resend_ans_req', 'msg_resend_ans_req',
]; ];
const DEFAULT_GETUPDATES_PARAMS = ['offset' => 0, 'limit' => null, 'timeout' => 100]; 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. * Instance of wrapper API.
* *
@ -505,9 +519,23 @@ class MTProto extends AsyncConstruct implements TLCallback
return $data; return $data;
} }
yield $this->session->offsetSet('data', $data); yield $this->session->offsetSet('data', $data);
var_dump("Saved!");
return $this->session; 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. * Constructor function.
@ -519,6 +547,7 @@ class MTProto extends AsyncConstruct implements TLCallback
*/ */
public function __magic_construct(SettingsAbstract $settings, APIWrapper $wrapper) public function __magic_construct(SettingsAbstract $settings, APIWrapper $wrapper)
{ {
self::$references[\spl_object_hash($this)] = $this;
$this->wrapper = $wrapper; $this->wrapper = $wrapper;
$this->setInitPromise($this->__construct_async($settings)); $this->setInitPromise($this->__construct_async($settings));
} }
@ -1023,6 +1052,8 @@ class MTProto extends AsyncConstruct implements TLCallback
*/ */
public function wakeup(SettingsAbstract $settings, APIWrapper $wrapper): \Generator public function wakeup(SettingsAbstract $settings, APIWrapper $wrapper): \Generator
{ {
// Set reference to itself
self::$references[\spl_object_hash($this)] = $this;
// Set API wrapper // Set API wrapper
$this->wrapper = $wrapper; $this->wrapper = $wrapper;
// BC stuff // BC stuff
@ -1132,6 +1163,9 @@ class MTProto extends AsyncConstruct implements TLCallback
public function unreference(): void public function unreference(): void
{ {
$this->logger->logger("Will unreference instance"); $this->logger->logger("Will unreference instance");
if (isset(self::$references[\spl_object_hash($this)])) {
unset(self::$references[\spl_object_hash($this)]);
}
$this->stopLoops(); $this->stopLoops();
if (isset($this->seqUpdater)) { if (isset($this->seqUpdater)) {
$this->seqUpdater->signal(true); $this->seqUpdater->signal(true);
@ -1153,7 +1187,6 @@ class MTProto extends AsyncConstruct implements TLCallback
$datacenter->disconnect(); $datacenter->disconnect();
} }
$this->logger->logger("Unreferenced instance"); $this->logger->logger("Unreferenced instance");
} }
/** /**
* Destructor. * Destructor.

View File

@ -59,7 +59,7 @@ class Magic
*/ */
public static $isFork = false; public static $isFork = false;
/** /**
* Whether this is an IPC worker * Whether this is an IPC worker.
*/ */
public static bool $isIpcWorker = false; public static bool $isIpcWorker = false;
/** /**
@ -234,7 +234,7 @@ class Magic
// Setup error reporting // Setup error reporting
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); \set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
\set_exception_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionHandler']); \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') { if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
try { try {
\error_reporting(E_ALL); \error_reporting(E_ALL);
@ -412,6 +412,7 @@ class Magic
$driver->unreference($key); $driver->unreference($key);
} }
} }
MTProto::serializeAll();
Loop::stop(); Loop::stop();
die($code); die($code);
} }

View File

@ -425,6 +425,18 @@ class Connection extends SettingsAbstract
return $this; return $this;
} }
/**
* Set proxies.
*
* @param array $proxies Proxies
*
* @return self
*/
public function setProxy(array $proxies): self
{
$this->proxy = $proxies;
return $this;
}
/** /**
* Clear proxies. * Clear proxies.
* *

View File

@ -46,7 +46,7 @@ abstract class SettingsAbstract
!isset($defaults[$name]) !isset($defaults[$name])
|| $other->{$name} !== $defaults[$name] // Isn't equal to the default value || $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->{"set$uc"}($other->{$name});
$this->changed = true; $this->changed = true;

View File

@ -52,6 +52,7 @@ class Shutdown
foreach (self::$callbacks as $callback) { foreach (self::$callbacks as $callback) {
$callback(); $callback();
} }
Magic::shutdown(0);
} }
/** /**
* Add a callback for script shutdown. * Add a callback for script shutdown.