Switch to external loop

This commit is contained in:
Daniil Gentili 2020-07-28 20:39:32 +02:00
parent 553e4c4da0
commit 444d55355a
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
31 changed files with 516 additions and 640 deletions

View File

@ -35,7 +35,8 @@
"league/uri": "^6",
"danog/ipc": "^0.1",
"tivie/htaccess-parser": "^0.2.3",
"amphp/log": "^1.1"
"amphp/log": "^1.1",
"danog/loop": "^0.1.0"
},
"require-dev": {
"vlucas/phpdotenv": "^3",

View File

@ -22,7 +22,7 @@ namespace danog\MadelineProto;
use Amp\Deferred;
use Amp\Promise;
use Amp\Success;
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
use danog\MadelineProto\Loop\Generic\PeriodicLoopInternal;
use danog\MadelineProto\MTProto\AuthKey;
use danog\MadelineProto\MTProto\PermAuthKey;
use danog\MadelineProto\MTProto\TempAuthKey;
@ -99,10 +99,8 @@ class DataCenterConnection implements JsonSerializable
private $linked;
/**
* Loop to keep weights at sane value.
*
* @var \danog\MadelineProto\Loop\Generic\PeriodicLoop
*/
private $robinLoop;
private ?PeriodicLoopInternal $robinLoop = null;
/**
* Decrement roundrobin weight by this value if busy reading.
*
@ -366,7 +364,7 @@ class DataCenterConnection implements JsonSerializable
$count = $media ? $this->API->settings['connection_settings']['media_socket_count']['min'] : 1;
if ($count > 1) {
if (!$this->robinLoop) {
$this->robinLoop = new PeriodicLoop($this->API, [$this, 'even'], "robin loop DC {$this->datacenter}", $this->API->settings['connection_settings']['robin_period']);
$this->robinLoop = new PeriodicLoopInternal($this->API, [$this, 'even'], "robin loop DC {$this->datacenter}", $this->API->settings['connection_settings']['robin_period'] * 1000);
}
$this->robinLoop->start();
}

View File

@ -12,7 +12,7 @@ final class WebRunner extends RunnerAbstract
private static $runPath;
/**
* Resources
* Resources.
*/
private static array $resources = [];
/**
@ -46,7 +46,9 @@ final class WebRunner extends RunnerAbstract
}
$rootDir = \dirname($rootDir).DIRECTORY_SEPARATOR;
$uriDir = \dirname($uri);
if ($uriDir !== '/') $uriDir .= DIRECTORY_SEPARATOR;
if ($uriDir !== '/') {
$uriDir .= DIRECTORY_SEPARATOR;
}
if (\substr($rootDir, -\strlen($uriDir)) !== $uriDir) {
throw new ContextException("Mismatch between absolute root dir ($rootDir) and URI dir ($uriDir)");

View File

@ -20,10 +20,10 @@ namespace danog\MadelineProto\Ipc;
use Amp\Ipc\IpcServer;
use Amp\Ipc\Sync\ChannelledSocket;
use danog\Loop\SignalLoop;
use danog\MadelineProto\Ipc\Runner\ProcessRunner;
use danog\MadelineProto\Ipc\Runner\WebRunner;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Impl\SignalLoop;
use danog\MadelineProto\Tools;
/**

View File

@ -1,7 +1,6 @@
<?php
/**
* Resumable loop interface.
* Internal loop trait.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@ -19,27 +18,29 @@
namespace danog\MadelineProto\Loop;
use Amp\Promise;
use danog\MadelineProto\InternalDoc;
/**
* Resumable loop interface.
*
* @author Daniil Gentili <daniil@daniil.it>
* API loop trait.
*/
interface ResumableLoopInterface extends LoopInterface
trait APILoop
{
use LoggerLoop {
__construct as private setLogger;
}
/**
* Pause the loop.
*
* @param int $time For how long to pause the loop, if null will pause forever (until resume is called from outside of the loop)
*
* @return Promise
* API instance.
*/
public function pause($time = null): Promise;
protected InternalDoc $API;
/**
* Resume the loop.
* Constructor.
*
* @return ?Promise
* @param InternalDoc $API API instance
*/
public function resume();
public function __construct(InternalDoc $API)
{
$this->API = $API;
$this->setLogger($API->getLogger());
}
}

View File

@ -21,8 +21,7 @@ namespace danog\MadelineProto\Loop\Connection;
use Amp\Deferred;
use Amp\Loop;
use danog\MadelineProto\Connection;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\Loop\ResumableSignalLoop;
use danog\MadelineProto\Tools;
/**
@ -32,31 +31,12 @@ use danog\MadelineProto\Tools;
*/
class CheckLoop extends ResumableSignalLoop
{
use Common;
/**
* Connection instance.
* Main loop.
*
* @var \danog\MadelineProto\Connection
* @return \Generator
*/
protected $connection;
/**
* DC ID.
*
* @var string
*/
protected $datacenter;
/**
* DataCenterConnection instance.
*
* @var \danog\MadelineProto\DataCenterConnection
*/
protected $datacenterConnection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
$this->API = $connection->getExtra();
$this->datacenter = $connection->getDatacenterID();
$this->datacenterConnection = $connection->getShared();
}
public function loop(): \Generator
{
$API = $this->API;
@ -64,6 +44,7 @@ class CheckLoop extends ResumableSignalLoop
$connection = $this->connection;
$shared = $this->datacenterConnection;
$timeout = $shared->getSettings()['timeout'];
$timeoutMs = $timeout * 1000;
$timeoutResend = $timeout * $timeout;
// Typically 25 seconds, good enough
while (true) {
@ -73,7 +54,7 @@ class CheckLoop extends ResumableSignalLoop
}
}
if (!$connection->hasPendingCalls()) {
if (yield $this->waitSignal($this->pause($timeout))) {
if (yield $this->waitSignal($this->pause($timeoutMs))) {
return;
}
continue;
@ -161,7 +142,7 @@ class CheckLoop extends ResumableSignalLoop
}
$connection->flush();
}
if (yield $this->waitSignal($this->pause($timeout))) {
if (yield $this->waitSignal($this->pause($timeoutMs))) {
return;
}
if ($connection->msgIdHandler->getMaxId(true) === $last_msgid && $connection->getLastChunk() === $last_chunk) {
@ -172,6 +153,11 @@ class CheckLoop extends ResumableSignalLoop
}
}
}
/**
* Loop name.
*
* @return string
*/
public function __toString(): string
{
return "check loop in DC {$this->datacenter}";

View File

@ -0,0 +1,61 @@
<?php
/**
* Common abstract class for all connection loops.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Connection;
use danog\MadelineProto\Connection;
use danog\MadelineProto\DataCenterConnection;
use danog\MadelineProto\Loop\InternalLoop;
/**
* RPC call status check loop.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
trait Common
{
use InternalLoop {
__construct as private init;
}
/**
* Connection instance.
*/
protected Connection $connection;
/**
* DC ID.
*
* @var string
*/
protected string $datacenter;
/**
* DataCenterConnection instance.
*/
protected DataCenterConnection $datacenterConnection;
/**
* Constructor function.
*
* @param Connection $connection Connection
*/
public function __construct(Connection $connection)
{
$this->init($connection->getExtra());
$this->connection = $connection;
$this->datacenter = $connection->getDatacenterID();
$this->datacenterConnection = $connection->getShared();
}
}

View File

@ -19,8 +19,7 @@
namespace danog\MadelineProto\Loop\Connection;
use danog\MadelineProto\Connection;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\Loop\ResumableSignalLoop;
/**
* HttpWait loop.
@ -29,31 +28,12 @@ use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
*/
class HttpWaitLoop extends ResumableSignalLoop
{
use Common;
/**
* Connection instance.
* Main loop.
*
* @var \danog\MadelineProto\Connection
* @return \Generator
*/
protected $connection;
/**
* DC ID.
*
* @var string
*/
protected $datacenter;
/**
* DataCenterConnection instance.
*
* @var \danog\MadelineProto\DataCenterConnection
*/
protected $datacenterConnection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
$this->API = $connection->getExtra();
$this->datacenter = $connection->getDatacenterID();
$this->datacenterConnection = $connection->getShared();
}
public function loop(): \Generator
{
$API = $this->API;
@ -82,6 +62,11 @@ class HttpWaitLoop extends ResumableSignalLoop
$API->logger->logger("DC {$datacenter}: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
}
}
/**
* Loop name.
*
* @return string
*/
public function __toString(): string
{
return "HTTP wait loop in DC {$this->datacenter}";

View File

@ -19,8 +19,7 @@
namespace danog\MadelineProto\Loop\Connection;
use danog\MadelineProto\Connection;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\Loop\ResumableSignalLoop;
/**
* Ping loop.
@ -29,31 +28,12 @@ use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
*/
class PingLoop extends ResumableSignalLoop
{
use Common;
/**
* Connection instance.
* Main loop.
*
* @var \danog\MadelineProto\Connection
* @return \Generator
*/
protected $connection;
/**
* DC ID.
*
* @var string
*/
protected $datacenter;
/**
* DataCenterConnection instance.
*
* @var \danog\MadelineProto\DataCenterConnection
*/
protected $datacenterConnection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
$this->API = $connection->getExtra();
$this->datacenter = $connection->getDatacenterID();
$this->datacenterConnection = $connection->getShared();
}
public function loop(): \Generator
{
$API = $this->API;
@ -61,13 +41,14 @@ class PingLoop extends ResumableSignalLoop
$connection = $this->connection;
$shared = $this->datacenterConnection;
$timeout = $shared->getSettings()['timeout'];
$timeoutMs = $timeout * 1000;
while (true) {
while (!$shared->hasTempAuthKey()) {
if (yield $this->waitSignal($this->pause())) {
return;
}
}
if (yield $this->waitSignal($this->pause($timeout))) {
if (yield $this->waitSignal($this->pause($timeoutMs))) {
return;
}
if (\time() - $connection->getLastChunk() >= $timeout) {
@ -81,6 +62,11 @@ class PingLoop extends ResumableSignalLoop
}
}
}
/**
* Get loop name.
*
* @return string
*/
public function __toString(): string
{
return "Ping loop in DC {$this->datacenter}";

View File

@ -23,9 +23,8 @@ use Amp\ByteStream\PendingReadError;
use Amp\ByteStream\StreamException;
use Amp\Loop;
use Amp\Websocket\ClosedException;
use danog\MadelineProto\Connection;
use danog\Loop\SignalLoop;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Impl\SignalLoop;
use danog\MadelineProto\MTProtoTools\Crypt;
use danog\MadelineProto\NothingInTheSocketException;
use danog\MadelineProto\Tools;
@ -37,31 +36,12 @@ use danog\MadelineProto\Tools;
*/
class ReadLoop extends SignalLoop
{
use Common;
/**
* Connection instance.
* Main loop.
*
* @var \danog\MadelineProto\Connection
* @return \Generator
*/
protected $connection;
/**
* DataCenterConnection instance.
*
* @var \danog\MadelineProto\DataCenterConnection
*/
protected $datacenterConnection;
/**
* DC ID.
*
* @var string
*/
protected $datacenter;
public function __construct(Connection $connection)
{
$this->connection = $connection;
$this->API = $connection->getExtra();
$this->datacenter = $connection->getDatacenterID();
$this->datacenterConnection = $connection->getShared();
}
public function loop(): \Generator
{
$API = $this->API;
@ -229,6 +209,11 @@ class ReadLoop extends SignalLoop
}
return true;
}
/**
* Get loop name.
*
* @return string
*/
public function __toString(): string
{
return "read loop in DC {$this->datacenter}";

View File

@ -21,9 +21,8 @@ namespace danog\MadelineProto\Loop\Connection;
use Amp\ByteStream\StreamException;
use Amp\Loop;
use danog\MadelineProto\Connection;
use danog\Loop\ResumableSignalLoop;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\MadelineProto\MTProtoTools\Crypt;
use danog\MadelineProto\Tools;
@ -38,32 +37,12 @@ class WriteLoop extends ResumableSignalLoop
const MAX_SIZE = 1 << 15;
const MAX_IDS = 8192;
use Common;
/**
* Connection instance.
* Main loop.
*
* @var \danog\MadelineProto\Connection
* @return \Generator
*/
protected $connection;
/**
* DataCenterConnection instance.
*
* @var \danog\MadelineProto\DataCenterConnection
*/
protected $datacenterConnection;
/**
* DC ID.
*
* @var string
*/
protected $datacenter;
public function __construct(Connection $connection)
{
$this->connection = $connection;
$this->datacenterConnection = $connection->getShared();
$this->API = $connection->getExtra();
$ctx = $connection->getCtx();
$this->datacenter = $connection->getDatacenterID();
}
public function loop(): \Generator
{
$API = $this->API;
@ -91,7 +70,7 @@ class WriteLoop extends ResumableSignalLoop
}
$connection->writing(true);
try {
$please_wait = yield $this->{$shared->hasTempAuthKey() ? 'encryptedWriteLoop' : 'unencryptedWriteLoop'}();
$please_wait = yield from $this->{$shared->hasTempAuthKey() ? 'encryptedWriteLoop' : 'unencryptedWriteLoop'}();
} catch (StreamException $e) {
if ($connection->shouldReconnect()) {
return;
@ -358,6 +337,11 @@ class WriteLoop extends ResumableSignalLoop
}
return $skipped;
}
/**
* Get loop name.
*
* @return string
*/
public function __toString(): string
{
return "write loop in DC {$this->datacenter}";

View File

@ -1,7 +1,7 @@
<?php
/**
* Generic loop.
* Update feeder loop.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@ -19,57 +19,30 @@
namespace danog\MadelineProto\Loop\Generic;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\Loop\Generic\GenericLoop as GenericGenericLoop;
use danog\MadelineProto\InternalDoc;
use danog\MadelineProto\Loop\APILoop;
/**
* Generic loop.
* {@inheritDoc}
*
* @author Daniil Gentili <daniil@daniil.it>
* @deprecated Use the danog/loop API instead
*/
class GenericLoop extends ResumableSignalLoop
class GenericLoop extends GenericGenericLoop
{
const STOP = -1;
const PAUSE = null;
const CONTINUE = 0;
protected $callback;
protected $name;
use APILoop {
__construct as private init;
}
/**
* Constructor.
*
* The callback will be bound to the GenericLoop instance: this means that you will be able to use `$this` as if the callback were actually the `loop` function (you can access the API property, use the pause/waitSignal methods & so on).
* The return value of the callable can be:
* A number - the loop will be paused for the specified number of seconds
* GenericLoop::STOP - The loop will stop
* GenericLoop::PAUSE - The loop will pause forever (or until the `resume` method is called on the loop object from outside the loop)
* GenericLoop::CONTINUE - Return this if you want to rerun the loop without waiting
*
* @param \danog\MadelineProto\API $API Instance of MadelineProto
* @param callable $callback Callback to run
* @param string $name Fetcher name
* @param InternalDoc $API API instance
* @param callable $callable Method
* @param string $name Loop name
*/
public function __construct($API, $callback, $name)
public function __construct(InternalDoc $API, callable $callable, string $name)
{
$this->API = $API;
$this->callback = $callback->bindTo($this);
$this->name = $name;
}
public function loop(): \Generator
{
$callback = $this->callback;
while (true) {
$timeout = yield $callback();
if ($timeout === self::PAUSE) {
$this->API->logger->logger("Pausing {$this}", \danog\MadelineProto\Logger::VERBOSE);
} elseif ($timeout > 0) {
$this->API->logger->logger("Pausing {$this} for {$timeout}", \danog\MadelineProto\Logger::VERBOSE);
}
if ($timeout === self::STOP || yield $this->waitSignal($this->pause($timeout))) {
return;
}
}
}
public function __toString(): string
{
return $this->name;
$this->init($API);
parent::__construct($callable, $name);
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* Periodic loop.
* Update feeder loop.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@ -19,64 +19,31 @@
namespace danog\MadelineProto\Loop\Generic;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\MadelineProto\MTProto;
use danog\Loop\Generic\PeriodicLoop as GenericPeriodicLoop;
use danog\MadelineProto\InternalDoc;
use danog\MadelineProto\Loop\APILoop;
/**
* Periodic loop.
* {@inheritDoc}
*
* @author Daniil Gentili <daniil@daniil.it>
* @deprecated Use the danog/loop API instead
*/
class PeriodicLoop extends ResumableSignalLoop
class PeriodicLoop extends GenericPeriodicLoop
{
/**
* Callback.
*
* @var callable
*/
private $callback;
/**
* Loop name.
*
* @var string
*/
private string $name;
/**
* Loop timeeout.
*
* @var int
*/
private $timeout;
use APILoop {
__construct as private init;
}
/**
* Constructor.
*
* @param \danog\MadelineProto\API $API Instance of MTProto class
* @param callable $callback Callback to call
* @param string $name Loop name
* @param int|float $timeout Loop timeout
* @param InternalDoc $API API instance
* @param callable $callable Method
* @param string $name Loop name
* @param ?int $interval Interval
*/
public function __construct($API, callable $callback, string $name, $timeout)
public function __construct(InternalDoc $API, callable $callable, string $name, ?int $interval)
{
$this->API = $API;
$this->callback = $callback;
$this->name = $name;
$this->timeout = $timeout;
}
public function loop(): \Generator
{
$callback = $this->callback;
$logger = $this->API->logger;
while (true) {
$result = yield $this->waitSignal($this->pause($this->timeout));
if ($result) {
$logger->logger("Got signal in {$this}, exiting");
return;
}
yield $callback();
}
}
public function __toString(): string
{
return $this->name;
$this->init($API);
parent::__construct($callable, $name, $interval === null ? $interval : $interval * 1000);
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* Signal loop interface.
* Update feeder loop.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@ -17,31 +17,33 @@
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop;
namespace danog\MadelineProto\Loop\Generic;
use Amp\Promise;
use danog\Loop\Generic\PeriodicLoop as GenericPeriodicLoop;
use danog\MadelineProto\Loop\InternalLoop;
use danog\MadelineProto\MTProto;
/**
* Signal loop interface.
* {@inheritDoc}
*
* @author Daniil Gentili <daniil@daniil.it>
* @internal For internal use
*/
interface SignalLoopInterface extends LoopInterface
class PeriodicLoopInternal extends GenericPeriodicLoop
{
use InternalLoop {
__construct as private init;
}
/**
* Resolve the promise or return|throw the signal.
* Constructor.
*
* @param Promise $promise The origin promise
*
* @return Promise
* @param MTProto $API API instance
* @param callable $callable Method
* @param string $name Loop name
* @param int|null $interval Interval
*/
public function waitSignal($promise): Promise;
/**
* Send a signal to the the loop.
*
* @param \Throwable|mixed $data Signal to send
*
* @return void
*/
public function signal($data): void;
public function __construct(MTProto $API, callable $callable, string $name, ?int $interval)
{
$this->init($API);
parent::__construct($callable, $name, $interval);
}
}

View File

@ -1,80 +0,0 @@
<?php
/**
* Loop helper trait.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Impl;
use Amp\Promise;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\LoopInterface;
/**
* Loop helper trait.
*
* Wraps the asynchronous generator methods with asynchronous promise-based methods
*
* @author Daniil Gentili <daniil@daniil.it>
*/
abstract class Loop implements LoopInterface
{
private $count = 0;
/**
* MTProto instance.
*
* @var \danog\MadelineProto\MTProto
*/
public $API;
public function __construct($API)
{
$this->API = $API;
}
public function start()
{
if ($this->count) {
//$this->API->logger->logger("NOT entering $this with running count {$this->count}", Logger::ERROR);
return false;
}
return \danog\MadelineProto\Tools::callFork($this->loopImpl());
}
private function loopImpl(): \Generator
{
$this->startedLoop();
$this->API->logger->logger("Entered {$this}", Logger::ULTRA_VERBOSE);
try {
yield from $this->loop();
} finally {
$this->exitedLoop();
$this->API->logger->logger("Physically exited {$this}", Logger::ULTRA_VERBOSE);
}
}
public function exitedLoop()
{
if ($this->count) {
$this->API->logger->logger("Exited {$this}", Logger::ULTRA_VERBOSE);
$this->count--;
}
}
public function startedLoop()
{
$this->count++;
}
public function isRunning()
{
return $this->count;
}
}

View File

@ -1,90 +0,0 @@
<?php
/**
* Loop helper trait.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Impl;
use Amp\Deferred;
use Amp\Loop;
use Amp\Promise;
use Amp\Success;
use danog\MadelineProto\Loop\ResumableLoopInterface;
/**
* Resumable signal loop helper trait.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
abstract class ResumableSignalLoop extends SignalLoop implements ResumableLoopInterface
{
private $resume;
private $pause;
protected $resumeWatcher;
public function pause($time = null): Promise
{
if (!\is_null($time)) {
if ($time <= 0) {
return new Success(0);
}
$resume = \microtime(true) + $time;
if ($this->resumeWatcher) {
Loop::cancel($this->resumeWatcher);
$this->resumeWatcher = null;
}
$this->resumeWatcher = Loop::delay((int) ($time * 1000), [$this, 'resume'], $resume);
}
$this->resume = new Deferred();
$pause = $this->pause;
$this->pause = new Deferred();
if ($pause) {
Loop::defer([$pause, 'resolve']);
}
return $this->resume->promise();
}
public function resume($watcherId = null, $expected = 0)
{
if ($this->resumeWatcher) {
$storedWatcherId = $this->resumeWatcher;
Loop::cancel($storedWatcherId);
$this->resumeWatcher = null;
if ($watcherId && $storedWatcherId !== $watcherId) {
return;
}
}
if ($this->resume) {
$resume = $this->resume;
$this->resume = null;
$resume->resolve();
return $this->pause ? $this->pause->promise() : null;
}
}
public function resumeDefer()
{
Loop::defer([$this, 'resume']);
return $this->pause ? $this->pause->promise() : null;
}
public function exitedLoop()
{
parent::exitedLoop();
if ($this->resumeWatcher) {
Loop::cancel($this->resumeWatcher);
$this->resumeWatcher = null;
}
}
}

View File

@ -1,70 +0,0 @@
<?php
/**
* Loop helper trait.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Impl;
use Amp\Deferred;
use Amp\Promise;
use danog\MadelineProto\Coroutine;
use danog\MadelineProto\Loop\SignalLoopInterface;
/**
* Signal loop helper trait.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
abstract class SignalLoop extends Loop implements SignalLoopInterface
{
private $signalDeferred;
/**
* Send signal to loop.
*
* @param mixed $what Data to signal
*
* @return void
*/
public function signal($what): void
{
if ($this->signalDeferred) {
$deferred = $this->signalDeferred;
$this->signalDeferred = null;
if ($what instanceof \Exception || $what instanceof \Throwable) {
$deferred->fail($what);
} else {
$deferred->resolve($what);
}
}
}
public function waitSignal($promise): Promise
{
if ($promise instanceof \Generator) {
$promise = new Coroutine($promise);
}
$this->signalDeferred = new Deferred();
$dpromise = $this->signalDeferred->promise();
$promise->onResolve(function () use ($promise) {
if ($this->signalDeferred !== null) {
$deferred = $this->signalDeferred;
$this->signalDeferred = null;
$deferred->resolve($promise);
}
});
return $dpromise;
}
}

View File

@ -1,7 +1,6 @@
<?php
/**
* Loop interface.
* Internal loop trait.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@ -19,29 +18,26 @@
namespace danog\MadelineProto\Loop;
/**
* Loop interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface LoopInterface
use danog\MadelineProto\MTProto;
trait InternalLoop
{
use LoggerLoop {
__construct as private setLogger;
}
/**
* Start the loop.
*
* @return void
* API instance.
*/
public function start();
protected MTProto $API;
/**
* The actual loop.
* Constructor.
*
* @return void
* @param MTProto $API API instance
*/
public function loop();
/**
* Get name of the loop.
*
* @return string
*/
public function __toString(): string;
public function __construct(MTProto $API)
{
$this->API = $API;
$this->setLogger($API->getLogger());
}
}

View File

@ -0,0 +1,119 @@
<?php
/**
* Loop logging trait.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Tools;
trait LoggerLoop
{
/**
* Whether the loop was started.
*/
private bool $started = false;
/**
* Logger instance.
*/
protected Logger $logger;
/**
* Constructor.
*
* @param Logger $logger Logger instance
*/
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
/**
* Start the loop.
*
* Returns false if the loop is already running.
*
* @return bool
*/
public function start(): bool
{
if ($this->started) {
return false;
}
Tools::callFork((function (): \Generator {
$this->startedLoop();
try {
yield from $this->loop();
} finally {
$this->exitedLoop();
}
})());
return true;
}
/**
* Check whether loop is running.
*
* @return boolean
*/
public function isRunning(): bool
{
return $this->started;
}
/**
* Signal that loop has started.
*
* @return void
*/
protected function startedLoop(): void
{
parent::startedLoop();
$this->logger->logger("Entered $this", Logger::ULTRA_VERBOSE);
}
/**
* Signal that loop has exited.
*
* @return void
*/
protected function exitedLoop(): void
{
parent::exitedLoop();
$this->logger->logger("Exited $this", Logger::ULTRA_VERBOSE);
}
/**
* Report pause, can be overriden for logging.
*
* @param integer $timeout Pause duration, 0 = forever
*
* @return void
*/
protected function reportPause(int $timeout): void
{
$this->logger->logger(
"Pausing $this for $timeout",
Logger::ULTRA_VERBOSE
);
}
/**
* Get loop name.
*
* @return string
*/
abstract public function __toString(): string;
}

View File

@ -19,30 +19,60 @@
namespace danog\MadelineProto\Loop\Update;
use Amp\Loop;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\Loop\ResumableSignalLoop;
use danog\MadelineProto\Loop\InternalLoop;
use danog\MadelineProto\MTProto;
use function Amp\delay;
/**
* update feed loop.
* Update feed loop.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class FeedLoop extends ResumableSignalLoop
{
private $incomingUpdates = [];
private $parsedUpdates = [];
private $channelId;
use InternalLoop {
__construct as private init;
}
/**
* Main loop ID.
*/
const GENERIC = 0;
/**
* Incoming updates array.
*/
private array $incomingUpdates = [];
/**
* Parsed updates array.
*/
private array $parsedUpdates = [];
/**
* Channel ID.
*/
private int $channelId;
/**
* Update loop.
*
* @var UpdateLoop
*/
private $updater;
public function __construct($API, $channelId = false)
private ?UpdateLoop $updater = null;
/**
* Constructor.
*
* @param MTProto $API API instance
* @param integer $channelId Constructor
*/
public function __construct(MTProto $API, int $channelId = 0)
{
$this->API = $API;
$this->init($API);
$this->channelId = $channelId;
}
/**
* Main loop.
*
* @return \Generator
*/
public function loop(): \Generator
{
$API = $this->API;
@ -55,7 +85,11 @@ class FeedLoop extends ResumableSignalLoop
return;
}
}
$this->state = $this->channelId === false ? yield from $API->loadUpdateState() : $API->loadChannelState($this->channelId);
yield (function (): \Generator {
yield delay(1);
return 1;
})();
$this->state = $this->channelId === self::GENERIC ? yield from $API->loadUpdateState() : $API->loadChannelState($this->channelId);
while (true) {
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
if (yield $this->waitSignal($this->pause())) {
@ -145,11 +179,11 @@ class FeedLoop extends ResumableSignalLoop
}
public function feedSingle(array $update): \Generator
{
$channelId = false;
$channelId = self::GENERIC;
switch ($update['_']) {
case 'updateNewChannelMessage':
case 'updateEditChannelMessage':
$channelId = isset($update['message']['to_id']['channel_id']) ? $update['message']['to_id']['channel_id'] : false;
$channelId = isset($update['message']['to_id']['channel_id']) ? $update['message']['to_id']['channel_id'] : self::GENERIC;
if (!$channelId) {
return false;
}
@ -159,7 +193,7 @@ class FeedLoop extends ResumableSignalLoop
$channelId = $update['channel_id'];
break;
case 'updateChannelTooLong':
$channelId = isset($update['channel_id']) ? $update['channel_id'] : false;
$channelId = isset($update['channel_id']) ? $update['channel_id'] : self::GENERIC;
if (!isset($update['pts'])) {
$update['pts'] = 1;
}
@ -202,7 +236,7 @@ class FeedLoop extends ResumableSignalLoop
$this->API->logger->logger("Not enough data: for message update {$log}, getting difference...", \danog\MadelineProto\Logger::VERBOSE);
$update = ['_' => 'updateChannelTooLong'];
if ($channelId && $to) {
$channelId = false;
$channelId = self::GENERIC;
}
}
break;
@ -217,7 +251,7 @@ class FeedLoop extends ResumableSignalLoop
if (isset($this->API->feeders[$channelId])) {
return yield from $this->API->feeders[$channelId]->feedSingle($update);
} elseif ($this->channelId) {
return yield from $this->API->feeders[false]->feedSingle($update);
return yield from $this->API->feeders[self::GENERIC]->feedSingle($update);
}
}
$this->API->logger->logger('Was fed an update of type '.$update['_']." in {$this}...", \danog\MadelineProto\Logger::VERBOSE);
@ -238,7 +272,7 @@ class FeedLoop extends ResumableSignalLoop
if ($message['_'] !== 'messageEmpty') {
$this->API->logger->logger('Getdiff fed me message of type '.$message['_']." in {$this}...", \danog\MadelineProto\Logger::VERBOSE);
}
$this->parsedUpdates[] = ['_' => $this->channelId === false ? 'updateNewMessage' : 'updateNewChannelMessage', 'message' => $message, 'pts' => -1, 'pts_count' => -1];
$this->parsedUpdates[] = ['_' => $this->channelId === self::GENERIC ? 'updateNewMessage' : 'updateNewChannelMessage', 'message' => $message, 'pts' => -1, 'pts_count' => -1];
}
}
public function __toString(): string

View File

@ -19,7 +19,8 @@
namespace danog\MadelineProto\Loop\Update;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\Loop\ResumableSignalLoop;
use danog\MadelineProto\Loop\InternalLoop;
/**
* update feed loop.
@ -28,17 +29,28 @@ use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
*/
class SeqLoop extends ResumableSignalLoop
{
private $incomingUpdates = [];
private $feeder;
private $pendingWakeups = [];
public function __construct($API)
{
$this->API = $API;
}
use InternalLoop;
/**
* Incoming updates.
*/
private array $incomingUpdates = [];
/**
* Update feeder.
*/
private ?FeedLoop $feeder = null;
/**
* Pending updates.
*/
private array $pendingWakeups = [];
/**
* Main loop.
*
* @return \Generator
*/
public function loop(): \Generator
{
$API = $this->API;
$this->feeder = $API->feeders[false];
$this->feeder = $API->feeders[FeedLoop::GENERIC];
if (!$this->API->settings['updates']['handle_updates']) {
return false;
}
@ -90,9 +102,9 @@ class SeqLoop extends ResumableSignalLoop
$result = $this->state->checkSeq($seq_start);
if ($result > 0) {
$this->API->logger->logger('Seq hole. seq_start: '.$seq_start.' != cur seq: '.($this->state->seq() + 1), \danog\MadelineProto\Logger::ERROR);
yield $this->pause(1.0);
yield $this->pause(1000);
if (!$this->incomingUpdates) {
yield $this->API->updaters[false]->resume();
yield $this->API->updaters[UpdateLoop::GENERIC]->resume();
}
$this->incomingUpdates = \array_merge($this->incomingUpdates, [$update], $updates);
continue;

View File

@ -19,9 +19,10 @@
namespace danog\MadelineProto\Loop\Update;
use Amp\Loop;
use danog\Loop\ResumableSignalLoop;
use danog\MadelineProto\Exception;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\MadelineProto\Loop\InternalLoop;
use danog\MadelineProto\MTProto;
use danog\MadelineProto\RPCErrorException;
/**
@ -31,14 +32,39 @@ use danog\MadelineProto\RPCErrorException;
*/
class UpdateLoop extends ResumableSignalLoop
{
use InternalLoop {
__construct as private init;
}
/**
* Main loop ID.
*/
const GENERIC = 0;
private $toPts;
private $channelId;
private $feeder;
public function __construct($API, $channelId)
/**
* Loop name.
*/
private int $channelId;
/**
* Feed loop.
*/
private ?FeedLoop $feeder = null;
/**
* Constructor.
*
* @param MTProto $API
* @param integer $channelId
*/
public function __construct(MTProto $API, int $channelId)
{
$this->API = $API;
$this->init($API);
$this->channelId = $channelId;
}
/**
* Main loop.
*
* @return \Generator
*/
public function loop(): \Generator
{
$API = $this->API;
@ -49,8 +75,8 @@ class UpdateLoop extends ResumableSignalLoop
return;
}
}
$this->state = $state = $this->channelId === false ? yield from $API->loadUpdateState() : $API->loadChannelState($this->channelId);
$timeout = $API->settings['updates']['getdifference_interval'];
$this->state = $state = $this->channelId === self::GENERIC ? yield from $API->loadUpdateState() : $API->loadChannelState($this->channelId);
$timeout = $API->settings['updates']['getdifference_interval'] * 1000;
$first = true;
while (true) {
while (!$API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
@ -184,18 +210,25 @@ class UpdateLoop extends ResumableSignalLoop
$API->signalUpdate();
$API->logger->logger("Finished signaling updates in {$this}, pausing");
$first = false;
if (yield $this->waitSignal($this->pause($timeout))) {
if (yield $this->waitSignal($this->pause($timeout * 1000))) {
$API->logger->logger("Exiting {$this} due to signal");
return;
}
}
}
public function setLimit($toPts)
public function setLimit($toPts): void
{
$this->toPts = $toPts;
}
/**
* Get loop name.
*
* @return string
*/
public function __toString(): string
{
return !$this->channelId ? 'getUpdate loop generic' : "getUpdate loop channel {$this->channelId}";
return $this->channelId ?
"getUpdate loop channel {$this->channelId}" :
'getUpdate loop generic';
}
}

View File

@ -29,7 +29,7 @@ use danog\MadelineProto\Db\DbPropertiesFabric;
use danog\MadelineProto\Db\DbPropertiesTrait;
use danog\MadelineProto\Db\Mysql;
use danog\MadelineProto\Ipc\Server;
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
use danog\MadelineProto\Loop\Generic\PeriodicLoopInternal;
use danog\MadelineProto\Loop\Update\FeedLoop;
use danog\MadelineProto\Loop\Update\SeqLoop;
use danog\MadelineProto\Loop\Update\UpdateLoop;
@ -344,46 +344,32 @@ class MTProto extends AsyncConstruct implements TLCallback
public $minDatabase;
/**
* TOS check loop.
*
* @var PeriodicLoop
*/
public $checkTosLoop;
public ?PeriodicLoopInternal $checkTosLoop = null;
/**
* Phone config loop.
*
* @var PeriodicLoop
*/
public $phoneConfigLoop;
public ?PeriodicLoopInternal $phoneConfigLoop = null;
/**
* Config loop.
*
* @var PeriodicLoop
*/
public $configLoop;
public ?PeriodicLoopInternal $configLoop = null;
/**
* Call checker loop.
*
* @var PeriodicLoop
*/
private $callCheckerLoop;
private ?PeriodicLoopInternal $callCheckerLoop = null;
/**
* Autoserialization loop.
*
* @var PeriodicLoop
*/
private $serializeLoop;
private ?PeriodicLoopInternal $serializeLoop = null;
/**
* RPC reporting loop.
*
* @var PeriodicLoop
*/
private $rpcLoop;
private ?PeriodicLoopInternal $rpcLoop = null;
/**
* IPC server.
*
* @var Server
*/
private $ipcServer;
private ?Server $ipcServer = null;
/**
* Feeder loops.
*
@ -401,7 +387,7 @@ class MTProto extends AsyncConstruct implements TLCallback
*
* @var boolean
*/
public $destructing = false;
public bool $destructing = false;
/**
* DataCenter instance.
*
@ -454,7 +440,7 @@ class MTProto extends AsyncConstruct implements TLCallback
*
* @param array $settings Settings
*
* @return void
* @return \Generator
*/
public function __construct_async($settings = []): \Generator
{
@ -708,22 +694,22 @@ class MTProto extends AsyncConstruct implements TLCallback
private function startLoops()
{
if (!$this->callCheckerLoop) {
$this->callCheckerLoop = new PeriodicLoop($this, [$this, 'checkCalls'], 'call check', 10);
$this->callCheckerLoop = new PeriodicLoopInternal($this, [$this, 'checkCalls'], 'call check', 10 * 1000);
}
if (!$this->serializeLoop) {
$this->serializeLoop = new PeriodicLoop($this, [$this, 'serialize'], 'serialize', $this->settings['serialization']['serialization_interval']);
$this->serializeLoop = new PeriodicLoopInternal($this, [$this, 'serialize'], 'serialize', $this->settings['serialization']['serialization_interval'] * 1000);
}
if (!$this->phoneConfigLoop) {
$this->phoneConfigLoop = new PeriodicLoop($this, [$this, 'getPhoneConfig'], 'phone config', 24 * 3600);
$this->phoneConfigLoop = new PeriodicLoopInternal($this, [$this, 'getPhoneConfig'], 'phone config', 24 * 3600 * 1000);
}
if (!$this->checkTosLoop) {
$this->checkTosLoop = new PeriodicLoop($this, [$this, 'checkTos'], 'TOS', 24 * 3600);
$this->checkTosLoop = new PeriodicLoopInternal($this, [$this, 'checkTos'], 'TOS', 24 * 3600 * 1000);
}
if (!$this->configLoop) {
$this->configLoop = new PeriodicLoop($this, [$this, 'getConfig'], 'config', 24 * 3600);
$this->configLoop = new PeriodicLoopInternal($this, [$this, 'getConfig'], 'config', 24 * 3600 * 1000);
}
if (!$this->rpcLoop) {
$this->rpcLoop = new PeriodicLoop($this, [$this, 'rpcReport'], 'config', 60);
$this->rpcLoop = new PeriodicLoopInternal($this, [$this, 'rpcReport'], 'config', 60 * 1000);
}
if (!$this->ipcServer) {
$this->ipcServer = new Server($this);
@ -812,7 +798,7 @@ class MTProto extends AsyncConstruct implements TLCallback
if (!$this->updates_state instanceof UpdatesState) {
$this->updates_state = new UpdatesState($this->updates_state);
}
$this->channels_state->__construct([false => $this->updates_state]);
$this->channels_state->__construct([UpdateLoop::GENERIC => $this->updates_state]);
unset($this->updates_state);
}
if (!isset($this->datacenter)) {
@ -1017,9 +1003,9 @@ class MTProto extends AsyncConstruct implements TLCallback
}
if ($this->authorized === self::LOGGED_IN && $this->settings['updates']['handle_updates']) {
$this->logger->logger(Lang::$current_lang['getupdates_deserialization'], Logger::NOTICE);
yield $this->updaters[false]->resume();
yield $this->updaters[UpdateLoop::GENERIC]->resume();
}
$this->updaters[false]->start();
$this->updaters[UpdateLoop::GENERIC]->start();
GarbageCollector::start();
}
@ -1165,7 +1151,7 @@ class MTProto extends AsyncConstruct implements TLCallback
$lang_pack = 'android';
}
// Detect app version
$app_version = self::RELEASE.' ('.self::V.', '.str_replace(' (AN UPDATE IS REQUIRED)', '', Magic::$revision).')';
$app_version = self::RELEASE.' ('.self::V.', '.\str_replace(' (AN UPDATE IS REQUIRED)', '', Magic::$revision).')';
if (($settings['app_info']['api_id'] ?? 0) === 6) {
// TG DEV NOTICE: these app info spoofing measures were implemented for NON-MALICIOUS purposes.
// All accounts registered with a custom API ID require manual verification through recover@telegram.org, to avoid instant permabans.
@ -1441,7 +1427,7 @@ class MTProto extends AsyncConstruct implements TLCallback
*/
public function setupLogger(): void
{
$this->logger = Logger::getLoggerFromSettings($this->settings, isset($this->authorization['user']) ? isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id'] : '');
$this->logger = Logger::getLoggerFromSettings($this->settings, isset($this->authorization['user']) ? (isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id']) : '');
}
/**
* Reset all MTProto sessions.
@ -1517,7 +1503,7 @@ class MTProto extends AsyncConstruct implements TLCallback
*/
public function connectToAllDcs(bool $reconnectAll = true): \Generator
{
$this->channels_state->get(false);
$this->channels_state->get(FeedLoop::GENERIC);
foreach ($this->channels_state->get() as $state) {
$channelId = $state->getChannel();
if (!isset($this->feeders[$channelId])) {
@ -1644,7 +1630,7 @@ class MTProto extends AsyncConstruct implements TLCallback
if (!isset($this->seqUpdater)) {
$this->seqUpdater = new SeqLoop($this);
}
$this->channels_state->get(false);
$this->channels_state->get(FeedLoop::GENERIC);
$channelIds = [];
foreach ($this->channels_state->get() as $state) {
$channelIds[] = $state->getChannel();

View File

@ -21,6 +21,7 @@ namespace danog\MadelineProto\MTProtoSession;
use Amp\Loop;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Update\UpdateLoop;
use danog\MadelineProto\MTProto;
/**
@ -84,8 +85,8 @@ trait ResponseHandler
$this->shared->getTempAuthKey()->setServerSalt($this->incoming_messages[$current_msg_id]['content']['server_salt']);
$this->ackIncomingMessageId($current_msg_id);
// Acknowledge that I received the server's response
if ($this->API->authorized === MTProto::LOGGED_IN && !$this->API->isInitingAuthorization() && $this->API->datacenter->getDataCenterConnection($this->API->datacenter->curdc)->hasTempAuthKey() && isset($this->API->updaters[false])) {
$this->API->updaters[false]->resumeDefer();
if ($this->API->authorized === MTProto::LOGGED_IN && !$this->API->isInitingAuthorization() && $this->API->datacenter->getDataCenterConnection($this->API->datacenter->curdc)->hasTempAuthKey() && isset($this->API->updaters[UpdateLoop::GENERIC])) {
$this->API->updaters[UpdateLoop::GENERIC]->resumeDefer();
}
unset($this->incoming_messages[$current_msg_id]['content']);
break;

View File

@ -609,7 +609,7 @@ trait AuthKeyHandler
}
continue;
}
yield $socket->waitGetConnection();
yield from $socket->waitGetConnection();
if (isset($this->init_auth_dcs[$id])) {
$this->pending_auth = true;
continue;
@ -620,7 +620,7 @@ trait AuthKeyHandler
}
if ($dcs) {
$first = \array_shift($dcs)();
yield $first;
yield from $first;
}
foreach ($dcs as $id => &$dc) {
$dc = $dc();

View File

@ -23,6 +23,8 @@ use Amp\Deferred;
use Amp\Http\Client\Request;
use Amp\Loop;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Update\FeedLoop;
use danog\MadelineProto\Loop\Update\UpdateLoop;
use danog\MadelineProto\RPCErrorException;
/**
@ -189,9 +191,9 @@ trait UpdateHandler
{
if (!$this->got_state) {
$this->got_state = true;
$this->channels_state->get(false, yield from $this->getUpdatesState());
$this->channels_state->get(0, yield from $this->getUpdatesState());
}
return $this->channels_state->get(false);
return $this->channels_state->get(0);
}
/**
* Load channel state.
@ -256,7 +258,7 @@ trait UpdateHandler
$result = [];
foreach ($updates['updates'] as $key => $update) {
if ($update['_'] === 'updateNewMessage' || $update['_'] === 'updateReadMessagesContents' || $update['_'] === 'updateEditMessage' || $update['_'] === 'updateDeleteMessages' || $update['_'] === 'updateReadHistoryInbox' || $update['_'] === 'updateReadHistoryOutbox' || $update['_'] === 'updateWebPage' || $update['_'] === 'updateMessageID') {
$result[yield from $this->feeders[false]->feedSingle($update)] = true;
$result[yield from $this->feeders[FeedLoop::GENERIC]->feedSingle($update)] = true;
unset($updates['updates'][$key]);
}
}
@ -272,7 +274,7 @@ trait UpdateHandler
$this->seqUpdater->resume();
break;
case 'updateShort':
$this->feeders[yield from $this->feeders[false]->feedSingle($updates['update'])]->resume();
$this->feeders[yield from $this->feeders[FeedLoop::GENERIC]->feedSingle($updates['update'])]->resume();
break;
case 'updateShortSentMessage':
if (!isset($updates['request']['body'])) {
@ -287,7 +289,7 @@ trait UpdateHandler
$from_id = isset($updates['from_id']) ? $updates['from_id'] : ($updates['out'] ? $this->authorization['user']['id'] : $updates['user_id']);
$to_id = isset($updates['chat_id']) ? -$updates['chat_id'] : ($updates['out'] ? $updates['user_id'] : $this->authorization['user']['id']);
if (!((yield from $this->peerIsset($from_id)) || !((yield from $this->peerIsset($to_id)) || isset($updates['via_bot_id']) && !((yield from $this->peerIsset($updates['via_bot_id'])) || isset($updates['entities']) && !((yield from $this->entitiesPeerIsset($updates['entities'])) || isset($updates['fwd_from']) && !(yield from $this->fwdPeerIsset($updates['fwd_from']))))))) {
yield $this->updaters[false]->resume();
yield $this->updaters[FeedLoop::GENERIC]->resume();
return;
}
$message = $updates;
@ -305,10 +307,10 @@ trait UpdateHandler
break;
}
$update = ['_' => 'updateNewMessage', 'message' => $message, 'pts' => $updates['pts'], 'pts_count' => $updates['pts_count']];
$this->feeders[yield from $this->feeders[false]->feedSingle($update)]->resume();
$this->feeders[yield from $this->feeders[FeedLoop::GENERIC]->feedSingle($update)]->resume();
break;
case 'updatesTooLong':
$this->updaters[false]->resume();
$this->updaters[UpdateLoop::GENERIC]->resume();
break;
default:
throw new \danog\MadelineProto\ResponseException('Unrecognized update received: '.\var_export($updates, true));
@ -389,7 +391,7 @@ trait UpdateHandler
}
if ($update['qts'] > $cur_state->qts() + 1) {
$this->logger->logger('Qts hole. Fetching updates manually: update qts: '.$update['qts'].' > current qts '.$cur_state->qts().'+1, chat id: '.$update['message']['chat_id'], \danog\MadelineProto\Logger::ERROR);
$this->updaters[false]->resumeDefer();
$this->updaters[UpdateLoop::GENERIC]->resumeDefer();
return false;
}
$this->logger->logger('Applying qts: '.$update['qts'].' over current qts '.$cur_state->qts().', chat id: '.$update['message']['chat_id'], \danog\MadelineProto\Logger::VERBOSE);

View File

@ -26,47 +26,37 @@ class UpdatesState
{
/**
* PTS.
*
* @var int
*/
private $pts = 1;
private int $pts = 1;
/**
* QTS.
*
* @var int
*/
private $qts = -1;
private int $qts = -1;
/**
* Seq.
*
* @var int
*/
private $seq = 0;
private int $seq = 0;
/**
* Date.
*
* @var int
*/
private $date = 1;
private int $date = 1;
/**
* Channel ID.
*
* @var int|bool
* @var int
*/
private $channelId;
/**
* Is busy?
*
* @var bool
*/
private $syncLoading = false;
private bool $syncLoading = false;
/**
* Init function.
*
* @param array $init Initial parameters
* @param bool $channelId Channel ID
* @param int $channelId Channel ID
*/
public function __construct($init = [], $channelId = false)
public function __construct(array $init = [], int $channelId = 0)
{
$this->channelId = $channelId;
$this->update($init);
@ -80,6 +70,16 @@ class UpdatesState
{
return $this->channelId ? ['pts', 'channelId'] : ['pts', 'qts', 'seq', 'date', 'channelId'];
}
/**
* Wakeup function.
*/
public function __wakeup()
{
/** @psalm-suppress DocblockTypeContradiction */
if ($this->channelId === false) {
$this->channelId = 0;
}
}
/**
* Is this state relative to a channel?
*
@ -92,9 +92,9 @@ class UpdatesState
/**
* Get the channel ID.
*
* @return int|null
* @return int
*/
public function getChannel()
public function getChannel(): int
{
return $this->channelId;
}

View File

@ -19,6 +19,7 @@
namespace danog\MadelineProto\SecretChats;
use danog\MadelineProto\Loop\Update\UpdateLoop;
use danog\MadelineProto\MTProto;
/**
@ -95,7 +96,7 @@ trait AuthKeyHandler
$this->checkG($g_a, $dh_config['p']);
$res = yield from $this->methodCallAsyncRead('messages.requestEncryption', ['user_id' => $user, 'g_a' => $g_a->toBytes()], ['datacenter' => $this->datacenter->curdc]);
$this->temp_requested_secret_chats[$res['id']] = $a;
$this->updaters[false]->resume();
$this->updaters[UpdateLoop::GENERIC]->resume();
$this->logger->logger('Secret chat '.$res['id'].' requested successfully!', \danog\MadelineProto\Logger::NOTICE);
return $res['id'];
}
@ -163,7 +164,7 @@ trait AuthKeyHandler
$this->temp_rekeyed_secret_chats[$e] = $a;
$this->secret_chats[$chat]['rekeying'] = [1, $e];
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionRequestKey', 'g_a' => $g_a->toBytes(), 'exchange_id' => $e]]], ['datacenter' => $this->datacenter->curdc]);
$this->updaters[false]->resume();
$this->updaters[UpdateLoop::GENERIC]->resume();
return $e;
}
/**
@ -203,7 +204,7 @@ trait AuthKeyHandler
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
$this->checkG($g_b, $dh_config['p']);
yield from $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAcceptKey', 'g_b' => $g_b->toBytes(), 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]], ['datacenter' => $this->datacenter->curdc]);
$this->updaters[false]->resume();
$this->updaters[UpdateLoop::GENERIC]->resume();
}
/**
* Commit rekeying of secret chat.
@ -238,7 +239,7 @@ trait AuthKeyHandler
$this->secret_chats[$chat]['key'] = $key;
$this->secret_chats[$chat]['ttr'] = 100;
$this->secret_chats[$chat]['updated'] = \time();
$this->updaters[false]->resume();
$this->updaters[UpdateLoop::GENERIC]->resume();
}
/**
* Complete rekeying.

View File

@ -19,6 +19,7 @@
namespace danog\MadelineProto\VoIP;
use danog\MadelineProto\Loop\Update\UpdateLoop;
use danog\MadelineProto\Tools;
/**
@ -109,7 +110,7 @@ trait AuthKeyHandler
$res = yield from $this->methodCallAsyncRead('phone.requestCall', ['user_id' => $user, 'g_a_hash' => \hash('sha256', $g_a->toBytes(), true), 'protocol' => ['_' => 'phoneCallProtocol', 'udp_p2p' => true, 'udp_reflector' => true, 'min_layer' => 65, 'max_layer' => \danog\MadelineProto\VoIP::getConnectionMaxLayer()]], ['datacenter' => $this->datacenter->curdc]);
$controller->setCall($res['phone_call']);
$this->calls[$res['phone_call']['id']] = $controller;
yield $this->updaters[false]->resume();
yield $this->updaters[UpdateLoop::GENERIC]->resume();
return $controller;
}
/**
@ -149,7 +150,7 @@ trait AuthKeyHandler
throw $e;
}
$this->calls[$res['phone_call']['id']]->storage['b'] = $b;
yield $this->updaters[false]->resume();
yield $this->updaters[UpdateLoop::GENERIC]->resume();
return true;
}
/**

View File

@ -19,8 +19,8 @@
namespace danog\MadelineProto\Wrappers;
use function Amp\ByteStream\getOutputBufferStream;
use \danog\MadelineProto\MTProto;
use function Amp\ByteStream\getOutputBufferStream;
trait Templates
{

View File

@ -6,14 +6,14 @@ function ___install_madeline()
{
if (\count(\debug_backtrace(0)) === 1) {
if (isset($GLOBALS['argv']) && !empty($GLOBALS['argv'])) {
$arguments = array_slice($GLOBALS['argv'], 1);
$arguments = \array_slice($GLOBALS['argv'], 1);
} elseif (isset($_GET['argv']) && !empty($_GET['argv'])) {
$arguments = $_GET['argv'];
} else {
$arguments = [];
}
if (count($arguments) >= 2) {
\define(\MADELINE_WORKER_TYPE::class, array_shift($arguments));
if (\count($arguments) >= 2) {
\define(\MADELINE_WORKER_TYPE::class, \array_shift($arguments));
\define(\MADELINE_WORKER_ARGS::class, $arguments);
} else {
die('MadelineProto loader: you must include this file in another PHP script, see https://docs.madelineproto.xyz for more info.'.PHP_EOL);