1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-11-27 01:34:40 +01:00

Fix garbage collection

This commit is contained in:
Daniil Gentili 2023-01-26 14:33:30 +01:00
parent 7488d9734e
commit 77a5417f20
10 changed files with 194 additions and 344 deletions

View File

@ -21,7 +21,6 @@
use danog\MadelineProto\EventHandler; use danog\MadelineProto\EventHandler;
use danog\MadelineProto\Logger; use danog\MadelineProto\Logger;
use danog\MadelineProto\Settings; use danog\MadelineProto\Settings;
use Revolt\EventLoop;
use function Amp\async; use function Amp\async;
use function Amp\Future\await; use function Amp\Future\await;
@ -54,9 +53,6 @@ class SecretHandler extends EventHandler
{ {
return [self::ADMIN]; return [self::ADMIN];
} }
public function onStart(): void {
EventLoop::delay(2.0, $this->stop(...));
}
/** /**
* Handle updates from users. * Handle updates from users.
* *

View File

@ -115,11 +115,6 @@ final class API extends InternalDoc
*/ */
private bool $oldInstance = false; private bool $oldInstance = false;
/**
* API wrapper (to avoid circular references).
*/
protected ?APIWrapper $wrapper = null;
/** /**
* Unlock callback. * Unlock callback.
* *
@ -147,19 +142,8 @@ final class API extends InternalDoc
Magic::start(light: true); Magic::start(light: true);
$settings = Settings::parseFromLegacy($settings); $settings = Settings::parseFromLegacy($settings);
$this->session = new SessionPaths($session); $this->session = new SessionPaths($session);
$this->wrapper = new APIWrapper( $this->wrapper = new APIWrapper($this->session);
$this->session, $this->exportNamespaces();
$this->exportNamespace()
);
foreach (\get_class_vars(APIFactory::class) as $key => $var) {
if (\in_array($key, ['namespace', 'API', 'asyncAPIPromise', 'methods'])) {
continue;
}
if (!isset($this->{$key})) {
$this->{$key} = $this->exportNamespace($key);
}
}
Logger::constructorFromSettings($settings instanceof Settings Logger::constructorFromSettings($settings instanceof Settings
? $settings->getLogger() ? $settings->getLogger()
@ -185,7 +169,6 @@ final class API extends InternalDoc
$appInfo->setApiHash($app['api_hash']); $appInfo->setApiHash($app['api_hash']);
} }
$this->wrapper->setAPI(new MTProto($settings, $this->wrapper)); $this->wrapper->setAPI(new MTProto($settings, $this->wrapper));
$this->APIFactory();
$this->wrapper->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE); $this->wrapper->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
} }
@ -294,26 +277,20 @@ final class API extends InternalDoc
} elseif ($unserialized instanceof ChannelledSocket) { } elseif ($unserialized instanceof ChannelledSocket) {
// Success, IPC client // Success, IPC client
$this->wrapper->setAPI(new Client($unserialized, $this->session, Logger::$default)); $this->wrapper->setAPI(new Client($unserialized, $this->session, Logger::$default));
$this->APIFactory();
return true; return true;
} elseif ($unserialized) { } elseif ($unserialized) {
// Success, full session // Success, full session
if ($this->wrapper->getAPI()) { $this->wrapper->getAPI()?->unreference();
$this->wrapper->getAPI()->unreference(); $this->wrapper = $unserialized;
$this->wrapper->setAPI(null); $this->wrapper->setSession($this->session);
} $this->exportNamespaces();
$this->wrapper->setWebApiTemplate($unserialized->getWebAPITemplate());
$this->wrapper->setAPI($unserialized->getAPI());
AbstractAPIFactory::link($this->wrapper->getFactory(), $this);
if ($this->wrapper->getAPI()) { if ($this->wrapper->getAPI()) {
unset($unserialized); unset($unserialized);
if ($settings instanceof SettingsIpc) { if ($settings instanceof SettingsIpc) {
$settings = new SettingsEmpty; $settings = new SettingsEmpty;
} }
$this->methods = self::getInternalMethodList($this->wrapper->getAPI(), MTProto::class);
$this->wrapper->getAPI()->wakeup($settings, $this->wrapper); $this->wrapper->getAPI()->wakeup($settings, $this->wrapper);
$this->APIFactory();
$this->wrapper->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE); $this->wrapper->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
return true; return true;
} }
@ -367,22 +344,6 @@ final class API extends InternalDoc
unset(self::$destructors[$id]); unset(self::$destructors[$id]);
}); });
} }
/**
* Init API wrapper.
*/
private function APIFactory(): void
{
if ($this->wrapper->getAPI()?->isInited()) {
if ($this->wrapper->getAPI() instanceof MTProto) {
foreach ($this->wrapper->getAPI()->getMethodNamespaces() as $namespace) {
if (!$this->{$namespace}) {
$this->{$namespace} = $this->exportNamespace($namespace);
}
}
}
$this->methods = self::getInternalMethodList($this->wrapper->getAPI(), MTProto::class);
}
}
/** /**
* Start MadelineProto and the event handler (enables async). * Start MadelineProto and the event handler (enables async).
@ -395,6 +356,7 @@ final class API extends InternalDoc
{ {
$started = false; $started = false;
$errors = []; $errors = [];
$prev = EventLoop::getErrorHandler();
EventLoop::setErrorHandler( EventLoop::setErrorHandler(
function (\Throwable $e) use (&$errors, &$started): void { function (\Throwable $e) use (&$errors, &$started): void {
if ($e instanceof UnhandledFutureError) { if ($e instanceof UnhandledFutureError) {
@ -419,6 +381,7 @@ final class API extends InternalDoc
} }
); );
$this->startAndLoopInternal($eventHandler, $started); $this->startAndLoopInternal($eventHandler, $started);
EventLoop::setErrorHandler($prev);
} }
/** /**
* Start multiple instances of MadelineProto and the event handlers (enables async). * Start multiple instances of MadelineProto and the event handlers (enables async).
@ -436,6 +399,7 @@ final class API extends InternalDoc
$started = \array_fill_keys(\array_keys($instances), false); $started = \array_fill_keys(\array_keys($instances), false);
$instanceOne = \array_values($instances)[0]; $instanceOne = \array_values($instances)[0];
$prev = EventLoop::getErrorHandler();
EventLoop::setErrorHandler( EventLoop::setErrorHandler(
function (\Throwable $e) use ($instanceOne, &$errors, &$started, $eventHandler): void { function (\Throwable $e) use ($instanceOne, &$errors, &$started, $eventHandler): void {
if ($e instanceof UnhandledFutureError) { if ($e instanceof UnhandledFutureError) {
@ -468,6 +432,8 @@ final class API extends InternalDoc
}); });
} }
await($promises); await($promises);
EventLoop::setErrorHandler($prev);
} }
/** /**

View File

@ -32,9 +32,12 @@ final class APIWrapper
*/ */
public function __construct( public function __construct(
private SessionPaths $session, private SessionPaths $session,
private AbstractAPIFactory $factory,
) { ) {
} }
public function setSession(SessionPaths $session): void
{
$this->session = $session;
}
public function getWebApiTemplate(): string public function getWebApiTemplate(): string
{ {
@ -51,6 +54,7 @@ final class APIWrapper
} }
public function setAPI(Client|MTProto|null $API): void public function setAPI(Client|MTProto|null $API): void
{ {
$this->API?->unreference();
$this->API = $API; $this->API = $API;
} }
@ -70,14 +74,6 @@ final class APIWrapper
return $this->API; return $this->API;
} }
/**
* Get API factory.
*/
public function getFactory(): AbstractAPIFactory
{
return $this->factory;
}
/** /**
* Get IPC path. * Get IPC path.
* *

View File

@ -20,7 +20,6 @@ declare(strict_types=1);
namespace danog\MadelineProto; namespace danog\MadelineProto;
use danog\MadelineProto\Ipc\Client;
use InvalidArgumentException; use InvalidArgumentException;
abstract class AbstractAPIFactory abstract class AbstractAPIFactory
@ -31,48 +30,27 @@ abstract class AbstractAPIFactory
* @internal * @internal
*/ */
private string $namespace = ''; private string $namespace = '';
/**
* Method list.
*
* @var array<string, callable>
*/
protected array $methods = [];
/** /**
* Main API instance. * API wrapper (to avoid circular references).
*/ */
private API $mainAPI; protected APIWrapper $wrapper;
/** /**
* Export APIFactory instance with the specified namespace. * Export APIFactory instance with the specified namespace.
*
* @param string $namespace Namespace
*/ */
protected function exportNamespace(string $namespace = ''): self protected function exportNamespaces(): void
{ {
$class = \array_reverse(\array_values(\class_parents(static::class)))[$namespace ? 1 : 2]; $class = \array_reverse(\array_values(\class_parents(static::class)))[1];
$instance = new $class; foreach (\get_class_vars(APIFactory::class) as $key => $var) {
$instance->namespace = $namespace ? $namespace.'.' : ''; if (\in_array($key, ['namespace', 'methods', 'wrapper'])) {
self::link($instance, $this); continue;
}
return $instance; $instance = new $class;
} $instance->namespace = $key.'.';
/** $instance->wrapper = $this->wrapper;
* Link two APIFactory instances. $this->{$key} = $instance;
*
* @param self $a First instance
* @param self $b Second instance
*/
protected static function link(self $a, self $b): void
{
$a->methods =& $b->methods;
if ($b instanceof API) {
$a->mainAPI = $b;
} elseif ($a instanceof API) {
$b->mainAPI = $a;
} else {
$a->mainAPI =& $b->mainAPI;
} }
} }
/** /**
@ -96,21 +74,14 @@ abstract class AbstractAPIFactory
$arguments = [$arguments]; $arguments = [$arguments];
} }
$lower_name = \strtolower($name); $name = $this->namespace.$name;
if ($this->namespace !== '' || !isset($this->methods[$lower_name])) { $aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : [];
$name = $this->namespace.$name; $aargs['apifactory'] = true;
$aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : []; $args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
$aargs['apifactory'] = true; if (isset($args[0]) && !isset($args['multiple'])) {
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : []; throw new InvalidArgumentException('Parameter names must be provided!');
if (isset($args[0]) && !isset($args['multiple'])) {
throw new InvalidArgumentException('Parameter names must be provided!');
}
return $this->mainAPI->wrapper->getAPI()->methodCallAsyncRead($name, $args, $aargs);
} }
if ($lower_name === 'seteventhandler') { return $this->wrapper->getAPI()->methodCallAsyncRead($name, $args, $aargs);
throw new InvalidArgumentException('Cannot call setEventHandler like this, please use MyEventHandler::startAndLoop("session.madeline", $settings);');
}
return $this->methods[$lower_name](...$arguments);
} }
/** /**
* Sleep function. * Sleep function.
@ -119,60 +90,4 @@ abstract class AbstractAPIFactory
{ {
return []; return [];
} }
/**
* Get fully resolved method list for object, including snake_case and camelCase variants.
*
* @param API|MTProto|Client $value Value
* @param string $class Custom class name
*/
protected static function getInternalMethodList(API|MTProto|Client $value, ?string $class = null): array
{
return \array_map(fn ($method) => [$value, $method], self::getInternalMethodListClass($class ?? $value::class));
}
/**
* Get fully resolved method list for object, including snake_case and camelCase variants.
*
* @param string $class Class name
*/
protected static function getInternalMethodListClass(string $class): array
{
static $cache = [];
if (isset($cache[$class])) {
return $cache[$class];
}
$methods = \get_class_methods($class);
foreach ($methods as $method) {
if ($method == 'methodCallAsyncRead') {
unset($methods[\array_search('methodCall', $methods)]);
} elseif (\stripos($method, 'async') !== false) {
if (\strpos($method, '_async') !== false) {
unset($methods[\array_search(\str_ireplace('_async', '', $method), $methods)]);
} else {
unset($methods[\array_search(\str_ireplace('async', '', $method), $methods)]);
}
}
}
$finalMethods = [];
foreach ($methods as $method) {
$actual_method = $method;
if ($method == 'methodCallAsyncRead') {
$method = 'methodCall';
} elseif (\stripos($method, 'async') !== false) {
if (\strpos($method, '_async') !== false) {
$method = \str_ireplace('_async', '', $method);
} else {
$method = \str_ireplace('async', '', $method);
}
}
$finalMethods[\strtolower($method)] = $actual_method;
if (\strpos($method, '_') !== false) {
$finalMethods[\strtolower(\str_replace('_', '', $method))] = $actual_method;
} else {
$finalMethods[\strtolower(StrTools::toSnakeCase($method))] = $actual_method;
}
}
$cache[$class] = $finalMethods;
return $finalMethods;
}
} }

View File

@ -259,10 +259,6 @@ final class AnnotationsBuilder
} }
$type = $method->getReturnType(); $type = $method->getReturnType();
$hasReturnValue = $type !== null; $hasReturnValue = $type !== null;
if (!$hasVariadic && !$static && !$hasReturnValue) {
$paramList .= '$extra, ';
$doc .= 'array $extra = []';
}
$doc = \rtrim($doc, ', '); $doc = \rtrim($doc, ', ');
$paramList = \rtrim($paramList, ', '); $paramList = \rtrim($paramList, ', ');
$doc .= ')'; $doc .= ')';
@ -278,13 +274,12 @@ final class AnnotationsBuilder
if ($method->getDeclaringClass()->getName() == StrTools::class) { if ($method->getDeclaringClass()->getName() == StrTools::class) {
$async = false; $async = false;
} }
$finalParamList = $hasVariadic ? "Tools::arr({$paramList})" : "[{$paramList}]";
$ret = $type && $type instanceof ReflectionNamedType && \in_array($type->getName(), ['self', 'void']) ? '' : 'return'; $ret = $type && $type instanceof ReflectionNamedType && \in_array($type->getName(), ['self', 'void']) ? '' : 'return';
$doc .= "\n{\n"; $doc .= "\n{\n";
if ($async) { if ($async) {
$doc .= " {$ret} \$this->__call(__FUNCTION__, {$finalParamList});\n"; $doc .= " {$ret} \$this->wrapper->getAPI()->{__FUNCTION__}({$paramList});\n";
} elseif (!$static) { } elseif (!$static) {
$doc .= " {$ret} \$this->API->{$name}({$paramList});\n"; $doc .= " {$ret} \$this->wrapper->getAPI()->{$name}({$paramList});\n";
} else { } else {
$doc .= " {$ret} \\".$method->getDeclaringClass()->getName().'::'.$name."({$paramList});\n"; $doc .= " {$ret} \\".$method->getDeclaringClass()->getName().'::'.$name."({$paramList});\n";
} }

View File

@ -84,11 +84,8 @@ abstract class EventHandler extends InternalDoc
*/ */
public function initInternal(APIWrapper $MadelineProto): void public function initInternal(APIWrapper $MadelineProto): void
{ {
self::link($this, $MadelineProto->getFactory()); $this->wrapper = $MadelineProto;
$this->API =& $MadelineProto->getAPI(); $this->exportNamespaces();
foreach ($this->API->getMethodNamespaces() as $namespace) {
$this->{$namespace} = $this->exportNamespace($namespace);
}
} }
private ?Future $startFuture = null; private ?Future $startFuture = null;
/** /**
@ -107,7 +104,7 @@ abstract class EventHandler extends InternalDoc
return; return;
} }
if (isset(static::$dbProperties)) { if (isset(static::$dbProperties)) {
$this->internalInitDb($this->API); $this->internalInitDb($this->wrapper->getAPI());
} }
if (\method_exists($this, 'onStart')) { if (\method_exists($this, 'onStart')) {
$r = $this->onStart(); $r = $this->onStart();
@ -141,12 +138,4 @@ abstract class EventHandler extends InternalDoc
{ {
return []; return [];
} }
/**
* Get API instance.
*/
public function getAPI(): MTProto
{
return $this->API;
}
} }

View File

@ -53,7 +53,7 @@ final class GarbageCollector
} }
self::$started = true; self::$started = true;
EventLoop::repeat(1, static function (): void { EventLoop::unreference(EventLoop::repeat(1, static function (): void {
$currentMemory = self::getMemoryConsumption(); $currentMemory = self::getMemoryConsumption();
if ($currentMemory > self::$memoryConsumption + self::$memoryDiffMb) { if ($currentMemory > self::$memoryConsumption + self::$memoryDiffMb) {
\gc_collect_cycles(); \gc_collect_cycles();
@ -63,7 +63,7 @@ final class GarbageCollector
Logger::log("gc_collect_cycles done. Cleaned memory: $cleanedMemory Mb", Logger::VERBOSE); Logger::log("gc_collect_cycles done. Cleaned memory: $cleanedMemory Mb", Logger::VERBOSE);
} }
} }
}); }));
if (!\defined('MADELINE_RELEASE_URL')) { if (!\defined('MADELINE_RELEASE_URL')) {
return; return;
@ -71,7 +71,9 @@ final class GarbageCollector
$client = HttpClientBuilder::buildDefault(); $client = HttpClientBuilder::buildDefault();
$request = new Request(MADELINE_RELEASE_URL); $request = new Request(MADELINE_RELEASE_URL);
$madelinePhpContents = null; $madelinePhpContents = null;
$cb = function () use ($client, $request, &$madelinePhpContents): bool {
$id = null;
$cb = function () use ($client, $request, &$madelinePhpContents, &$id): void {
try { try {
$madelinePhpContents ??= read(MADELINE_PHP); $madelinePhpContents ??= read(MADELINE_PHP);
$contents = $client->request(new Request("https://phar.madelineproto.xyz/phar.php?v=new".\rand(0, PHP_INT_MAX))) $contents = $client->request(new Request("https://phar.madelineproto.xyz/phar.php?v=new".\rand(0, PHP_INT_MAX)))
@ -94,7 +96,10 @@ final class GarbageCollector
if (Magic::$isIpcWorker) { if (Magic::$isIpcWorker) {
throw new SignalException('!!!!!!!!!!!!! An update of MadelineProto is required, shutting down worker! !!!!!!!!!!!!!'); throw new SignalException('!!!!!!!!!!!!! An update of MadelineProto is required, shutting down worker! !!!!!!!!!!!!!');
} }
return true; if ($id) {
EventLoop::cancel($id);
}
return;
} }
foreach (\glob(MADELINE_PHAR_GLOB) as $path) { foreach (\glob(MADELINE_PHAR_GLOB) as $path) {
@ -114,11 +119,9 @@ final class GarbageCollector
} catch (Throwable $e) { } catch (Throwable $e) {
Logger::log("An error occurred in the phar cleanup loop: $e", Logger::FATAL_ERROR); Logger::log("An error occurred in the phar cleanup loop: $e", Logger::FATAL_ERROR);
} }
return false;
}; };
$cb(); $cb();
self::$cleanupLoop = new PeriodicLoop($cb, 'Phar cleanup loop', 60*1000); EventLoop::unreference($id = EventLoop::repeat(60.0, $cb));
self::$cleanupLoop->start();
} }
/** @var \WeakMap<\Fiber, true> */ /** @var \WeakMap<\Fiber, true> */

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,6 @@ declare(strict_types=1);
namespace danog\MadelineProto\Loop; namespace danog\MadelineProto\Loop;
use danog\MadelineProto\EventHandler;
use danog\MadelineProto\InternalDoc; use danog\MadelineProto\InternalDoc;
/** /**
@ -44,6 +43,6 @@ trait APILoop
public function __construct(InternalDoc $API) public function __construct(InternalDoc $API)
{ {
$this->API = $API; $this->API = $API;
$this->setLogger($API instanceof EventHandler ? $API->getAPI()->getLogger() : $API->getLogger()); $this->setLogger($API->getLogger());
} }
} }

View File

@ -601,7 +601,7 @@ final class MTProto implements TLCallback, LoggerGetter
try { try {
$this->ipcServer = new Server($this); $this->ipcServer = new Server($this);
$this->ipcServer->setSettings($this->settings->getIpc()); $this->ipcServer->setSettings($this->settings->getIpc());
$this->ipcServer->setIpcPath($this->wrapper->session); $this->ipcServer->setIpcPath($this->wrapper->getSession());
} catch (Throwable $e) { } catch (Throwable $e) {
$this->logger->logger("Error while starting IPC server: $e", Logger::FATAL_ERROR); $this->logger->logger("Error while starting IPC server: $e", Logger::FATAL_ERROR);
} }
@ -656,15 +656,6 @@ final class MTProto implements TLCallback, LoggerGetter
$this->settings->applyChanges(); $this->settings->applyChanges();
} }
/**
* Set API wrapper needed for triggering serialization functions.
*
* @internal
*/
public function setWrapper(APIWrapper $wrapper): void
{
$this->wrapper = $wrapper;
}
/** /**
* Get API wrapper. * Get API wrapper.
* *
@ -888,7 +879,7 @@ final class MTProto implements TLCallback, LoggerGetter
try { try {
$this->ipcServer = new Server($this); $this->ipcServer = new Server($this);
$this->ipcServer->setSettings($this->settings->getIpc()); $this->ipcServer->setSettings($this->settings->getIpc());
$this->ipcServer->setIpcPath($this->wrapper->session); $this->ipcServer->setIpcPath($this->wrapper->getSession());
} catch (Throwable $e) { } catch (Throwable $e) {
$this->logger->logger("Error while starting IPC server: $e", Logger::FATAL_ERROR); $this->logger->logger("Error while starting IPC server: $e", Logger::FATAL_ERROR);
} }