Database.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. declare(strict_types=1);
  7. namespace Magento\Framework\Lock\Backend;
  8. use Magento\Framework\App\DeploymentConfig;
  9. use Magento\Framework\App\ResourceConnection;
  10. use Magento\Framework\Config\ConfigOptionsListConstants;
  11. use Magento\Framework\Exception\AlreadyExistsException;
  12. use Magento\Framework\Exception\InputException;
  13. use Magento\Framework\Phrase;
  14. /**
  15. * Implementation of the lock manager on the basis of MySQL.
  16. */
  17. class Database implements \Magento\Framework\Lock\LockManagerInterface
  18. {
  19. /**
  20. * @var ResourceConnection
  21. */
  22. private $resource;
  23. /**
  24. * @var DeploymentConfig
  25. */
  26. private $deploymentConfig;
  27. /**
  28. * @var string Lock prefix
  29. */
  30. private $prefix;
  31. /**
  32. * @var string|false Holds current lock name if set, otherwise false
  33. */
  34. private $currentLock = false;
  35. /**
  36. * @param ResourceConnection $resource
  37. * @param DeploymentConfig $deploymentConfig
  38. * @param string|null $prefix
  39. */
  40. public function __construct(
  41. ResourceConnection $resource,
  42. DeploymentConfig $deploymentConfig,
  43. string $prefix = null
  44. ) {
  45. $this->resource = $resource;
  46. $this->deploymentConfig = $deploymentConfig;
  47. $this->prefix = $prefix;
  48. }
  49. /**
  50. * Sets a lock for name
  51. *
  52. * @param string $name lock name
  53. * @param int $timeout How long to wait lock acquisition in seconds, negative value means infinite timeout
  54. * @return bool
  55. * @throws InputException
  56. * @throws AlreadyExistsException
  57. * @throws \Zend_Db_Statement_Exception
  58. */
  59. public function lock(string $name, int $timeout = -1): bool
  60. {
  61. if (!$this->deploymentConfig->isDbAvailable()) {
  62. return true;
  63. };
  64. $name = $this->addPrefix($name);
  65. /**
  66. * Before MySQL 5.7.5, only a single simultaneous lock per connection can be acquired.
  67. * This limitation can be removed once MySQL minimum requirement has been raised,
  68. * currently we support MySQL 5.6 way only.
  69. */
  70. if ($this->currentLock) {
  71. throw new AlreadyExistsException(
  72. new Phrase(
  73. 'Current connection is already holding lock for %1, only single lock allowed',
  74. [$this->currentLock]
  75. )
  76. );
  77. }
  78. $result = (bool)$this->resource->getConnection()->query(
  79. "SELECT GET_LOCK(?, ?);",
  80. [(string)$name, (int)$timeout]
  81. )->fetchColumn();
  82. if ($result === true) {
  83. $this->currentLock = $name;
  84. }
  85. return $result;
  86. }
  87. /**
  88. * Releases a lock for name
  89. *
  90. * @param string $name lock name
  91. * @return bool
  92. * @throws InputException
  93. * @throws \Zend_Db_Statement_Exception
  94. */
  95. public function unlock(string $name): bool
  96. {
  97. if (!$this->deploymentConfig->isDbAvailable()) {
  98. return true;
  99. };
  100. $name = $this->addPrefix($name);
  101. $result = (bool)$this->resource->getConnection()->query(
  102. "SELECT RELEASE_LOCK(?);",
  103. [(string)$name]
  104. )->fetchColumn();
  105. if ($result === true) {
  106. $this->currentLock = false;
  107. }
  108. return $result;
  109. }
  110. /**
  111. * Tests of lock is set for name
  112. *
  113. * @param string $name lock name
  114. * @return bool
  115. * @throws InputException
  116. * @throws \Zend_Db_Statement_Exception
  117. */
  118. public function isLocked(string $name): bool
  119. {
  120. if (!$this->deploymentConfig->isDbAvailable()) {
  121. return false;
  122. };
  123. $name = $this->addPrefix($name);
  124. return (bool)$this->resource->getConnection()->query(
  125. "SELECT IS_USED_LOCK(?);",
  126. [(string)$name]
  127. )->fetchColumn();
  128. }
  129. /**
  130. * Adds prefix and checks for max length of lock name
  131. *
  132. * Limited to 64 characters in MySQL.
  133. *
  134. * @param string $name
  135. * @return string
  136. * @throws InputException
  137. */
  138. private function addPrefix(string $name): string
  139. {
  140. $name = $this->getPrefix() . '|' . $name;
  141. if (strlen($name) > 64) {
  142. throw new InputException(new Phrase('Lock name too long: %1...', [substr($name, 0, 64)]));
  143. }
  144. return $name;
  145. }
  146. /**
  147. * Get installation specific lock prefix to avoid lock conflicts
  148. *
  149. * @return string lock prefix
  150. */
  151. private function getPrefix(): string
  152. {
  153. if ($this->prefix === null) {
  154. $this->prefix = (string)$this->deploymentConfig->get(
  155. ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT
  156. . '/'
  157. . ConfigOptionsListConstants::KEY_NAME,
  158. ''
  159. );
  160. }
  161. return $this->prefix;
  162. }
  163. }