Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PIPRES-319: Lock process adapter #839

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
}
Loading