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