Skip to content

Commit

Permalink
PIPRES-319: Lock process adapter (#839)
Browse files Browse the repository at this point in the history
  • Loading branch information
mandan2 authored Nov 13, 2023
1 parent 97b0eff commit 8d7b716
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/Config/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ class Config
const APPLE_PAY_DIRECT_ORDER_CREATION_MAX_WAIT_RETRIES = 10;
const BANCONTACT_ORDER_CREATION_MAX_WAIT_RETRIES = 600;

public const LOCK_TIME_TO_LIVE = 60;

/** @var array */
public static $methods = [
'banktransfer' => 'Bank',
Expand Down
3 changes: 3 additions & 0 deletions src/Exception/Code/ExceptionCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ class ExceptionCode

public const INFRASTRUCTURE_FAILED_TO_INSTALL_ORDER_STATE = 1001;
public const INFRASTRUCTURE_UNKNOWN_ERROR = 1002;
public const INFRASTRUCTURE_LOCK_EXISTS = 1003;
public const INFRASTRUCTURE_LOCK_ON_ACQUIRE_IS_MISSING = 1004;
public const INFRASTRUCTURE_LOCK_ON_RELEASE_IS_MISSING = 1005;

public const FAILED_TO_FIND_CUSTOMER_ADDRESS = 2001;

Expand Down
79 changes: 79 additions & 0 deletions src/Infrastructure/Adapter/Lock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace Mollie\Infrastructure\Adapter;

use Mollie\Config\Config;
use Mollie\Infrastructure\Exception\CouldNotHandleLocking;
use Symfony\Component\Lock\Factory as LockFactoryV3;
use Symfony\Component\Lock\LockFactory as LockFactoryV4;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\Lock\Store\FlockStore;

class Lock
{
private $lockFactory;
/** @var ?LockInterface */
private $lock;

public function __construct()
{
$store = new FlockStore();

if (class_exists(LockFactoryV4::class)) {
// Symfony 4.4+
$this->lockFactory = new LockFactoryV4($store);

return;
}

// Symfony 3.4+
$this->lockFactory = new LockFactoryV3($store);
}

/**
* @throws CouldNotHandleLocking
*/
public function create(string $resource, int $ttl = Config::LOCK_TIME_TO_LIVE, bool $autoRelease = true): void
{
if ($this->lock) {
throw CouldNotHandleLocking::lockExists();
}

$this->lock = $this->lockFactory->createLock($resource, $ttl, $autoRelease);
}

/**
* @throws CouldNotHandleLocking
*/
public function acquire(bool $blocking = false): bool
{
if (!$this->lock) {
throw CouldNotHandleLocking::lockOnAcquireIsMissing();
}

return $this->lock->acquire($blocking);
}

/**
* @throws CouldNotHandleLocking
*/
public function release(): void
{
if (!$this->lock) {
throw CouldNotHandleLocking::lockOnReleaseIsMissing();
}

$this->lock->release();

$this->lock = null;
}

public function __destruct()
{
try {
$this->release();
} catch (CouldNotHandleLocking $exception) {
return;
}
}
}
33 changes: 33 additions & 0 deletions src/Infrastructure/Exception/CouldNotHandleLocking.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Mollie\Infrastructure\Exception;

use Mollie\Exception\Code\ExceptionCode;
use Mollie\Exception\MollieException;

class CouldNotHandleLocking extends MollieException
{
public static function lockExists(): self
{
return new self(
'Lock exists',
ExceptionCode::INFRASTRUCTURE_LOCK_EXISTS
);
}

public static function lockOnAcquireIsMissing(): self
{
return new self(
'Lock on acquire is missing',
ExceptionCode::INFRASTRUCTURE_LOCK_ON_ACQUIRE_IS_MISSING
);
}

public static function lockOnReleaseIsMissing(): self
{
return new self(
'Lock on release is missing',
ExceptionCode::INFRASTRUCTURE_LOCK_ON_RELEASE_IS_MISSING
);
}
}
74 changes: 74 additions & 0 deletions tests/Integration/Infrastructure/Adapter/LockTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace Mollie\Tests\Integration\Infrastructure\Adapter;

use Mollie\Exception\Code\ExceptionCode;
use Mollie\Infrastructure\Adapter\Lock;
use Mollie\Infrastructure\Exception\CouldNotHandleLocking;
use Mollie\Tests\Integration\BaseTestCase;

class LockTest extends BaseTestCase
{
public function testItSuccessfullyCompletesLockFlow(): void
{
/** @var Lock $lock */
$lock = $this->getService(Lock::class);

$lock->create('test-lock-name');

$this->assertTrue($lock->acquire());

$lock->release();
}

public function testItSuccessfullyLocksResourceFromAnotherProcess(): void
{
/** @var Lock $lock */
$lock = $this->getService(Lock::class);

$lock->create('test-lock-name');

$this->assertTrue($lock->acquire());

/** @var Lock $newLock */
$newLock = $this->getService(Lock::class);

$newLock->create('test-lock-name');

$this->assertFalse($newLock->acquire());
}

public function testItUnsuccessfullyCompletesLockFlowFailedToCreateLockWithMissingLock(): void
{
/** @var Lock $lock */
$lock = $this->getService(Lock::class);

$this->expectException(CouldNotHandleLocking::class);
$this->expectExceptionCode(ExceptionCode::INFRASTRUCTURE_LOCK_EXISTS);

$lock->create('test-lock-name');
$lock->create('test-lock-name');
}

public function testItUnsuccessfullyCompletesLockFlowFailedToAcquireLockWithMissingLock(): void
{
/** @var Lock $lock */
$lock = $this->getService(Lock::class);

$this->expectException(CouldNotHandleLocking::class);
$this->expectExceptionCode(ExceptionCode::INFRASTRUCTURE_LOCK_ON_ACQUIRE_IS_MISSING);

$lock->acquire();
}

public function testItUnsuccessfullyCompletesLockFlowFailedToReleaseLockWithMissingLock(): void
{
/** @var Lock $lock */
$lock = $this->getService(Lock::class);

$this->expectException(CouldNotHandleLocking::class);
$this->expectExceptionCode(ExceptionCode::INFRASTRUCTURE_LOCK_ON_RELEASE_IS_MISSING);

$lock->release();
}
}

0 comments on commit 8d7b716

Please sign in to comment.