mirror of
https://github.com/danog/MadelineProto.git
synced 2024-11-30 06:59:01 +01:00
Fix garbage collection
This commit is contained in:
parent
7488d9734e
commit
77a5417f20
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user