Skip to content

Commit 165f374

Browse files
committed
Implement fair semaphore with locking, implementation is complete now
1 parent d27685d commit 165f374

File tree

1 file changed

+100
-3
lines changed

1 file changed

+100
-3
lines changed

src/Semaphore.php

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
namespace CodeOrange\RedisCountingSemaphore;
33

44
use Predis\Client;
5+
use Predis\Transaction\MultiExec;
56
use Ramsey\Uuid\Uuid;
67

78
/**
@@ -48,7 +49,7 @@ public function acquire($sleep = null, $retries = null) {
4849
// We already have it
4950
return true;
5051
}
51-
$acquired = $this->acquire_unfair();
52+
$acquired = $this->acquire_fair_with_lock();
5253
if ($acquired) {
5354
return true;
5455
} else {
@@ -70,7 +71,7 @@ public function release() {
7071
// We didn't have it
7172
return;
7273
}
73-
$this->release_unfair();
74+
$this->release_fair();
7475
}
7576

7677
/**
@@ -82,7 +83,7 @@ public function refresh() {
8283
if (!$this->identifier) {
8384
return false;
8485
}
85-
return true;
86+
return $this->refresh_fair();
8687
}
8788

8889
//<editor-fold desc="Methods as built up to in the book">
@@ -115,10 +116,106 @@ private function release_unfair() {
115116
return $this->client->zrem($this->name, $id);
116117
}
117118
private function acquire_fair() {
119+
$identifier = (string) Uuid::uuid4();
120+
$cszet = $this->name . ':owner';
121+
$ctr = $this->name . ':counter';
122+
123+
$now = time();
124+
125+
$transaction = $this->client->transaction();
126+
127+
// Time out old entries
128+
$transaction->zremrangebyscore($this->name, '-inf', $now - $this->timeout);
129+
$transaction->zinterstore($cszet, [$cszet, $this->name], ['weights' => [1, 0]]);
130+
131+
// Get the counter
132+
$transaction->incr($ctr);
133+
$result = $transaction->execute();
134+
$counter = $result[count($result) - 1];
135+
136+
// Try to acquire the semaphore
137+
$transaction = $this->client->transaction();
138+
$transaction->zadd($this->name, [$identifier => $now]);
139+
$transaction->zadd($cszet, [$identifier => $counter]);
140+
141+
// Check the rank to determine if we got the semaphore
142+
$transaction->zrank($cszet, $identifier);
143+
$result = $transaction->execute();
144+
$rank = $result[count($result) - 1];
145+
if ($rank < $this->limit) {
146+
// We got it!
147+
$this->identifier = $identifier;
148+
return true;
149+
}
150+
151+
// We didn't get the semaphore, clean out the bad data
152+
$transaction = $this->client->transaction();
153+
$transaction->zrem($this->name, $identifier);
154+
$transaction->zrem($cszet, $identifier);
155+
$transaction->execute();
156+
157+
return false;
158+
}
159+
private function release_fair() {
160+
$id = $this->identifier;
161+
$this->identifier = null;
118162

163+
$transaction = $this->client->transaction();
164+
$transaction->zrem($this->name, $id);
165+
$transaction->zrem($this->name . ':owner', $id);
166+
return $transaction->execute()[0];
167+
}
168+
private function refresh_fair() {
169+
if ($this->client->zadd($this->name, [$this->identifier => time()])) {
170+
// We lost it
171+
$this->release_fair();
172+
return false;
173+
}
174+
// We still have it
175+
return true;
119176
}
120177
private function acquire_fair_with_lock() {
178+
$identifier = $this->acquire_lock(0.01);
179+
if ($identifier) {
180+
try {
181+
return $this->acquire_fair();
182+
} finally {
183+
$this->release_lock($identifier);
184+
}
185+
}
186+
return false;
187+
}
188+
189+
// From section 6.2 of the book
190+
public function acquire_lock($acquire_timeout = 10) {
191+
$identifier = (string)Uuid::uuid4();
192+
193+
$end = time() + $acquire_timeout;
194+
while (time() < $end) {
195+
$res = $this->client->setnx('lock:' . $this->name, $identifier);
196+
if ($res) {
197+
return $identifier;
198+
}
199+
sleep(0.001);
200+
}
201+
return false;
202+
}
203+
public function release_lock($id) {
204+
$lockname = 'lock:' . $this->name;
121205

206+
$res = $this->client->transaction(['watch' => $lockname, 'cas' => true, 'retry' => 1000], function (MultiExec $t) use ($id, $lockname) {
207+
$value = $t->get($lockname);
208+
if ($value === $id) {
209+
$t->multi();
210+
$t->del([$lockname]);
211+
}
212+
});
213+
if ($res) {
214+
return true;
215+
} else {
216+
// We didn't execute anything, so we've lost the lock
217+
return false;
218+
}
122219
}
123220
//</editor-fold>
124221

0 commit comments

Comments
 (0)