1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-12-02 23:27:46 +01:00
MadelineProto/src/Ipc/Server.php

277 lines
8.6 KiB
PHP
Raw Normal View History

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;
2023-09-06 17:45:58 +02:00
use Amp\CompositeException;
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;
use danog\MadelineProto\Shutdown;
2020-07-11 20:01:54 +02:00
use danog\MadelineProto\Tools;
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;
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
}
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.
*
2023-09-06 21:12:19 +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
{
$id = Tools::randomInt(2000000000);
2021-04-09 17:21:37 +02:00
$started = false;
2023-09-06 17:45:58 +02:00
$e = null;
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);
2023-09-06 18:39:48 +02:00
return async(self::monitor(...), $session, $id, $started, null);
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;
}
2023-09-06 17:45:58 +02:00
} catch (Throwable $e2) {
Logger::log($e2);
if ($e) {
$e = new CompositeException([$e, $e2]);
} else {
$e = $e2;
}
2020-09-23 00:57:49 +02:00
}
2023-09-06 17:45:58 +02:00
return async(self::monitor(...), $session, $id, $started, $e);
2020-09-24 11:45:20 +02:00
}
/**
* Monitor session.
*/
2023-09-06 17:45:58 +02:00
private static function monitor(SessionPaths $session, int $id, bool $started, ?\Throwable $e): 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) {
$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)) {
2023-09-06 17:45:58 +02:00
return new Exception("We couldn't start the IPC server, please check the logs!", previous: $e);
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()) {
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-08-27 11:57:49 +02:00
$this->API->waitForInit();
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 {
while ($payload = $socket->receive()) {
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 {
EventLoop::queue(function () use ($socket, $payload): void {
try {
$socket->disconnect();
} catch (Throwable $e) {
Logger::log("Exception during shutdown in IPC connection: $e");
}
if ($payload === self::SHUTDOWN) {
Shutdown::removeCallback('restarter');
$this->stop();
}
});
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 {
2020-09-25 19:17:16 +02:00
if ($payload[1] instanceof Wrapper) {
$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);
} finally {
if (isset($wrapper)) {
EventLoop::queue(function () use ($wrapper): void {
try {
$wrapper->disconnect();
} catch (Throwable $e) {
Logger::log("Exception during shutdown in IPC connection: $e");
}
});
}
2020-07-11 20:01:54 +02:00
}
try {
$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 {
$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
}
}