From c554bda966987fca8b1174218a8c7568e1fe7a74 Mon Sep 17 00:00:00 2001 From: Alexander Pankratov Date: Sat, 2 Sep 2023 23:45:01 +0200 Subject: [PATCH] Feat: group requests in bulks. --- .env.docker.example | 6 + .env.example | 6 + composer.lock | 146 +++++++++++----------- config.php | 3 +- src/Controllers/AbstractApiController.php | 5 +- src/Controllers/ApiController.php | 50 +++++++- 6 files changed, 139 insertions(+), 77 deletions(-) diff --git a/.env.docker.example b/.env.docker.example index 22c8d79..5239205 100644 --- a/.env.docker.example +++ b/.env.docker.example @@ -8,6 +8,12 @@ SERVER_PORT=9503 MEMORY_LIMIT=256M TIMEZONE=UTC +# REQUEST WILL BE GROUPED INTO BULKS AND EXECUTED AS SINGLE BULK +# VALUE IN SECOND +# DECREASE TO REDUCE LATENCY, INCREASE TO REDUCE LOAD ON SERVER. +# SET to 0 to DISABLE +REQUESTS_BULK_INTERVAL=0.5 + # List of allowed clients. Separate with comma. # Leave blanc, to allow requests from all IP (THIS WILL MAKE API UNSECURE!) # To recieve requests from internet also need to (THIS WILL MAKE API UNSECURE!): diff --git a/.env.example b/.env.example index 19fd09b..7a67e77 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,12 @@ SERVER_PORT=9503 MEMORY_LIMIT=256M TIMEZONE=UTC +# REQUEST WILL BE GROUPED INTO BULKS AND EXECUTED AS SINGLE BULK +# VALUE IN SECOND +# DECREASE TO REDUCE LATENCY, INCREASE TO REDUCE LOAD ON SERVER. +# SET to 0 to DISABLE +REQUESTS_BULK_INTERVAL=0.5 + # List of allowed clients. Separate with comma. # Leave blanc, to allow requests from all IP (THIS WILL MAKE API UNSECURE!) IP_WHITELIST=127.0.0.1 diff --git a/composer.lock b/composer.lock index 1057f66..bcbe6c5 100644 --- a/composer.lock +++ b/composer.lock @@ -89,16 +89,16 @@ }, { "name": "amphp/byte-stream", - "version": "v2.0.1", + "version": "v2.0.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "7e7a77579f3e90c6fbd56e49628e6ace02d8f88a" + "reference": "408a3b4fc4f4c7604575dc8704f18c1bd91c3ceb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/7e7a77579f3e90c6fbd56e49628e6ace02d8f88a", - "reference": "7e7a77579f3e90c6fbd56e49628e6ace02d8f88a", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/408a3b4fc4f4c7604575dc8704f18c1bd91c3ceb", + "reference": "408a3b4fc4f4c7604575dc8704f18c1bd91c3ceb", "shasum": "" }, "require": { @@ -119,7 +119,8 @@ "type": "library", "autoload": { "files": [ - "src/functions.php" + "src/functions.php", + "src/Internal/functions.php" ], "psr-4": { "Amp\\ByteStream\\": "src" @@ -151,7 +152,7 @@ ], "support": { "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v2.0.1" + "source": "https://github.com/amphp/byte-stream/tree/v2.0.2" }, "funding": [ { @@ -159,7 +160,7 @@ "type": "github" } ], - "time": "2023-02-03T04:06:20+00:00" + "time": "2023-09-01T04:41:26+00:00" }, { "name": "amphp/cache", @@ -474,28 +475,28 @@ }, { "name": "amphp/http", - "version": "v2.0.0", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/amphp/http.git", - "reference": "f8bb94a2b079aa8e30ad78b5374df787a2122dfe" + "reference": "9f3500bef4bb15cf41987f21136539c0a06555a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/http/zipball/f8bb94a2b079aa8e30ad78b5374df787a2122dfe", - "reference": "f8bb94a2b079aa8e30ad78b5374df787a2122dfe", + "url": "https://api.github.com/repos/amphp/http/zipball/9f3500bef4bb15cf41987f21136539c0a06555a3", + "reference": "9f3500bef4bb15cf41987f21136539c0a06555a3", "shasum": "" }, "require": { "amphp/hpack": "^3", "amphp/parser": "^1.1", - "league/uri-components": "^2.4", + "league/uri-components": "^2.4.2 | ^7.1", "php": ">=8.1", - "psr/http-message": "^1 || ^2" + "psr/http-message": "^1 | ^2" }, "require-dev": { "amphp/php-cs-fixer-config": "^2", - "league/uri": "^6.8", + "league/uri": "^6.8 | ^7.1", "phpunit/phpunit": "^9", "psalm/phar": "^5.4" }, @@ -526,7 +527,7 @@ "description": "Basic HTTP primitives which can be shared by servers and clients.", "support": { "issues": "https://github.com/amphp/http/issues", - "source": "https://github.com/amphp/http/tree/v2.0.0" + "source": "https://github.com/amphp/http/tree/v2.1.0" }, "funding": [ { @@ -534,20 +535,20 @@ "type": "github" } ], - "time": "2023-04-12T19:20:53+00:00" + "time": "2023-08-22T19:50:46+00:00" }, { "name": "amphp/http-client", - "version": "v5.0.0-beta.13", + "version": "v5.0.0-beta.14", "source": { "type": "git", "url": "https://github.com/amphp/http-client.git", - "reference": "fd5b005d9799aab0e67764cf889586660516cd14" + "reference": "a9f2bebe03baaac7fa029b5d01bb66ab4a9c5b5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/http-client/zipball/fd5b005d9799aab0e67764cf889586660516cd14", - "reference": "fd5b005d9799aab0e67764cf889586660516cd14", + "url": "https://api.github.com/repos/amphp/http-client/zipball/a9f2bebe03baaac7fa029b5d01bb66ab4a9c5b5b", + "reference": "a9f2bebe03baaac7fa029b5d01bb66ab4a9c5b5b", "shasum": "" }, "require": { @@ -588,6 +589,7 @@ }, "autoload": { "files": [ + "src/functions.php", "src/Internal/functions.php" ], "psr-4": { @@ -624,7 +626,7 @@ ], "support": { "issues": "https://github.com/amphp/http-client/issues", - "source": "https://github.com/amphp/http-client/tree/v5.0.0-beta.13" + "source": "https://github.com/amphp/http-client/tree/v5.0.0-beta.14" }, "funding": [ { @@ -632,7 +634,7 @@ "type": "github" } ], - "time": "2023-08-15T20:25:04+00:00" + "time": "2023-08-23T20:15:15+00:00" }, { "name": "amphp/http-client-cookies", @@ -717,12 +719,12 @@ "source": { "type": "git", "url": "https://github.com/amphp/http-server.git", - "reference": "4e6e8b1e23c55ff8b1377f6eaccda3f49894723e" + "reference": "5057e59b7119314f2f1f5c7740daf3bcbbd1f163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/http-server/zipball/4e6e8b1e23c55ff8b1377f6eaccda3f49894723e", - "reference": "4e6e8b1e23c55ff8b1377f6eaccda3f49894723e", + "url": "https://api.github.com/repos/amphp/http-server/zipball/5057e59b7119314f2f1f5c7740daf3bcbbd1f163", + "reference": "5057e59b7119314f2f1f5c7740daf3bcbbd1f163", "shasum": "" }, "require": { @@ -734,11 +736,11 @@ "amphp/pipeline": "^1", "amphp/socket": "^2.1", "amphp/sync": "^2", - "league/uri": "^6", - "league/uri-interfaces": "^2.3", + "league/uri": "^6.8 | ^7.1", + "league/uri-interfaces": "^2.3 | ^7.1", "php": ">=8.1", - "psr/http-message": "^1", - "psr/log": "^1|^2|^3", + "psr/http-message": "^1 | ^2", + "psr/log": "^1 | ^2 | ^3", "revolt/event-loop": "^1" }, "require-dev": { @@ -746,7 +748,7 @@ "amphp/log": "^2", "amphp/php-cs-fixer-config": "^2", "amphp/phpunit-util": "^3", - "league/uri-components": "^2", + "league/uri-components": "^2.4.2 | ^7.1", "monolog/monolog": "^3", "phpunit/phpunit": "^9", "psalm/phar": "^5.4" @@ -807,27 +809,27 @@ "type": "github" } ], - "time": "2023-08-20T19:03:40+00:00" + "time": "2023-09-01T05:04:07+00:00" }, { "name": "amphp/http-server-form-parser", - "version": "v2.0.0-beta.3", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/amphp/http-server-form-parser.git", - "reference": "42193795723ea72e950b74f0a9075bb794c542f0" + "reference": "6f160914a1cd9aaa104b589c0c590613600bfa70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/http-server-form-parser/zipball/42193795723ea72e950b74f0a9075bb794c542f0", - "reference": "42193795723ea72e950b74f0a9075bb794c542f0", + "url": "https://api.github.com/repos/amphp/http-server-form-parser/zipball/6f160914a1cd9aaa104b589c0c590613600bfa70", + "reference": "6f160914a1cd9aaa104b589c0c590613600bfa70", "shasum": "" }, "require": { "amphp/amp": "^3", "amphp/byte-stream": "^2", "amphp/http": "^2", - "amphp/http-server": "^3", + "amphp/http-server": "^3.2", "amphp/pipeline": "^1", "php": ">=8.1", "revolt/event-loop": "^1" @@ -869,19 +871,20 @@ "email": "aaron@trowski.com" } ], - "description": "A form parser for Amp's HTTP parser.", - "homepage": "https://github.com/amphp/http-server-form-parser", + "description": "An HTTP server plugin that simplifies form data handling. Effortlessly parse incoming form submissions and extracting its data.", + "homepage": "https://amphp.org/http-server-form-parser", "keywords": [ "amp", "amphp", "async", "form", "http", - "non-blocking" + "non-blocking", + "revolt" ], "support": { "issues": "https://github.com/amphp/http-server-form-parser/issues", - "source": "https://github.com/amphp/http-server-form-parser/tree/v2.0.0-beta.3" + "source": "https://github.com/amphp/http-server-form-parser/tree/v2.0.0" }, "funding": [ { @@ -889,7 +892,7 @@ "type": "github" } ], - "time": "2023-04-22T15:36:02+00:00" + "time": "2023-08-25T02:52:20+00:00" }, { "name": "amphp/http-server-router", @@ -1116,16 +1119,16 @@ }, { "name": "amphp/parallel", - "version": "v2.2.1", + "version": "v2.2.2", "source": { "type": "git", "url": "https://github.com/amphp/parallel.git", - "reference": "ba11031b8664134b13c150530ae041a75e631858" + "reference": "f54bb4090670397544f74e14a1e650bf2cfd853b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/parallel/zipball/ba11031b8664134b13c150530ae041a75e631858", - "reference": "ba11031b8664134b13c150530ae041a75e631858", + "url": "https://api.github.com/repos/amphp/parallel/zipball/f54bb4090670397544f74e14a1e650bf2cfd853b", + "reference": "f54bb4090670397544f74e14a1e650bf2cfd853b", "shasum": "" }, "require": { @@ -1187,7 +1190,7 @@ ], "support": { "issues": "https://github.com/amphp/parallel/issues", - "source": "https://github.com/amphp/parallel/tree/v2.2.1" + "source": "https://github.com/amphp/parallel/tree/v2.2.2" }, "funding": [ { @@ -1195,7 +1198,7 @@ "type": "github" } ], - "time": "2023-05-22T03:33:27+00:00" + "time": "2023-08-30T17:43:42+00:00" }, { "name": "amphp/parser", @@ -2515,12 +2518,12 @@ "source": { "type": "git", "url": "https://github.com/danog/MadelineProto.git", - "reference": "dc34a380b3dd936c0dfc753dc89aa7389d5ac377" + "reference": "546e9a0d23ad4f05a5e2b3446be7315d4e53b95b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/danog/MadelineProto/zipball/dc34a380b3dd936c0dfc753dc89aa7389d5ac377", - "reference": "dc34a380b3dd936c0dfc753dc89aa7389d5ac377", + "url": "https://api.github.com/repos/danog/MadelineProto/zipball/546e9a0d23ad4f05a5e2b3446be7315d4e53b95b", + "reference": "546e9a0d23ad4f05a5e2b3446be7315d4e53b95b", "shasum": "" }, "require": { @@ -2572,7 +2575,6 @@ "danog/phpdoc": "^0.1.7", "dg/bypass-finals": "dev-master", "ext-ctype": "*", - "leproxy/leproxy": "^0.2.2", "phpdocumentor/reflection-docblock": "dev-master", "phpdocumentor/type-resolver": "1.x-dev@dev", "phpunit/phpunit": "^9", @@ -2637,7 +2639,7 @@ "type": "github" } ], - "time": "2023-08-21T13:09:02+00:00" + "time": "2023-09-02T20:24:26+00:00" }, { "name": "danog/primemodule", @@ -2755,16 +2757,16 @@ }, { "name": "dasprid/enum", - "version": "1.0.4", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/DASPRiD/Enum.git", - "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f" + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", - "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", "shasum": "" }, "require": { @@ -2799,9 +2801,9 @@ ], "support": { "issues": "https://github.com/DASPRiD/Enum/issues", - "source": "https://github.com/DASPRiD/Enum/tree/1.0.4" + "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" }, - "time": "2023-03-01T18:44:03+00:00" + "time": "2023-08-25T16:18:39+00:00" }, { "name": "daverandom/libdns", @@ -3906,16 +3908,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -3930,7 +3932,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3968,7 +3970,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -3984,20 +3986,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -4012,7 +4014,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4051,7 +4053,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -4067,7 +4069,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "vlucas/phpdotenv", diff --git a/config.php b/config.php index 7979431..ca9a61c 100644 --- a/config.php +++ b/config.php @@ -15,7 +15,7 @@ $settings = [ ], 'logger' => [ // Logger settings 'type' => Logger::CALLABLE_LOGGER, // 0 - Logs disabled, 3 - echo logs. - 'extra' => LogObserver::class . '::log', + 'extra' => LogObserver::log(...), 'level' => (int)getenv('LOGGER_LEVEL'), // Logging level, available logging levels are: ULTRA_VERBOSE - 5, VERBOSE - 4 , NOTICE - 3, WARNING - 2, ERROR - 1, FATAL_ERROR - 0. ], 'rpc' => [ @@ -53,6 +53,7 @@ $settings = [ explode(',', (string)getenv('IP_WHITELIST')) ) ), + 'bulk_interval' => (float)getenv('REQUESTS_BULK_INTERVAL') ], 'health_check' => [ 'enabled' => (bool)filter_var((string)getenv('HEALTHCHECK_ENABLED'), FILTER_VALIDATE_BOOL), diff --git a/src/Controllers/AbstractApiController.php b/src/Controllers/AbstractApiController.php index 2920f9d..ba98cc0 100644 --- a/src/Controllers/AbstractApiController.php +++ b/src/Controllers/AbstractApiController.php @@ -4,7 +4,7 @@ namespace TelegramApiServer\Controllers; use Amp\Future; use Amp\Http\Server\FormParser\StreamedField; -use Amp\Http\Server\FormParser\StreamingParser; +use Amp\Http\Server\FormParser\StreamingFormParser; use Amp\Http\Server\Request; use Amp\Http\Server\RequestHandler\ClosureRequestHandler; use Amp\Http\Server\Response; @@ -18,6 +18,7 @@ use TelegramApiServer\MadelineProtoExtensions\ApiExtensions; use TelegramApiServer\MadelineProtoExtensions\SystemApiExtensions; use Throwable; use UnexpectedValueException; +use function Amp\delay; use function mb_strpos; abstract class AbstractApiController @@ -95,7 +96,7 @@ abstract class AbstractApiController switch (true) { case $contentType === 'application/x-www-form-urlencoded': case mb_strpos($contentType, 'multipart/form-data') !== false: - $form = (new StreamingParser())->parseForm($this->request); + $form = (new StreamingFormParser())->parseForm($this->request); $post = []; while ($form->continue()) { diff --git a/src/Controllers/ApiController.php b/src/Controllers/ApiController.php index c153f27..8bd7969 100644 --- a/src/Controllers/ApiController.php +++ b/src/Controllers/ApiController.php @@ -2,8 +2,18 @@ namespace TelegramApiServer\Controllers; +use Amp\Sync\LocalKeyedMutex; +use Amp\Sync\LocalMutex; +use Amp\Sync\StaticKeyMutex; +use Amp\Sync\SyncException; use Exception; +use Revolt\EventLoop; use TelegramApiServer\Client; +use TelegramApiServer\Config; +use TelegramApiServer\Logger; +use function Amp\async; +use function Amp\delay; +use function Amp\Future\awaitAll; class ApiController extends AbstractApiController { @@ -25,10 +35,46 @@ class ApiController extends AbstractApiController /** * @throws Exception */ - protected function callApi() + protected function callApi(): mixed { $madelineProto = Client::getInstance()->getSession($this->session); - return $this->callApiCommon($madelineProto); + $tick = Config::getInstance()->get('api.bulk_interval'); + + if (!$tick) { + return $this->callApiCommon($madelineProto); + } + + //GROUP REQUESTS IN BULKS + static $futures = []; + + $futures[] = $future = async($this->callApiCommon(...), $madelineProto); + delay($this->waitNextTick()); + + if ($futures) { + awaitAll($futures); + Logger::getInstance()->notice("Executed bulk requests:" . count($futures)); + $futures = []; + } + + return $future->await(); + } + + /** + * Sync threads execution via time ticks + * Need to enable madelineProto futures bulk execution + * @param float $tick interval of execution in seconds. + */ + protected function waitNextTick(float $tick = 0.5): float { + $tickMs = $tick * 1000; + $now = (int)(microtime(true) * 1000); + $currentTick = intdiv((int)(microtime(true) * 1000), $tickMs); + $nextTick = ($currentTick + 1); + $nextTickTime = $nextTick * $tickMs; + $wait = round(($nextTickTime - $now)/1000, 3); + + Logger::getInstance()->notice("Waiting $wait seconds before tick"); + + return $wait; } } \ No newline at end of file