Skip to content

Commit 093f389

Browse files
author
Willem Stuursma-Ruwen
authored
Merge pull request #29 from sdoatput/return-value-on-unlock-error
Add ability to retrieve executed code result or exception on unlock error
2 parents 6925de8 + c526260 commit 093f389

File tree

4 files changed

+138
-8
lines changed

4 files changed

+138
-8
lines changed

README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ method guarantees that the code is only executed by one process at once. Other
5555
processes have to wait until the mutex is available. The critical code may throw
5656
an exception, which would release the lock as well.
5757

58-
This method returns what ever is returned to the given callable. The return
58+
This method returns whatever is returned to the given callable. The return
5959
value is not checked, thus it is up to the user to decide if for example the
6060
return value `false` or `null` should be seen as a failed action.
6161

@@ -119,6 +119,39 @@ if (false === $newBalance) {
119119
}
120120
```
121121

122+
### Extracting code result after lock release exception
123+
124+
Mutex implementations based on [`malkush\lock\mutex\LockMutex`][12] will throw
125+
[`malkusch\lock\exception\LockReleaseException`][13] in case of lock release
126+
problem, but the synchronized code block will be already executed at this point.
127+
In order to read the code result (or an exception thrown there),
128+
`LockReleaseException` provides methods to extract it.
129+
130+
Example:
131+
```php
132+
try {
133+
// or $mutex->check(...)
134+
$mutex->synchronized(function () {
135+
if (someCondition()) {
136+
throw new \DomainException();
137+
}
138+
139+
return "result";
140+
});
141+
} catch (LockReleaseException $unlock_exception) {
142+
if ($unlock_exception->getCodeException() !== null) {
143+
$code_exception = $unlock_exception->getCodeException()
144+
// do something with the code exception
145+
} else {
146+
$code_result = $unlock_exception->getCodeResult();
147+
// do something with the code result
148+
}
149+
150+
// deal with LockReleaseException or propagate it
151+
throw $unlock_exception;
152+
}
153+
```
154+
122155
### Implementations
123156

124157
Because the [`malkusch\lock\mutex\Mutex`](#mutex) class is an abstract class,
@@ -374,3 +407,5 @@ If you like this project and feel generous donate a few Bitcoins here:
374407
[9]: https://en.wikipedia.org/wiki/Double-checked_locking
375408
[10]: https://en.wikipedia.org/wiki/Compare-and-swap
376409
[11]: https://github.com/php-lock/lock/blob/master/classes/mutex/CASMutex.php#L44
410+
[12]: https://github.com/php-lock/lock/blob/master/classes/mutex/LockMutex.php
411+
[13]: https://github.com/php-lock/lock/blob/master/classes/exception/LockReleaseException.php

classes/exception/LockReleaseException.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,45 @@
1616
class LockReleaseException extends MutexException
1717
{
1818

19+
/**
20+
* @var mixed
21+
*/
22+
private $code_result;
23+
24+
/**
25+
* @var \Throwable|null
26+
*/
27+
private $code_exception;
28+
29+
/**
30+
* @return mixed The return value of the executed code block.
31+
*/
32+
public function getCodeResult()
33+
{
34+
return $this->code_result;
35+
}
36+
37+
/**
38+
* @param mixed $code_result The return value of the executed code block.
39+
*/
40+
public function setCodeResult($code_result): void
41+
{
42+
$this->code_result = $code_result;
43+
}
44+
45+
/**
46+
* @return \Throwable|null The exception thrown by the code block or null when there was no exception.
47+
*/
48+
public function getCodeException(): ?\Throwable
49+
{
50+
return $this->code_exception;
51+
}
52+
53+
/**
54+
* @param \Throwable $code_exception The exception thrown by the code block.
55+
*/
56+
public function setCodeException(\Throwable $code_exception): void
57+
{
58+
$this->code_exception = $code_exception;
59+
}
1960
}

classes/mutex/LockMutex.php

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace malkusch\lock\mutex;
44

5-
use malkusch\lock\exception\LockReleaseException;
65
use malkusch\lock\exception\LockAcquireException;
6+
use malkusch\lock\exception\LockReleaseException;
77

88
/**
99
* Locking mutex.
@@ -31,14 +31,32 @@ abstract protected function lock(): void;
3131
* @throws LockReleaseException The lock could not be released.
3232
*/
3333
abstract protected function unlock(): void;
34-
34+
3535
public function synchronized(callable $code)
3636
{
3737
$this->lock();
38+
39+
$code_result = null;
40+
$code_exception = null;
3841
try {
39-
return $code();
42+
$code_result = $code();
43+
} catch (\Throwable $exception) {
44+
$code_exception = $exception;
45+
46+
throw $exception;
4047
} finally {
41-
$this->unlock();
48+
try {
49+
$this->unlock();
50+
} catch (LockReleaseException $lock_exception) {
51+
$lock_exception->setCodeResult($code_result);
52+
if ($code_exception !== null) {
53+
$lock_exception->setCodeException($code_exception);
54+
}
55+
56+
throw $lock_exception;
57+
}
4258
}
59+
60+
return $code_result;
4361
}
4462
}

tests/mutex/LockMutexTest.php

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,54 @@ public function testUnlockFailsAfterCode()
9292
/**
9393
* Tests unlock() fails after the code threw an exception.
9494
*
95-
* The previous exception should be the code's exception.
96-
*
9795
* @expectedException malkusch\lock\exception\LockReleaseException
9896
*/
9997
public function testUnlockFailsAfterException()
10098
{
101-
$this->mutex->expects($this->any())
99+
$this->mutex->expects($this->once())
102100
->method("unlock")
103101
->willThrowException(new LockReleaseException());
104102

105103
$this->mutex->synchronized(function () {
106104
throw new \DomainException();
107105
});
108106
}
107+
108+
/**
109+
* Tests the code result is available in LockReleaseException.
110+
*/
111+
public function testCodeResultAvailableAfterFailedUnlock()
112+
{
113+
$this->mutex->expects($this->once())
114+
->method("unlock")
115+
->willThrowException(new LockReleaseException());
116+
117+
try {
118+
$this->mutex->synchronized(function () {
119+
return "result";
120+
});
121+
} catch (LockReleaseException $exception) {
122+
$this->assertEquals("result", $exception->getCodeResult());
123+
$this->assertNull($exception->getCodeException());
124+
}
125+
}
126+
127+
/**
128+
* Tests the code exception is available in LockReleaseException.
129+
*/
130+
public function testCodeExceptionAvailableAfterFailedUnlock()
131+
{
132+
$this->mutex->expects($this->once())
133+
->method("unlock")
134+
->willThrowException(new LockReleaseException());
135+
136+
try {
137+
$this->mutex->synchronized(function () {
138+
throw new \DomainException("Domain exception");
139+
});
140+
} catch (LockReleaseException $exception) {
141+
$this->assertInstanceOf(\DomainException::class, $exception->getCodeException());
142+
$this->assertEquals("Domain exception", $exception->getCodeException()->getMessage());
143+
}
144+
}
109145
}

0 commit comments

Comments
 (0)