2022-12-30 21:54:44 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
2020-07-11 20:01:54 +02:00
|
|
|
/**
|
|
|
|
* API wrapper module.
|
|
|
|
*
|
|
|
|
* 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>
|
2023-01-04 12:43:01 +01:00
|
|
|
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
|
2020-07-11 20:01:54 +02:00
|
|
|
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
|
|
|
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace danog\MadelineProto\Ipc;
|
|
|
|
|
2022-12-30 20:24:13 +01:00
|
|
|
use Amp\DeferredFuture;
|
2022-12-30 21:43:58 +01:00
|
|
|
use Amp\Future;
|
2020-07-11 20:01:54 +02:00
|
|
|
use Amp\Ipc\IpcServer;
|
|
|
|
use Amp\Ipc\Sync\ChannelledSocket;
|
2023-01-24 14:28:49 +01:00
|
|
|
use danog\Loop\Loop;
|
2022-12-30 19:21:36 +01:00
|
|
|
use danog\MadelineProto\Exception;
|
2020-07-11 20:01:54 +02:00
|
|
|
use danog\MadelineProto\Ipc\Runner\ProcessRunner;
|
|
|
|
use danog\MadelineProto\Ipc\Runner\WebRunner;
|
|
|
|
use danog\MadelineProto\Logger;
|
2020-08-27 19:23:34 +02:00
|
|
|
use danog\MadelineProto\Loop\InternalLoop;
|
2020-09-24 11:45:20 +02:00
|
|
|
use danog\MadelineProto\SessionPaths;
|
2020-09-26 17:11:41 +02:00
|
|
|
use danog\MadelineProto\Settings\Ipc;
|
2023-01-21 21:32:36 +01:00
|
|
|
use danog\MadelineProto\Shutdown;
|
2020-07-11 20:01:54 +02:00
|
|
|
use danog\MadelineProto\Tools;
|
2023-01-21 21:21:35 +01:00
|
|
|
use Revolt\EventLoop;
|
2022-12-30 19:21:36 +01:00
|
|
|
use Throwable;
|
2020-07-11 20:01:54 +02:00
|
|
|
|
2023-01-03 21:51:49 +01:00
|
|
|
use function Amp\async;
|
2023-01-15 16:12:12 +01:00
|
|
|
use function Amp\delay;
|
2021-06-10 15:54:15 +02:00
|
|
|
|
2020-07-11 20:01:54 +02:00
|
|
|
/**
|
|
|
|
* IPC server.
|
2023-02-16 18:38:47 +01:00
|
|
|
*
|
|
|
|
* @internal
|
2020-07-11 20:01:54 +02:00
|
|
|
*/
|
2023-01-24 14:28:49 +01:00
|
|
|
class Server extends Loop
|
2020-07-11 20:01:54 +02:00
|
|
|
{
|
2020-08-27 19:23:34 +02:00
|
|
|
use InternalLoop;
|
2021-05-12 20:43:55 +02:00
|
|
|
/**
|
|
|
|
* Server version.
|
|
|
|
*/
|
|
|
|
const VERSION = 1;
|
2020-07-12 00:17:47 +02:00
|
|
|
/**
|
2020-09-24 11:45:20 +02:00
|
|
|
* Shutdown server.
|
2020-07-12 00:17:47 +02:00
|
|
|
*/
|
2020-09-24 11:45:20 +02:00
|
|
|
const SHUTDOWN = 0;
|
2020-07-12 00:17:47 +02:00
|
|
|
/**
|
2020-09-24 11:45:20 +02:00
|
|
|
* Boolean to shut down worker, if started.
|
2020-07-12 00:17:47 +02:00
|
|
|
*/
|
2020-09-24 11:45:20 +02:00
|
|
|
private static bool $shutdown = false;
|
|
|
|
/**
|
|
|
|
* Deferred to shut down worker, if started.
|
|
|
|
*/
|
2022-12-30 21:43:58 +01:00
|
|
|
private static ?DeferredFuture $shutdownDeferred = null;
|
2021-04-23 17:41:08 +02:00
|
|
|
/**
|
|
|
|
* Boolean whether to shut down worker, if started.
|
|
|
|
*/
|
|
|
|
private static bool $shutdownNow = false;
|
2020-07-11 20:01:54 +02:00
|
|
|
/**
|
|
|
|
* IPC server.
|
|
|
|
*/
|
2020-09-25 19:17:16 +02:00
|
|
|
protected IpcServer $server;
|
|
|
|
/**
|
|
|
|
* Callback IPC server.
|
|
|
|
*/
|
|
|
|
private ServerCallback $callback;
|
2020-07-11 20:01:54 +02:00
|
|
|
/**
|
|
|
|
* Set IPC path.
|
|
|
|
*
|
2020-09-25 19:17:16 +02:00
|
|
|
* @param SessionPaths $session Session
|
2020-07-11 20:01:54 +02:00
|
|
|
*/
|
2020-09-25 19:17:16 +02:00
|
|
|
public function setIpcPath(SessionPaths $session): void
|
2020-07-11 20:01:54 +02:00
|
|
|
{
|
2022-12-30 20:24:13 +01:00
|
|
|
self::$shutdownDeferred ??= new DeferredFuture;
|
2020-09-25 19:17:16 +02:00
|
|
|
$this->server = new IpcServer($session->getIpcPath());
|
|
|
|
$this->callback = new ServerCallback($this->API);
|
|
|
|
$this->callback->setIpcPath($session);
|
2020-07-11 20:01:54 +02:00
|
|
|
}
|
2020-09-25 20:49:51 +02:00
|
|
|
public function start(): bool
|
|
|
|
{
|
|
|
|
return $this instanceof ServerCallback ? parent::start() : $this->callback->start() && parent::start();
|
|
|
|
}
|
2020-07-11 20:01:54 +02:00
|
|
|
/**
|
|
|
|
* Start IPC server in background.
|
|
|
|
*
|
2020-09-24 11:45:20 +02:00
|
|
|
* @param SessionPaths $session Session path
|
2020-07-11 20:01:54 +02:00
|
|
|
*/
|
2022-12-30 21:43:58 +01:00
|
|
|
public static function startMe(SessionPaths $session): Future
|
2020-07-11 20:01:54 +02:00
|
|
|
{
|
2020-09-25 10:26:10 +02:00
|
|
|
$id = Tools::randomInt(2000000000);
|
2021-04-09 17:21:37 +02:00
|
|
|
$started = false;
|
2020-07-11 20:01:54 +02:00
|
|
|
try {
|
|
|
|
Logger::log("Starting IPC server $session (process)");
|
2023-01-15 16:12:12 +01:00
|
|
|
ProcessRunner::start((string) $session, $id);
|
2021-04-09 17:21:37 +02:00
|
|
|
$started = true;
|
2023-01-15 16:12:12 +01:00
|
|
|
WebRunner::start((string) $session, $id);
|
|
|
|
return async(self::monitor(...), $session, $id, $started);
|
2022-12-30 19:21:36 +01:00
|
|
|
} catch (Throwable $e) {
|
2020-07-11 20:01:54 +02:00
|
|
|
Logger::log($e);
|
|
|
|
}
|
2020-09-23 00:57:49 +02:00
|
|
|
try {
|
|
|
|
Logger::log("Starting IPC server $session (web)");
|
2023-01-20 15:38:55 +01:00
|
|
|
if (WebRunner::start((string) $session, $id)) {
|
|
|
|
$started = true;
|
|
|
|
}
|
2022-12-30 19:21:36 +01:00
|
|
|
} catch (Throwable $e) {
|
2020-09-23 00:57:49 +02:00
|
|
|
Logger::log($e);
|
|
|
|
}
|
2023-01-15 16:12:12 +01:00
|
|
|
return async(self::monitor(...), $session, $id, $started);
|
2020-09-24 11:45:20 +02:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Monitor session.
|
|
|
|
*/
|
2023-01-15 16:12:12 +01:00
|
|
|
private static function monitor(SessionPaths $session, int $id, bool $started): bool|Throwable
|
2020-09-24 11:45:20 +02:00
|
|
|
{
|
2021-04-09 17:21:37 +02:00
|
|
|
if (!$started) {
|
|
|
|
Logger::log("It looks like the server couldn't be started, trying to connect anyway...");
|
|
|
|
}
|
|
|
|
$count = 0;
|
2020-09-24 11:45:20 +02:00
|
|
|
while (true) {
|
2022-12-30 22:31:20 +01:00
|
|
|
$state = $session->getIpcState();
|
2020-09-24 11:45:20 +02:00
|
|
|
if ($state && $state->getStartupId() === $id) {
|
|
|
|
if ($e = $state->getException()) {
|
|
|
|
Logger::log("IPC server got exception $e");
|
|
|
|
return $e;
|
|
|
|
}
|
2023-01-04 15:13:55 +01:00
|
|
|
Logger::log('IPC server started successfully!');
|
2020-09-24 11:45:20 +02:00
|
|
|
return true;
|
2021-04-09 17:21:37 +02:00
|
|
|
} elseif (!$started && $count > 0 && $count > 2*($state ? 3 : 1)) {
|
|
|
|
return new Exception("We couldn't start the IPC server, please check the logs!");
|
2020-09-24 11:45:20 +02:00
|
|
|
}
|
2023-01-15 16:12:12 +01:00
|
|
|
delay(0.5);
|
2021-04-09 17:21:37 +02:00
|
|
|
$count++;
|
2020-09-24 11:45:20 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Wait for shutdown.
|
|
|
|
*/
|
2023-01-04 12:12:44 +01:00
|
|
|
public static function waitShutdown(): void
|
2020-09-24 11:45:20 +02:00
|
|
|
{
|
2021-04-23 17:41:08 +02:00
|
|
|
if (self::$shutdownNow) {
|
2023-01-04 12:12:44 +01:00
|
|
|
return;
|
2021-04-23 17:41:08 +02:00
|
|
|
}
|
2022-12-30 20:24:13 +01:00
|
|
|
self::$shutdownDeferred ??= new DeferredFuture;
|
2023-01-04 12:12:44 +01:00
|
|
|
self::$shutdownDeferred->getFuture()->await();
|
2020-07-11 20:01:54 +02:00
|
|
|
}
|
2021-12-08 20:40:38 +01:00
|
|
|
/**
|
2021-12-09 13:25:14 +01:00
|
|
|
* Shutdown.
|
2021-12-08 20:40:38 +01:00
|
|
|
*/
|
2023-01-24 14:28:49 +01:00
|
|
|
final public function stop(): bool
|
2021-12-08 20:40:38 +01:00
|
|
|
{
|
2023-01-24 14:28:49 +01:00
|
|
|
$this->server->close();
|
|
|
|
if (!$this instanceof ServerCallback) {
|
|
|
|
$this->callback->server->close();
|
|
|
|
}
|
2021-12-08 20:40:38 +01:00
|
|
|
if (self::$shutdownDeferred) {
|
|
|
|
self::$shutdownNow = true;
|
|
|
|
$deferred = self::$shutdownDeferred;
|
|
|
|
self::$shutdownDeferred = null;
|
2022-12-30 21:43:58 +01:00
|
|
|
$deferred->complete();
|
2021-12-08 20:40:38 +01:00
|
|
|
}
|
2023-01-24 14:28:49 +01:00
|
|
|
return true;
|
2021-12-08 20:40:38 +01:00
|
|
|
}
|
2020-07-11 20:01:54 +02:00
|
|
|
/**
|
|
|
|
* Main loop.
|
|
|
|
*/
|
2023-01-24 14:28:49 +01:00
|
|
|
protected function loop(): ?float
|
2020-07-11 20:01:54 +02:00
|
|
|
{
|
2023-01-24 14:28:49 +01:00
|
|
|
while ($socket = $this->server->accept()) {
|
2023-01-21 21:21:35 +01:00
|
|
|
EventLoop::queue($this->clientLoop(...), $socket);
|
2020-07-11 20:01:54 +02:00
|
|
|
}
|
|
|
|
$this->server->close();
|
2020-09-25 21:21:17 +02:00
|
|
|
if (isset($this->callback)) {
|
2023-01-24 14:28:49 +01:00
|
|
|
$this->callback->server->close();
|
2020-09-25 21:21:17 +02:00
|
|
|
}
|
2023-01-24 14:28:49 +01:00
|
|
|
return self::STOP;
|
2020-07-11 20:01:54 +02:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Client handler loop.
|
|
|
|
*
|
|
|
|
* @param ChannelledSocket $socket Client
|
|
|
|
*/
|
2023-01-04 12:43:01 +01:00
|
|
|
protected function clientLoop(ChannelledSocket $socket): void
|
2020-07-11 20:01:54 +02:00
|
|
|
{
|
2023-01-04 15:13:55 +01:00
|
|
|
$this->API->logger('Accepted IPC client connection!');
|
2020-07-11 20:01:54 +02:00
|
|
|
|
|
|
|
$id = 0;
|
2020-09-24 11:45:20 +02:00
|
|
|
$payload = null;
|
2020-07-12 01:12:20 +02:00
|
|
|
try {
|
2022-12-30 22:31:20 +01:00
|
|
|
while ($payload = $socket->receive()) {
|
2023-01-21 21:21:35 +01:00
|
|
|
EventLoop::queue($this->clientRequest(...), $socket, $id++, $payload);
|
2020-07-12 01:12:20 +02:00
|
|
|
}
|
2022-12-30 19:21:36 +01:00
|
|
|
} catch (Throwable $e) {
|
2021-04-01 22:43:17 +02:00
|
|
|
Logger::log("Exception in IPC connection: $e");
|
2020-09-24 11:45:20 +02:00
|
|
|
} finally {
|
2021-04-09 17:21:37 +02:00
|
|
|
try {
|
2022-12-30 22:31:20 +01:00
|
|
|
$socket->disconnect();
|
2022-12-30 19:21:36 +01:00
|
|
|
} catch (Throwable $e) {
|
2021-04-09 17:21:37 +02:00
|
|
|
}
|
2020-09-24 21:13:58 +02:00
|
|
|
if ($payload === self::SHUTDOWN) {
|
2023-01-24 14:28:49 +01:00
|
|
|
Shutdown::removeCallback('restarter');
|
|
|
|
$this->stop();
|
2020-09-24 21:13:58 +02:00
|
|
|
}
|
2020-07-12 01:27:26 +02:00
|
|
|
}
|
2020-07-11 20:01:54 +02:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Handle client request.
|
|
|
|
*
|
2021-04-09 17:21:37 +02:00
|
|
|
* @param ChannelledSocket $socket Socket
|
|
|
|
* @param array{0: string, 1: array|Wrapper} $payload Payload
|
2020-07-11 20:01:54 +02:00
|
|
|
*/
|
2023-01-03 22:07:58 +01:00
|
|
|
private function clientRequest(ChannelledSocket $socket, int $id, array $payload): void
|
2020-07-11 20:01:54 +02:00
|
|
|
{
|
|
|
|
try {
|
2023-01-11 18:47:27 +01:00
|
|
|
$this->API->waitForInit();
|
2020-09-25 19:17:16 +02:00
|
|
|
if ($payload[1] instanceof Wrapper) {
|
2020-09-25 20:49:51 +02:00
|
|
|
$wrapper = $payload[1];
|
|
|
|
$payload[1] = $this->callback->unwrap($wrapper);
|
2020-09-25 19:17:16 +02:00
|
|
|
}
|
2020-07-11 20:01:54 +02:00
|
|
|
$result = $this->API->{$payload[0]}(...$payload[1]);
|
2022-12-30 19:21:36 +01:00
|
|
|
} catch (Throwable $e) {
|
2020-09-24 23:25:54 +02:00
|
|
|
$this->API->logger("Got error while calling IPC method: $e", Logger::ERROR);
|
2020-07-11 20:01:54 +02:00
|
|
|
$result = new ExitFailure($e);
|
2020-09-25 20:49:51 +02:00
|
|
|
} finally {
|
|
|
|
if (isset($wrapper)) {
|
2021-04-09 17:21:37 +02:00
|
|
|
try {
|
2022-12-30 22:31:20 +01:00
|
|
|
$wrapper->disconnect();
|
2022-12-30 19:21:36 +01:00
|
|
|
} catch (Throwable $e) {
|
2021-04-09 17:21:37 +02:00
|
|
|
}
|
2020-09-25 20:49:51 +02:00
|
|
|
}
|
2020-07-11 20:01:54 +02:00
|
|
|
}
|
|
|
|
try {
|
2022-12-30 22:31:20 +01:00
|
|
|
$socket->send([$id, $result]);
|
2022-12-30 19:21:36 +01:00
|
|
|
} catch (Throwable $e) {
|
2022-11-16 17:17:37 +01:00
|
|
|
$this->API->logger("Got error while trying to send result of {$payload[0]}: $e", Logger::ERROR);
|
2020-07-11 20:01:54 +02:00
|
|
|
try {
|
2022-12-30 22:31:20 +01:00
|
|
|
$socket->send([$id, new ExitFailure($e)]);
|
2022-12-30 19:21:36 +01:00
|
|
|
} catch (Throwable $e) {
|
2022-11-16 17:17:37 +01:00
|
|
|
$this->API->logger("Got error while trying to send error of error of {$payload[0]}: $e", Logger::ERROR);
|
2020-07-11 20:01:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Get the name of the loop.
|
|
|
|
*/
|
|
|
|
public function __toString(): string
|
|
|
|
{
|
2023-01-04 15:13:55 +01:00
|
|
|
return 'IPC server';
|
2020-07-11 20:01:54 +02:00
|
|
|
}
|
|
|
|
}
|