Skip to content

Commit ea0e77d

Browse files
author
Jonathan Gaillard
committed
Merge pull request #7 from gaillard/bug
Collapse cleaning updates into normal updates.
2 parents 7502b6c + f4fef7d commit ea0e77d

File tree

2 files changed

+63
-28
lines changed

2 files changed

+63
-28
lines changed

src/Locker.php

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,20 @@ public function readLock($id, $staleDuration)
8585

8686
while (time() < $timeoutTimestamp) {
8787
$readerId = new \MongoId();
88-
$query = ['_id' => $id, 'writing' => false, 'writePending' => false];
88+
$query = [
89+
'_id' => $id,
90+
'$or' => [
91+
//normal case where we have zero or more readers and no writers pending
92+
['writing' => false, 'writePending' => false],
93+
//case where a pending write is stale
94+
['writing' => false, 'writePending' => true, 'writeStaleTs' => ['$lte' => new \MongoDate()]],
95+
//case where a writer is stale
96+
['writing' => true, 'writeStaleTs' => ['$lte' => new \MongoDate()]],
97+
],
98+
];
8999
$update = [
90100
'$push' => ['readers' => ['id' => $readerId, 'staleTs' => $staleTimestamp]],
91-
'$set' => ['writeStaleTs' => null],
101+
'$set' => ['writing' => false, 'writePending' => false, 'writeStaleTs' => null],
92102
];
93103
try {
94104
if ($this->collection->update($query, $update, ['upsert' => true])['n'] === 1) {
@@ -100,10 +110,6 @@ public function readLock($id, $staleDuration)
100110
}
101111
}
102112

103-
if ($this->clearStuckWrite($id)) {
104-
continue;
105-
}
106-
107113
usleep($this->pollDuration);
108114
}
109115

@@ -119,7 +125,11 @@ public function readLock($id, $staleDuration)
119125
*/
120126
public function readUnlock($id, \MongoId $readerId)
121127
{
122-
$this->collection->update(['_id' => $id], ['$pull' => ['readers' => ['id' => $readerId]]]);
128+
$this->collection->update(
129+
['_id' => $id],
130+
//pull this reader id, or any stale readers
131+
['$pull' => ['readers' => ['$or' => [['id' => $readerId], ['staleTs' => ['$lte' => new \MongoDate()]]]]]]
132+
);
123133
$this->collection->remove(['_id' => $id, 'writing' => false, 'readers' => ['$size' => 0]]);
124134
}
125135

@@ -144,7 +154,17 @@ public function writeLock($id, $staleDuration)
144154
$staleTimestamp = new \MongoDate((int)min(time() + $staleDuration, PHP_INT_MAX));
145155

146156
while (time() < $timeoutTimestamp) {
147-
$query = ['_id' => $id, 'writing' => false, 'readers' => ['$size' => 0]];
157+
$query = [
158+
'_id' => $id,
159+
'$or' => [
160+
//normal case when readers are done
161+
['writing' => false, 'readers' => ['$size' => 0]],
162+
//to clean where writer is stuck
163+
['writing' => true, 'writeStaleTs' => ['$lte' => new \MongoDate()]],
164+
//to clean where all readers are stuck
165+
['writing' => false, 'readers.staleTs' => ['$not' => ['$gt' => new \MongoDate()]]],
166+
],
167+
];
148168
$update = [
149169
'_id' => $id,
150170
'writing' => true,
@@ -162,11 +182,10 @@ public function writeLock($id, $staleDuration)
162182
}
163183
}
164184

165-
if ($this->clearStuckWrite($id) || $this->clearStuckRead($id)) {
166-
continue;
167-
}
168-
169-
$this->collection->update(['_id' => $id], ['$set' => ['writePending' => true]]);
185+
$this->collection->update(
186+
['_id' => $id],
187+
['$set' => ['writePending' => true, 'writeStaleTs' => $staleTimestamp]]
188+
);
170189

171190
usleep($this->pollDuration);
172191
}
@@ -184,19 +203,4 @@ public function writeUnlock($id)
184203
{
185204
$this->collection->remove(['_id' => $id]);
186205
}
187-
188-
private function clearStuckWrite($id)
189-
{
190-
return $this->collection->remove(
191-
['_id' => $id, 'writing' => true, 'writeStaleTs' => ['$lte' => new \MongoDate()]]
192-
)['n'] === 1;
193-
}
194-
195-
private function clearStuckRead($id)
196-
{
197-
$now = new \MongoDate();
198-
$query = ['_id' => $id, 'writing' => false, 'readers.staleTs' => ['$lte' => $now]];
199-
$update = ['$pull' => ['readers' => ['staleTs' => ['$lte' => $now]]]];
200-
return $this->collection->update($query, $update)['n'] === 1;
201-
}
202206
}

tests/LockerTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,24 @@ public function readLockClearStuck()
134134
$this->locker->readLock('theId', 1000);
135135
}
136136

137+
/**
138+
* @test
139+
*/
140+
public function readLockClearStuckWritePending()
141+
{
142+
$this->locker->readLock('theId', 2);
143+
144+
//the attempted write lock below only sets writePending since we already have a read lock
145+
$locker = new Locker($this->collection, 0, 1);
146+
try {
147+
$locker->writeLock('theId', 0);
148+
$this->fail();
149+
} catch (\Exception $e) {
150+
}
151+
152+
$this->locker->readLock('theId', 1000);
153+
}
154+
137155
/**
138156
* @test
139157
* @expectedException \Exception
@@ -220,6 +238,19 @@ public function readUnlockExistingReader()
220238
$this->assertGreaterThan(time() + 990, $actualReaders[0]['staleTs']->sec);
221239
}
222240

241+
/**
242+
* @test
243+
*/
244+
public function readUnlockExistingReaderStale()
245+
{
246+
$this->locker->readLock('theId', 0);
247+
248+
$readerId = $this->locker->readLock('theId', 1000);
249+
$this->locker->readUnlock('theId', $readerId);
250+
251+
$this->assertSame(0, $this->collection->count());
252+
}
253+
223254
/**
224255
* @test
225256
*/

0 commit comments

Comments
 (0)