Skip to content

Commit d27685d

Browse files
committed
Initial version with unfair implementation from the book
0 parents  commit d27685d

File tree

4 files changed

+345
-0
lines changed

4 files changed

+345
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
.idea/
3+
index.php

composer.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "code-orange/redis-counting-semaphore",
3+
"description": "Distributed counting semaphore implementation for redis",
4+
"type": "library",
5+
"license": "proprietary",
6+
"authors": [
7+
{
8+
"name": "Tim van Dalen",
9+
"email": "tim@code-orange.nl"
10+
}
11+
],
12+
"autoload": {
13+
"psr-4": {
14+
"CodeOrange\\RedisCountingSemaphore\\": "src/"
15+
}
16+
},
17+
"require": {
18+
"predis/predis": "^1.1",
19+
"ramsey/uuid": "^3.7"
20+
}
21+
}

composer.lock

Lines changed: 196 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Semaphore.php

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
namespace CodeOrange\RedisCountingSemaphore;
3+
4+
use Predis\Client;
5+
use Ramsey\Uuid\Uuid;
6+
7+
/**
8+
* Class Semaphore
9+
*
10+
* A fair, race free implementation of a counting semaphore, based on the algorithm description in
11+
* section 6.3 of "Redis in Action"
12+
* (https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6-3-counting-semaphores/)
13+
*
14+
* @package CodeOrange\RedisCountingSemaphore
15+
*/
16+
class Semaphore {
17+
private $client;
18+
private $name;
19+
private $limit;
20+
private $timeout;
21+
22+
/** @var string $identifier Identifier for the acquired semaphore */
23+
private $identifier = null;
24+
25+
/**
26+
* Semaphore constructor.
27+
* @param Client $client Predis client with an open connection
28+
* @param string $name Name of the semaphore
29+
* @param int $limit The amount of resources this semaphore protects
30+
* @param int $timeout Timeout of an acquired semaphore, in seconds
31+
*/
32+
public function __construct(Client $client, $name, $limit = 1, $timeout = 10) {
33+
$this->client = $client;
34+
$this->name = $name;
35+
$this->limit = $limit;
36+
$this->timeout = $timeout;
37+
}
38+
39+
/**
40+
* Try to acquire a semaphore
41+
*
42+
* @param float $sleep Number of seconds to sleep between retries. If null, this function will not retry but return immediately.
43+
* @param int $retries Number of times to retry before giving up
44+
* @return bool Whether or not the semaphore was acquired correctly
45+
*/
46+
public function acquire($sleep = null, $retries = null) {
47+
if ($this->identifier) {
48+
// We already have it
49+
return true;
50+
}
51+
$acquired = $this->acquire_unfair();
52+
if ($acquired) {
53+
return true;
54+
} else {
55+
if ($retries > 0 && $sleep > 0) {
56+
sleep($sleep);
57+
return $this->acquire($sleep, $retries - 1);
58+
}
59+
return false;
60+
}
61+
}
62+
63+
/**
64+
* Release this semaphore
65+
*
66+
* @return void
67+
*/
68+
public function release() {
69+
if (!$this->identifier) {
70+
// We didn't have it
71+
return;
72+
}
73+
$this->release_unfair();
74+
}
75+
76+
/**
77+
* Refresh the semaphore
78+
*
79+
* @return bool Whether or not we still have the semaphore
80+
*/
81+
public function refresh() {
82+
if (!$this->identifier) {
83+
return false;
84+
}
85+
return true;
86+
}
87+
88+
//<editor-fold desc="Methods as built up to in the book">
89+
private function acquire_unfair() {
90+
$identifier = (string) Uuid::uuid4();
91+
$now = time();
92+
93+
$transaction = $this->client->transaction();
94+
// Time out old identifiers
95+
$transaction->zremrangebyscore($this->name, '-inf', $now - $this->timeout);
96+
// Try to acquire semaphore
97+
$transaction->zadd($this->name, [$identifier => $now]);
98+
// Check to see if we have it
99+
$transaction->zrank($this->name, $identifier);
100+
$result = $transaction->execute();
101+
$rank = $result[count($result) - 1];
102+
if ($rank < $this->limit) {
103+
// We got it!
104+
$this->identifier = $identifier;
105+
return true;
106+
}
107+
108+
// We didn't get it, remove the identifier from the table
109+
$this->client->zrem($this->name, $identifier);
110+
return false;
111+
}
112+
private function release_unfair() {
113+
$id = $this->identifier;
114+
$this->identifier = null;
115+
return $this->client->zrem($this->name, $id);
116+
}
117+
private function acquire_fair() {
118+
119+
}
120+
private function acquire_fair_with_lock() {
121+
122+
}
123+
//</editor-fold>
124+
125+
}

0 commit comments

Comments
 (0)