|
25 | 25 | use Doctrine\DBAL\Result; |
26 | 26 | use Doctrine\DBAL\Schema\Schema; |
27 | 27 | use Doctrine\DBAL\Statement; |
| 28 | +use OC\DB\Middleware\ConnectionActivityNotifier; |
28 | 29 | use OC\DB\QueryBuilder\Partitioned\PartitionedQueryBuilder; |
29 | 30 | use OC\DB\QueryBuilder\Partitioned\PartitionSplit; |
30 | 31 | use OC\DB\QueryBuilder\QueryBuilder; |
@@ -63,6 +64,8 @@ class Connection extends PrimaryReadReplicaConnection { |
63 | 64 | protected int $queriesBuilt = 0; |
64 | 65 | protected int $queriesExecuted = 0; |
65 | 66 | protected ?DbDataCollector $dbDataCollector = null; |
| 67 | + /** Seconds the connection may sit idle before the next use re-verifies connectivity */ |
| 68 | + private const CONNECTION_CHECK_INTERVAL = 30; |
66 | 69 | private array $lastConnectionCheck = []; |
67 | 70 |
|
68 | 71 | protected ?float $transactionActiveSince = null; |
@@ -122,6 +125,10 @@ 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 | + $activityNotifier->setListener($this->refreshLastConnectionCheck(...)); |
| 131 | + } |
125 | 132 | $this->isShardingEnabled = isset($this->params['sharding']) && !empty($this->params['sharding']); |
126 | 133 |
|
127 | 134 | if ($this->isShardingEnabled) { |
@@ -221,7 +228,7 @@ public function connect($connectionName = null) { |
221 | 228 | $status = parent::connect(); |
222 | 229 | $eventLogger->end('connect:db'); |
223 | 230 |
|
224 | | - $this->lastConnectionCheck[$this->getConnectionName()] = time(); |
| 231 | + $this->refreshLastConnectionCheck(); |
225 | 232 |
|
226 | 233 | return $status; |
227 | 234 | } catch (Exception $e) { |
@@ -902,21 +909,32 @@ public function rollBack() { |
902 | 909 | private function reconnectIfNeeded(): void { |
903 | 910 | if ( |
904 | 911 | !isset($this->lastConnectionCheck[$this->getConnectionName()]) |
905 | | - || time() <= $this->lastConnectionCheck[$this->getConnectionName()] + 30 |
| 912 | + || time() <= $this->lastConnectionCheck[$this->getConnectionName()] + self::CONNECTION_CHECK_INTERVAL |
906 | 913 | || $this->isTransactionActive() |
907 | 914 | ) { |
908 | 915 | return; |
909 | 916 | } |
910 | 917 |
|
911 | 918 | try { |
912 | 919 | $this->_conn->query($this->getDriver()->getDatabasePlatform()->getDummySelectSQL()); |
913 | | - $this->lastConnectionCheck[$this->getConnectionName()] = time(); |
| 920 | + $this->refreshLastConnectionCheck(); |
914 | 921 | } catch (ConnectionLost|\Exception $e) { |
915 | 922 | $this->logger->warning('Exception during connectivity check, closing and reconnecting', ['exception' => $e]); |
916 | 923 | $this->close(); |
917 | 924 | } |
918 | 925 | } |
919 | 926 |
|
| 927 | + /** |
| 928 | + * A successful round trip proves the connection is alive: pushing the idle |
| 929 | + * timer forward keeps the connectivity probe of reconnectIfNeeded() from |
| 930 | + * firing between adjacent operations, where its query would reset the |
| 931 | + * driver level last insert id on MySQL. Invoked for every driver level |
| 932 | + * execution via the ConnectionActivityMiddleware. |
| 933 | + */ |
| 934 | + private function refreshLastConnectionCheck(): void { |
| 935 | + $this->lastConnectionCheck[$this->getConnectionName()] = time(); |
| 936 | + } |
| 937 | + |
920 | 938 | private function getConnectionName(): string { |
921 | 939 | return $this->isConnectedToPrimary() ? 'primary' : 'replica'; |
922 | 940 | } |
|
0 commit comments