22namespace CodeOrange \RedisCountingSemaphore ;
33
44use Predis \Client ;
5+ use Predis \Transaction \MultiExec ;
56use 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