Skip to content

Commit

Permalink
Add support for ephemeral filesystems, UNIX socket database connectio…
Browse files Browse the repository at this point in the history
…ns, fix revoltphp bug, fix updatePinnedMessages
  • Loading branch information
danog committed Nov 19, 2023
1 parent b26f6ec commit 57fa342
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 44 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"webmozart/assert": "^1.11",
"bacon/bacon-qr-code": "^2.0",
"nikic/php-parser": "^4.16",
"revolt/event-loop": "^1.0.4"
"revolt/event-loop": "^1.0.5"
},
"require-dev": {
"phpdocumentor/reflection-docblock": "dev-master",
Expand Down
2 changes: 1 addition & 1 deletion src/Db/DbPropertiesTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function initDb(MTProto $API, bool $reset = false): void
if ($reset) {
unset($this->{$property});
} else {
$table = ($type['global'] ?? false) ? ($API->isTestMode() ? 'test_' : 'prod_') : $prefix.'_';
$table = $prefix.'_';
$table .= $type['table'] ?? "{$className}_{$property}";
$promises[$property] = async(DbPropertiesFactory::get(...), $dbSettings, $table, $type, $this->{$property} ?? null);
}
Expand Down
37 changes: 28 additions & 9 deletions src/Db/Driver/Mysql.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,30 @@ final class Mysql
public static function getConnection(DatabaseMysql $settings): array
{
self::$mutex ??= new LocalKeyedMutex;
$dbKey = $settings->getKey();
$dbKey = $settings->getDbIdentifier();
$lock = self::$mutex->acquire($dbKey);

try {
if (!isset(self::$connections[$dbKey])) {
$config = MysqlConfig::fromString('host='.str_replace('tcp://', '', $settings->getUri()))
->withUser($settings->getUsername())
->withPassword($settings->getPassword())
->withDatabase($settings->getDatabase());
$host = str_replace(['tcp://', 'unix://'], '', $settings->getUri());
if ($host[0] === '/') {
$port = 0;
} else {
$host = explode(':', $host, 2);
if (\count($host) === 2) {
[$host, $port] = $host;
} else {
$host = $host[0];
$port = MysqlConfig::DEFAULT_PORT;
}
}
$config = new MysqlConfig(
host: $host,
port: (int) $port,
user: $settings->getUsername(),
password: $settings->getPassword(),
database: $settings->getDatabase()
);

self::createDb($config);

Expand All @@ -62,14 +77,18 @@ public static function getConnection(DatabaseMysql $settings): array

try {
$pdo = new PDO(
"mysql:host={$host};port={$port};charset=UTF8",
$host[0] === '/'
? "mysql:unix_socket={$host};charset=UTF8"
: "mysql:host={$host};port={$port};charset=UTF8",
$settings->getUsername(),
$settings->getPassword(),
);
} catch (PDOException) {
} catch (PDOException $e) {
$config = $config->withPassword(null);
$pdo = new PDO(
"mysql:host={$host};port={$port};charset=UTF8",
$host[0] === '/'
? "mysql:unix_socket={$host};charset=UTF8"
: "mysql:host={$host};port={$port};charset=UTF8",
$settings->getUsername(),
);
}
Expand Down Expand Up @@ -105,7 +124,7 @@ private static function createDb(MysqlConfig $config): void
}
$connection->close();
} catch (Throwable $e) {
Logger::log($e->getMessage(), Logger::ERROR);
Logger::log("An error occurred while trying to create the database: ".$e->getMessage(), Logger::ERROR);
}
}
}
25 changes: 20 additions & 5 deletions src/Db/Driver/Postgres.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,30 @@ final class Postgres
public static function getConnection(DatabasePostgres $settings): PostgresConnectionPool
{
self::$mutex ??= new LocalKeyedMutex;
$dbKey = $settings->getKey();
$dbKey = $settings->getDbIdentifier();
$lock = self::$mutex->acquire($dbKey);

try {
if (empty(self::$connections[$dbKey])) {
$config = PostgresConfig::fromString('host='.str_replace('tcp://', '', $settings->getUri()))
->withUser($settings->getUsername())
->withPassword($settings->getPassword())
->withDatabase($settings->getDatabase());
$host = str_replace(['tcp://', 'unix://'], '', $settings->getUri());
if ($host[0] === '/') {
$port = 0;
} else {
$host = explode(':', $host, 2);
if (\count($host) === 2) {
[$host, $port] = $host;
} else {
$host = $host[0];
$port = PostgresConfig::DEFAULT_PORT;
}
}
$config = new PostgresConfig(
host: $host,
port: (int) $port,
user: $settings->getUsername(),
password: $settings->getPassword(),
database: $settings->getDatabase()
);

self::createDb($config);
self::$connections[$dbKey] = new PostgresConnectionPool($config, $settings->getMaxConnections(), $settings->getIdleTimeout());
Expand Down
2 changes: 1 addition & 1 deletion src/Db/Driver/Redis.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ final class Redis
public static function getConnection(DatabaseRedis $settings): RedisClient
{
self::$mutex ??= new LocalKeyedMutex;
$dbKey = $settings->getKey();
$dbKey = $settings->getDbIdentifier();
$lock = self::$mutex->acquire($dbKey);

try {
Expand Down
11 changes: 8 additions & 3 deletions src/MTProto.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
use danog\MadelineProto\MTProtoTools\ReferenceDatabase;
use danog\MadelineProto\MTProtoTools\UpdateHandler;
use danog\MadelineProto\MTProtoTools\UpdatesState;
use danog\MadelineProto\Settings\Database\DriverDatabaseAbstract;
use danog\MadelineProto\Settings\TLSchema;
use danog\MadelineProto\TL\Conversion\BotAPI;
use danog\MadelineProto\TL\Conversion\BotAPIFiles;
Expand Down Expand Up @@ -562,9 +563,13 @@ public function getSessionName(): string
/** @internal */
public function getDbPrefix(): string
{
$prefix = $this->getSelf()['id'] ?? null;
if (!$prefix) {
$this->tmpDbPrefix ??= 'tmp_'.spl_object_id($this);
$prefix = null;
if ($this->settings->getDb() instanceof DriverDatabaseAbstract) {
$prefix = $this->settings->getDb()->getEphemeralFilesystemPrefix();
}
$prefix ??= $this->getSelf()['id'] ?? null;
if ($prefix === null) {
$this->tmpDbPrefix ??= 'tmp_'.hash('sha256', $this->getSessionName());
$prefix = $this->tmpDbPrefix;
}
return (string) $prefix;
Expand Down
1 change: 1 addition & 0 deletions src/MTProtoTools/PeerHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ public function getIdInternal(mixed $id): ?int
case 'updateBotChatInviteRequester':
case 'updatePendingJoinRequests':
case 'updateStory':
case 'updatePinnedMessages':
case 'dialog':
case 'dialogPeer':
case 'notifyPeer':
Expand Down
25 changes: 23 additions & 2 deletions src/Serialization.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use danog\MadelineProto\Db\DbPropertiesFactory;
use danog\MadelineProto\Db\DriverArray;
use danog\MadelineProto\Ipc\Server;
use danog\MadelineProto\Settings\Database\DriverDatabaseAbstract;
use danog\MadelineProto\Settings\DatabaseAbstract;
use Revolt\EventLoop;
use Throwable;
Expand Down Expand Up @@ -114,8 +115,28 @@ abstract class Serialization
public static function unserialize(SessionPaths $session, SettingsAbstract $settings, bool $forceFull = false): array
{
if (!exists($session->getSessionPath())) {
// No session exists yet, lock for when we create it
return [null, Tools::flock($session->getLockPath(), LOCK_EX, 1)];
if ($settings instanceof Settings) {
$dbSettings = $settings->getDb();
} else {
$dbSettings = $settings;
}
$unlock = Tools::flock($session->getLockPath(), LOCK_EX, 1);
$unserialized = null;
if ($dbSettings instanceof DriverDatabaseAbstract
&& $prefix = $dbSettings->getEphemeralFilesystemPrefix()
) {
$tableName = "{$prefix}_MTProto_session";
$unserialized = DbPropertiesFactory::get(
$dbSettings,
$tableName,
['enableCache' => false],
);
$unserialized = $unserialized['data'];
if (!$unserialized) {
$unserialized = null;
}
}
return [$unserialized, $unlock];
}

//Logger::log('Waiting for exclusive session lock...');
Expand Down
61 changes: 59 additions & 2 deletions src/Settings/Database/DriverDatabaseAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,66 @@ abstract class DriverDatabaseAbstract extends DatabaseAbstract
protected ?SerializerType $serializer = null;

/**
* Get DB key.
* If set, indicates that the filesystem is ephemeral, and thus session files will not be used to store persistent data.
*
* Must contain a unique string, used as prefix for database tables, different for every session.
* The prefix may be the same if different databases are used.
*
* This is useful when running MadelineProto inside docker containers without volumes, using just a database.
*
* Note that the session folder must still NEVER be deleted *if* MadelineProto is running,
* or else the session will be dropped from the database due to AUTH_KEY_DUPLICATED errors.
*
* Stopping the container and then deleting the session folder is 100% OK though.
*/
protected ?string $ephemeralFilesystemPrefix = null;

/**
* If set, indicates that the filesystem is ephemeral, and thus session files will not be used to store persistent data.
*
* Must contain a unique string, used as prefix for database tables, different for every session.
* The prefix may be the same if different databases are used.
*
* This is useful when running MadelineProto inside docker containers without volumes, using just a database.
*
* Note that the session folder must still NEVER be deleted *if* MadelineProto is running,
* or else the session will be dropped from the database due to AUTH_KEY_DUPLICATED errors.
*
* Stopping the container and then deleting the session folder is 100% OK though.
*/
public function getEphemeralFilesystemPrefix(): ?string
{
return $this->ephemeralFilesystemPrefix;
}

/**
* If set, indicates that the filesystem is ephemeral, and thus session files will not be used to store persistent data.
*
* Must contain a unique string, used as prefix for database tables, different for every session.
* The prefix may be the same if different databases are used.
*
* This is useful when running MadelineProto inside docker containers without volumes, using just a database.
*
* Note that the session folder must still NEVER be deleted *if* MadelineProto is running,
* or else the session will be dropped from the database due to AUTH_KEY_DUPLICATED errors.
*
* Stopping the container and then deleting the session folder is 100% OK though.
*
* @param ?string $ephemeralFilesystemPrefix The database prefix
*/
public function setEphemeralFilesystemPrefix(?string $ephemeralFilesystemPrefix): static
{
$this->ephemeralFilesystemPrefix = $ephemeralFilesystemPrefix;

return $this;
}

/**
* Get the DB's unique ID.
*
* @internal
*/
public function getKey(): string
public function getDbIdentifier(): string
{
$uri = parse_url($this->getUri());
$host = $uri['host'] ?? '';
Expand Down
10 changes: 5 additions & 5 deletions src/Settings/DatabaseAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function getEnableFileReferenceDb(): bool
*
* @param bool $enableFileReferenceDb Whether to enable the file reference database. If disabled, will break file downloads.
*/
public function setEnableFileReferenceDb(bool $enableFileReferenceDb): self
public function setEnableFileReferenceDb(bool $enableFileReferenceDb): static
{
$this->enableFileReferenceDb = $enableFileReferenceDb;

Expand All @@ -78,7 +78,7 @@ public function getEnableMinDb(): bool
*
* @param bool $enableMinDb Whether to enable the min database. If disabled, will break sendMessage (and other methods) in certain conditions.
*/
public function setEnableMinDb(bool $enableMinDb): self
public function setEnableMinDb(bool $enableMinDb): static
{
$this->enableMinDb = $enableMinDb;

Expand All @@ -98,7 +98,7 @@ public function getEnableUsernameDb(): bool
*
* @param bool $enableUsernameDb Whether to enable the username database. If disabled, will break sendMessage (and other methods) with usernames.
*/
public function setEnableUsernameDb(bool $enableUsernameDb): self
public function setEnableUsernameDb(bool $enableUsernameDb): static
{
$this->enableUsernameDb = $enableUsernameDb;

Expand All @@ -118,7 +118,7 @@ public function getEnableFullPeerDb(): bool
*
* @param bool $enableFullPeerDb Whether to enable the full peer info database. If disabled, will break getFullInfo.
*/
public function setEnableFullPeerDb(bool $enableFullPeerDb): self
public function setEnableFullPeerDb(bool $enableFullPeerDb): static
{
$this->enableFullPeerDb = $enableFullPeerDb;

Expand All @@ -138,7 +138,7 @@ public function getEnablePeerInfoDb(): bool
*
* @param bool $enablePeerInfoDb Whether to enable the peer info database. If disabled, will break getInfo.
*/
public function setEnablePeerInfoDb(bool $enablePeerInfoDb): self
public function setEnablePeerInfoDb(bool $enablePeerInfoDb): static
{
$this->enablePeerInfoDb = $enablePeerInfoDb;

Expand Down
15 changes: 0 additions & 15 deletions src/Shutdown.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@

namespace danog\MadelineProto;

use ReflectionClass;
use Revolt\EventLoop;
use Revolt\EventLoop\Internal\AbstractDriver;

use function Amp\ByteStream\getStdin;

/**
Expand Down Expand Up @@ -52,17 +48,6 @@ final class Shutdown
*/
private static function shutdown(): void
{
$obj = EventLoop::getSuspension();
$reflection = new ReflectionClass($obj);
$reflection->getProperty('pending')->setValue($obj, false);
$obj = EventLoop::getDriver();
$reflection = new ReflectionClass(AbstractDriver::class);
if (!$reflection->getProperty('callbackFiber')->isInitialized($obj)
|| $reflection->getProperty('callbackFiber')->getValue($obj)->isTerminated()
) {
$reflection->getMethod('createCallbackFiber')->invoke($obj);
}

foreach (self::$callbacks as $callback) {
$callback();
}
Expand Down

0 comments on commit 57fa342

Please sign in to comment.