· 7 years ago · Dec 21, 2018, 10:20 AM
1diff --git a/admin.php b/admin.php
2index 153a7f67d2..0a673fee91 100644
3--- a/admin.php
4+++ b/admin.php
5@@ -24,7 +24,23 @@ define('ACCOUNT_TYPE', 'admin');
6 try {
7 require(dirname(__FILE__) . '/init.php');
8
9+ /** @var \Tygh\Lock\Factory $lock_factory */
10+ $lock_factory = Tygh::$app['lock.factory'];
11+
12+ $lock = $lock_factory->createLock('test');
13+
14+ if (!$lock->acquire() && $lock->wait()) {
15+ fn_print_r('load from cache');
16+ } else {
17+ sleep(10);
18+ fn_print_r('generate cache');
19+ }
20+
21+ fn_print_die('done');
22+
23 fn_dispatch();
24+} catch (\Tygh\Lock\ExceededSuspendProcessesCountException $e) {
25+ fn_print_die('Please wait');
26 } catch (Tygh\Exceptions\AException $e) {
27 $e->output();
28 }
29diff --git a/app/Tygh/Lock/Factory.php b/app/Tygh/Lock/Factory.php
30index 4565fbc25c..88d0dcd946 100644
31--- a/app/Tygh/Lock/Factory.php
32+++ b/app/Tygh/Lock/Factory.php
33@@ -27,21 +27,37 @@ class Factory
34 */
35 protected $store;
36
37+ /**
38+ * @var int
39+ */
40+ protected $default_max_suspend_processes_count;
41+
42 /**
43 * Factory constructor.
44 *
45 * @param \Tygh\Lock\StoreInterface $store
46+ * @param int $default_max_suspend_processes_count
47 */
48- public function __construct(StoreInterface $store)
49+ public function __construct(StoreInterface $store, $default_max_suspend_processes_count = 0)
50 {
51 $this->store = $store;
52+ $this->default_max_suspend_processes_count = $default_max_suspend_processes_count;
53 }
54
55 /**
56- * @inheritdoc
57+ * @param string $resource
58+ * @param float $ttl
59+ * @param bool $auto_release
60+ * @param null|int $max_suspend_processes_count
61+ *
62+ * @return \Tygh\Lock\Lock
63 */
64- public function createLock($resource, $ttl = 30.0, $auto_release = true)
65+ public function createLock($resource, $ttl = 30.0, $auto_release = true, $max_suspend_processes_count = null)
66 {
67- return new Lock(new Key($resource), $this->store, $ttl, $auto_release);
68+ if ($max_suspend_processes_count === null) {
69+ $max_suspend_processes_count = $this->default_max_suspend_processes_count;
70+ }
71+
72+ return new Lock(new Key($resource), $this->store, $ttl, $auto_release, $max_suspend_processes_count);
73 }
74 }
75\ No newline at end of file
76diff --git a/app/Tygh/Lock/Lock.php b/app/Tygh/Lock/Lock.php
77index 1a7c8d63e1..202b4e4e88 100644
78--- a/app/Tygh/Lock/Lock.php
79+++ b/app/Tygh/Lock/Lock.php
80@@ -57,19 +57,27 @@ class Lock implements LockInterface
81 */
82 private $dirty = false;
83
84+ /**
85+ * @var int
86+ */
87+ private $max_suspend_processes_count = 0;
88+
89 /**
90 * @param \Symfony\Component\Lock\Key $key Resource to lock
91 * @param StoreInterface $store Store used to handle lock persistence
92 * @param float|null $ttl Maximum expected lock duration in seconds
93 * @param bool $auto_release Whether to automatically release the lock or not when the lock
94 * instance is destroyed
95+ * @param int $max_suspend_processes_count
96+ *
97 */
98- public function __construct(Key $key, StoreInterface $store, $ttl = null, $auto_release = true)
99+ public function __construct(Key $key, StoreInterface $store, $ttl = null, $auto_release = true, $max_suspend_processes_count = 0)
100 {
101 $this->store = $store;
102 $this->key = $key;
103 $this->ttl = $ttl;
104 $this->auto_release = (bool) $auto_release;
105+ $this->max_suspend_processes_count = (int) $max_suspend_processes_count;
106 }
107
108 /**
109@@ -173,9 +181,16 @@ class Lock implements LockInterface
110 * @param int $retry_count Maximum amount of retry
111 *
112 * @return bool
113+ * @throws ExceededSuspendProcessesCountException
114 */
115 public function wait($retry_sleep = 100, $retry_count = PHP_INT_MAX)
116 {
117+ if ($this->max_suspend_processes_count > 0
118+ && !$this->store->incSuspendProcessesCount($this->key, $this->max_suspend_processes_count)
119+ ) {
120+ throw new ExceededSuspendProcessesCountException();
121+ }
122+
123 $retry = 0;
124 $sleep_randomness = (int) ($retry_sleep / 10);
125
126diff --git a/app/Tygh/Lock/Store/DatabaseStore.php b/app/Tygh/Lock/Store/DatabaseStore.php
127index ef6cd4ebeb..a659098bc0 100644
128--- a/app/Tygh/Lock/Store/DatabaseStore.php
129+++ b/app/Tygh/Lock/Store/DatabaseStore.php
130@@ -75,7 +75,7 @@ class DatabaseStore implements StoreInterface
131
132 try {
133 $this->query(
134- 'INSERT INTO ?:lock_keys (key_id, token, expiry_at) VALUES (?s, ?s, UNIX_TIMESTAMP(NOW()) + ?i)',
135+ 'INSERT INTO ?:lock_keys (key_id, token, expiry_at, suspend_processes_count) VALUES (?s, ?s, UNIX_TIMESTAMP(NOW()) + ?i, 0)',
136 $this->getHashedKey($key), $this->getToken($key), $this->initial_ttl
137 );
138 } catch (DatabaseException $exception) {
139@@ -133,6 +133,15 @@ class DatabaseStore implements StoreInterface
140 }
141 }
142
143+ public function incSuspendProcessesCount(Key $key, $max_suspend_processes_count)
144+ {
145+ return (bool) $this->query(
146+ 'UPDATE ?:lock_keys SET suspend_processes_count = suspend_processes_count + 1'
147+ . ' WHERE key_id = ?s AND suspend_processes_count < ?i',
148+ $this->getHashedKey($key), $max_suspend_processes_count
149+ );
150+ }
151+
152 /**
153 * @inheritDoc
154 */
155diff --git a/app/Tygh/Lock/Store/RetryTillSaveStore.php b/app/Tygh/Lock/Store/RetryTillSaveStore.php
156index 46446e2a1e..ccbc9392ce 100644
157--- a/app/Tygh/Lock/Store/RetryTillSaveStore.php
158+++ b/app/Tygh/Lock/Store/RetryTillSaveStore.php
159@@ -50,4 +50,12 @@ class RetryTillSaveStore extends BaseRetryTillSaveStore implements StoreInterfac
160 {
161 return $this->decorated->exists($key, $owned_to_current_process);
162 }
163+
164+ /**
165+ * @inheritDoc
166+ */
167+ public function incSuspendProcessesCount(Key $key, $max_suspend_processes_count)
168+ {
169+ return $this->decorated->incSuspendProcessesCount($key, $max_suspend_processes_count);
170+ }
171 }
172\ No newline at end of file
173diff --git a/app/Tygh/Lock/StoreInterface.php b/app/Tygh/Lock/StoreInterface.php
174index 88538812d1..1050613af2 100644
175--- a/app/Tygh/Lock/StoreInterface.php
176+++ b/app/Tygh/Lock/StoreInterface.php
177@@ -33,4 +33,6 @@ interface StoreInterface extends BaseStoreInterface
178 * @return bool
179 */
180 public function exists(Key $key, $owned_to_current_process = true);
181+
182+ public function incSuspendProcessesCount(Key $key, $max_suspend_processes_count);
183 }
184\ No newline at end of file
185diff --git a/app/Tygh/Providers/LockProvider.php b/app/Tygh/Providers/LockProvider.php
186index a09014c34c..5ad64c6a4e 100644
187--- a/app/Tygh/Providers/LockProvider.php
188+++ b/app/Tygh/Providers/LockProvider.php
189@@ -49,7 +49,7 @@ class LockProvider implements ServiceProviderInterface
190 $provider = 'dummy';
191 }
192
193- return new Factory($app['lock.provider.' . $provider]);
194+ return new Factory($app['lock.provider.' . $provider], Registry::ifGet('config.lock_max_suspend_processes_count', 0));
195 };
196
197 $app['lock.provider.dummy'] = function (Container $app) {
198diff --git a/config.local.php b/config.local.php
199index 229894008b..b4d5a847aa 100644
200--- a/config.local.php
201+++ b/config.local.php
202@@ -119,6 +119,8 @@ $config['cache_xcache_global_ttl'] = 0;
203 $config['lock_backend'] = 'database';
204 $config['lock_redis_server'] = 'localhost';
205 $config['lock_redis_server_password'] = null;
206+$config['lock_max_suspend_processes_count'] = 0;
207+$config['lock_max_suspend_processes_count'] = $_SERVER['REQUEST_METHOD'] !== 'POST' && !defined('AJAX_REQUEST') ? 2 : 0;
208
209 // Set to unique store prefix if you use the same Redis/Xcache/Apc storage
210 // for serveral cart installations
211diff --git a/var/database/data/struct/struct.sql b/var/database/data/struct/struct.sql
212index e0d742056a..556c16a4d1 100644
213--- a/var/database/data/struct/struct.sql
214+++ b/var/database/data/struct/struct.sql
215@@ -1578,6 +1578,7 @@ DROP TABLE IF EXISTS cscart_lock_keys;
216 CREATE TABLE `cscart_lock_keys` (
217 `key_id` varchar(64) NOT NULL,
218 `token` varchar(64) NOT NULL,
219+ `suspend_processes_count` smallint(5) unsigned NOT NULL DEFAULT '0',
220 `expiry_at` int(11) unsigned NOT NULL,
221 PRIMARY KEY (`key_id`)
222 ) DEFAULT CHARSET=utf8;