1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-11-26 21:14:43 +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\Logger;
use danog\MadelineProto\Settings;
use Revolt\EventLoop;
use function Amp\async;
use function Amp\Future\await;
@ -54,9 +53,6 @@ class SecretHandler extends EventHandler
{
return [self::ADMIN];
}
public function onStart(): void {
EventLoop::delay(2.0, $this->stop(...));
}
/**
* Handle updates from users.
*

View File

@ -115,11 +115,6 @@ final class API extends InternalDoc
*/
private bool $oldInstance = false;
/**
* API wrapper (to avoid circular references).
*/
protected ?APIWrapper $wrapper = null;
/**
* Unlock callback.
*
@ -147,19 +142,8 @@ final class API extends InternalDoc
Magic::start(light: true);
$settings = Settings::parseFromLegacy($settings);
$this->session = new SessionPaths($session);
$this->wrapper = new APIWrapper(
$this->session,
$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);
}
}
$this->wrapper = new APIWrapper($this->session);
$this->exportNamespaces();
Logger::constructorFromSettings($settings instanceof Settings
? $settings->getLogger()
@ -185,7 +169,6 @@ final class API extends InternalDoc
$appInfo->setApiHash($app['api_hash']);
}
$this->wrapper->setAPI(new MTProto($settings, $this->wrapper));
$this->APIFactory();
$this->wrapper->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
}
@ -294,26 +277,20 @@ final class API extends InternalDoc
} elseif ($unserialized instanceof ChannelledSocket) {
// Success, IPC client
$this->wrapper->setAPI(new Client($unserialized, $this->session, Logger::$default));
$this->APIFactory();
return true;
} elseif ($unserialized) {
// Success, full session
if ($this->wrapper->getAPI()) {
$this->wrapper->getAPI()->unreference();
$this->wrapper->setAPI(null);
}
$this->wrapper->setWebApiTemplate($unserialized->getWebAPITemplate());
$this->wrapper->setAPI($unserialized->getAPI());
AbstractAPIFactory::link($this->wrapper->getFactory(), $this);
$this->wrapper->getAPI()?->unreference();
$this->wrapper = $unserialized;
$this->wrapper->setSession($this->session);
$this->exportNamespaces();
if ($this->wrapper->getAPI()) {
unset($unserialized);
if ($settings instanceof SettingsIpc) {
$settings = new SettingsEmpty;
}
$this->methods = self::getInternalMethodList($this->wrapper->getAPI(), MTProto::class);
$this->wrapper->getAPI()->wakeup($settings, $this->wrapper);
$this->APIFactory();
$this->wrapper->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
return true;
}
@ -367,22 +344,6 @@ final class API extends InternalDoc
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).
@ -395,6 +356,7 @@ final class API extends InternalDoc
{
$started = false;
$errors = [];
$prev = EventLoop::getErrorHandler();
EventLoop::setErrorHandler(
function (\Throwable $e) use (&$errors, &$started): void {
if ($e instanceof UnhandledFutureError) {
@ -419,6 +381,7 @@ final class API extends InternalDoc
}
);
$this->startAndLoopInternal($eventHandler, $started);
EventLoop::setErrorHandler($prev);
}
/**
* 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);
$instanceOne = \array_values($instances)[0];
$prev = EventLoop::getErrorHandler();
EventLoop::setErrorHandler(
function (\Throwable $e) use ($instanceOne, &$errors, &$started, $eventHandler): void {
if ($e instanceof UnhandledFutureError) {
@ -468,6 +432,8 @@ final class API extends InternalDoc
});
}
await($promises);
EventLoop::setErrorHandler($prev);
}
/**

View File

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

View File

@ -20,7 +20,6 @@ declare(strict_types=1);
namespace danog\MadelineProto;
use danog\MadelineProto\Ipc\Client;
use InvalidArgumentException;
abstract class AbstractAPIFactory
@ -31,48 +30,27 @@ abstract class AbstractAPIFactory
* @internal
*/
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.
*
* @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;
$instance->namespace = $namespace ? $namespace.'.' : '';
self::link($instance, $this);
return $instance;
}
/**
* Link two APIFactory instances.
*
* @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;
foreach (\get_class_vars(APIFactory::class) as $key => $var) {
if (\in_array($key, ['namespace', 'methods', 'wrapper'])) {
continue;
}
$instance = new $class;
$instance->namespace = $key.'.';
$instance->wrapper = $this->wrapper;
$this->{$key} = $instance;
}
}
/**
@ -96,21 +74,14 @@ abstract class AbstractAPIFactory
$arguments = [$arguments];
}
$lower_name = \strtolower($name);
if ($this->namespace !== '' || !isset($this->methods[$lower_name])) {
$name = $this->namespace.$name;
$aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : [];
$aargs['apifactory'] = true;
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
if (isset($args[0]) && !isset($args['multiple'])) {
throw new InvalidArgumentException('Parameter names must be provided!');
}
return $this->mainAPI->wrapper->getAPI()->methodCallAsyncRead($name, $args, $aargs);
$name = $this->namespace.$name;
$aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : [];
$aargs['apifactory'] = true;
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
if (isset($args[0]) && !isset($args['multiple'])) {
throw new InvalidArgumentException('Parameter names must be provided!');
}
if ($lower_name === 'seteventhandler') {
throw new InvalidArgumentException('Cannot call setEventHandler like this, please use MyEventHandler::startAndLoop("session.madeline", $settings);');
}
return $this->methods[$lower_name](...$arguments);
return $this->wrapper->getAPI()->methodCallAsyncRead($name, $args, $aargs);
}
/**
* Sleep function.
@ -119,60 +90,4 @@ abstract class AbstractAPIFactory
{
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();
$hasReturnValue = $type !== null;
if (!$hasVariadic && !$static && !$hasReturnValue) {
$paramList .= '$extra, ';
$doc .= 'array $extra = []';
}
$doc = \rtrim($doc, ', ');
$paramList = \rtrim($paramList, ', ');
$doc .= ')';
@ -278,13 +274,12 @@ final class AnnotationsBuilder
if ($method->getDeclaringClass()->getName() == StrTools::class) {
$async = false;
}
$finalParamList = $hasVariadic ? "Tools::arr({$paramList})" : "[{$paramList}]";
$ret = $type && $type instanceof ReflectionNamedType && \in_array($type->getName(), ['self', 'void']) ? '' : 'return';
$doc .= "\n{\n";
if ($async) {
$doc .= " {$ret} \$this->__call(__FUNCTION__, {$finalParamList});\n";
$doc .= " {$ret} \$this->wrapper->getAPI()->{__FUNCTION__}({$paramList});\n";
} elseif (!$static) {
$doc .= " {$ret} \$this->API->{$name}({$paramList});\n";
$doc .= " {$ret} \$this->wrapper->getAPI()->{$name}({$paramList});\n";
} else {
$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
{
self::link($this, $MadelineProto->getFactory());
$this->API =& $MadelineProto->getAPI();
foreach ($this->API->getMethodNamespaces() as $namespace) {
$this->{$namespace} = $this->exportNamespace($namespace);
}
$this->wrapper = $MadelineProto;
$this->exportNamespaces();
}
private ?Future $startFuture = null;
/**
@ -107,7 +104,7 @@ abstract class EventHandler extends InternalDoc
return;
}
if (isset(static::$dbProperties)) {
$this->internalInitDb($this->API);
$this->internalInitDb($this->wrapper->getAPI());
}
if (\method_exists($this, 'onStart')) {
$r = $this->onStart();
@ -141,12 +138,4 @@ abstract class EventHandler extends InternalDoc
{
return [];
}
/**
* Get API instance.
*/
public function getAPI(): MTProto
{
return $this->API;
}
}

View File

@ -53,7 +53,7 @@ final class GarbageCollector
}
self::$started = true;
EventLoop::repeat(1, static function (): void {
EventLoop::unreference(EventLoop::repeat(1, static function (): void {
$currentMemory = self::getMemoryConsumption();
if ($currentMemory > self::$memoryConsumption + self::$memoryDiffMb) {
\gc_collect_cycles();
@ -63,7 +63,7 @@ final class GarbageCollector
Logger::log("gc_collect_cycles done. Cleaned memory: $cleanedMemory Mb", Logger::VERBOSE);
}
}
});
}));
if (!\defined('MADELINE_RELEASE_URL')) {
return;
@ -71,7 +71,9 @@ final class GarbageCollector
$client = HttpClientBuilder::buildDefault();
$request = new Request(MADELINE_RELEASE_URL);
$madelinePhpContents = null;
$cb = function () use ($client, $request, &$madelinePhpContents): bool {
$id = null;
$cb = function () use ($client, $request, &$madelinePhpContents, &$id): void {
try {
$madelinePhpContents ??= read(MADELINE_PHP);
$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) {
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) {
@ -114,11 +119,9 @@ final class GarbageCollector
} catch (Throwable $e) {
Logger::log("An error occurred in the phar cleanup loop: $e", Logger::FATAL_ERROR);
}
return false;
};
$cb();
self::$cleanupLoop = new PeriodicLoop($cb, 'Phar cleanup loop', 60*1000);
self::$cleanupLoop->start();
EventLoop::unreference($id = EventLoop::repeat(60.0, $cb));
}
/** @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;
use danog\MadelineProto\EventHandler;
use danog\MadelineProto\InternalDoc;
/**
@ -44,6 +43,6 @@ trait APILoop
public function __construct(InternalDoc $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 {
$this->ipcServer = new Server($this);
$this->ipcServer->setSettings($this->settings->getIpc());
$this->ipcServer->setIpcPath($this->wrapper->session);
$this->ipcServer->setIpcPath($this->wrapper->getSession());
} catch (Throwable $e) {
$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();
}
/**
* Set API wrapper needed for triggering serialization functions.
*
* @internal
*/
public function setWrapper(APIWrapper $wrapper): void
{
$this->wrapper = $wrapper;
}
/**
* Get API wrapper.
*
@ -888,7 +879,7 @@ final class MTProto implements TLCallback, LoggerGetter
try {
$this->ipcServer = new Server($this);
$this->ipcServer->setSettings($this->settings->getIpc());
$this->ipcServer->setIpcPath($this->wrapper->session);
$this->ipcServer->setIpcPath($this->wrapper->getSession());
} catch (Throwable $e) {
$this->logger->logger("Error while starting IPC server: $e", Logger::FATAL_ERROR);
}