|
48 | 48 | use Doctrine\DBAL\Result; |
49 | 49 | use Doctrine\DBAL\Schema\Schema; |
50 | 50 | use Doctrine\DBAL\Statement; |
| 51 | +use OC\DB\Middleware\ConnectionActivityNotifier; |
51 | 52 | use OC\DB\QueryBuilder\QueryBuilder; |
52 | 53 | use OC\SystemConfig; |
53 | 54 | use OCP\DB\QueryBuilder\IQueryBuilder; |
@@ -86,6 +87,8 @@ class Connection extends PrimaryReadReplicaConnection { |
86 | 87 |
|
87 | 88 | /** @var DbDataCollector|null */ |
88 | 89 | protected $dbDataCollector = null; |
| 90 | + /** Seconds the connection may sit idle before the next use re-verifies connectivity */ |
| 91 | + private const CONNECTION_CHECK_INTERVAL = 30; |
89 | 92 | private array $lastConnectionCheck = []; |
90 | 93 |
|
91 | 94 | protected ?float $transactionActiveSince = null; |
@@ -122,6 +125,13 @@ public function __construct( |
122 | 125 | parent::__construct($params, $driver, $config, $eventManager); |
123 | 126 | $this->adapter = new $params['adapter']($this); |
124 | 127 | $this->tablePrefix = $params['tablePrefix']; |
| 128 | + $activityNotifier = $params['activity_notifier'] ?? null; |
| 129 | + if ($activityNotifier instanceof ConnectionActivityNotifier) { |
| 130 | + // first-class callable syntax requires PHP 8.1, this branch still supports 8.0 |
| 131 | + $activityNotifier->setListener(function (): void { |
| 132 | + $this->refreshLastConnectionCheck(); |
| 133 | + }); |
| 134 | + } |
125 | 135 |
|
126 | 136 | $this->systemConfig = \OC::$server->getSystemConfig(); |
127 | 137 | $this->clock = Server::get(ClockInterface::class); |
@@ -160,7 +170,7 @@ public function connect($connectionName = null) { |
160 | 170 | $status = parent::connect(); |
161 | 171 | $eventLogger->end('connect:db'); |
162 | 172 |
|
163 | | - $this->lastConnectionCheck[$this->getConnectionName()] = time(); |
| 173 | + $this->refreshLastConnectionCheck(); |
164 | 174 |
|
165 | 175 | return $status; |
166 | 176 | } catch (Exception $e) { |
@@ -786,21 +796,32 @@ public function rollBack() { |
786 | 796 | private function reconnectIfNeeded(): void { |
787 | 797 | if ( |
788 | 798 | !isset($this->lastConnectionCheck[$this->getConnectionName()]) || |
789 | | - time() <= $this->lastConnectionCheck[$this->getConnectionName()] + 30 || |
| 799 | + time() <= $this->lastConnectionCheck[$this->getConnectionName()] + self::CONNECTION_CHECK_INTERVAL || |
790 | 800 | $this->isTransactionActive() |
791 | 801 | ) { |
792 | 802 | return; |
793 | 803 | } |
794 | 804 |
|
795 | 805 | try { |
796 | 806 | $this->_conn->query($this->getDriver()->getDatabasePlatform()->getDummySelectSQL()); |
797 | | - $this->lastConnectionCheck[$this->getConnectionName()] = time(); |
| 807 | + $this->refreshLastConnectionCheck(); |
798 | 808 | } catch (ConnectionLost|\Exception $e) { |
799 | 809 | $this->logger->warning('Exception during connectivity check, closing and reconnecting', ['exception' => $e]); |
800 | 810 | $this->close(); |
801 | 811 | } |
802 | 812 | } |
803 | 813 |
|
| 814 | + /** |
| 815 | + * A successful round trip proves the connection is alive: pushing the idle |
| 816 | + * timer forward keeps the connectivity probe of reconnectIfNeeded() from |
| 817 | + * firing between adjacent operations, where its query would reset the |
| 818 | + * driver level last insert id on MySQL. Invoked for every driver level |
| 819 | + * execution via the ConnectionActivityMiddleware. |
| 820 | + */ |
| 821 | + private function refreshLastConnectionCheck(): void { |
| 822 | + $this->lastConnectionCheck[$this->getConnectionName()] = time(); |
| 823 | + } |
| 824 | + |
804 | 825 | private function getConnectionName(): string { |
805 | 826 | return $this->isConnectedToPrimary() ? 'primary' : 'replica'; |
806 | 827 | } |
|
0 commit comments