toString())->toBe($key); } }); it('rejects invalid lock keys', function () { $invalidKeys = [ '', // empty str_repeat('a', 256), // too long 'lock with spaces', 'lock@invalid', 'lock#invalid', 'lock$invalid', 'lock%invalid', 'lock&invalid', 'lock*invalid', 'lock(invalid)', 'lock+invalid', 'lock=invalid', 'lock[invalid]', 'lock{invalid}', 'lock|invalid', 'lock\\invalid', 'lock:invalid', 'lock;invalid', 'lock"invalid', 'lock\'invalid', 'lock', 'lock,invalid', 'lock?invalid', 'lock/invalid', 'lock~invalid', 'lock`invalid' ]; foreach ($invalidKeys as $key) { expect(fn() => LockKey::fromString($key)) ->toThrow(\InvalidArgumentException::class); } }); it('allows valid characters', function () { $validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.'; $lockKey = LockKey::fromString($validChars); expect($lockKey->toString())->toBe($validChars); }); it('is readonly and immutable', function () { $lockKey = LockKey::fromString('test-lock'); $reflection = new ReflectionClass($lockKey); expect($reflection->isReadOnly())->toBeTrue(); $valueProperty = $reflection->getProperty('value'); expect($valueProperty->isReadOnly())->toBeTrue(); }); }); describe('Factory Methods', function () { it('creates lock key for job', function () { $jobId = JobId::fromString('job-12345'); $lockKey = LockKey::forJob($jobId); expect($lockKey->toString())->toBe('job.job-12345'); }); it('creates lock key for queue', function () { $queueName = QueueName::fromString('email-processing'); $lockKey = LockKey::forQueue($queueName); expect($lockKey->toString())->toBe('queue.email-processing'); }); it('creates lock key for worker', function () { $workerId = WorkerId::fromString('worker-123'); $lockKey = LockKey::forWorker($workerId); expect($lockKey->toString())->toBe('worker.worker-123'); }); it('creates lock key for resource', function () { $lockKey = LockKey::forResource('database', 'primary_db'); expect($lockKey->toString())->toBe('database.primary_db'); }); it('creates lock key for batch', function () { $lockKey = LockKey::forBatch('monthly-report-2024-01'); expect($lockKey->toString())->toBe('batch.monthly-report-2024-01'); }); }); describe('String Operations', function () { it('equals() compares lock keys correctly', function () { $key1 = LockKey::fromString('test-lock'); $key2 = LockKey::fromString('test-lock'); $key3 = LockKey::fromString('other-lock'); expect($key1->equals($key2))->toBeTrue(); expect($key1->equals($key3))->toBeFalse(); expect($key2->equals($key3))->toBeFalse(); }); it('string representation works correctly', function () { $value = 'test-lock-key'; $lockKey = LockKey::fromString($value); expect($lockKey->toString())->toBe($value); expect((string) $lockKey)->toBe($value); expect($lockKey->jsonSerialize())->toBe($value); }); it('withPrefix() creates new instance with prefix', function () { $original = LockKey::fromString('test-lock'); $prefixed = $original->withPrefix('system'); expect($prefixed->toString())->toBe('system.test-lock'); expect($original->toString())->toBe('test-lock'); // Original unchanged }); it('withSuffix() creates new instance with suffix', function () { $original = LockKey::fromString('test-lock'); $suffixed = $original->withSuffix('v2'); expect($suffixed->toString())->toBe('test-lock.v2'); expect($original->toString())->toBe('test-lock'); // Original unchanged }); it('can chain prefix and suffix', function () { $original = LockKey::fromString('lock'); $chained = $original->withPrefix('system')->withSuffix('temp'); expect($chained->toString())->toBe('system.lock.temp'); expect($original->toString())->toBe('lock'); // Original unchanged }); it('matches() supports pattern matching', function () { $lockKey = LockKey::fromString('job.email.batch-123'); expect($lockKey->matches('job.*'))->toBeTrue(); expect($lockKey->matches('job.email.*'))->toBeTrue(); expect($lockKey->matches('*.batch-123'))->toBeTrue(); expect($lockKey->matches('*email*'))->toBeTrue(); expect($lockKey->matches('worker.*'))->toBeFalse(); expect($lockKey->matches('*.queue.*'))->toBeFalse(); }); }); describe('Edge Cases', function () { it('handles minimum and maximum length keys', function () { $minKey = LockKey::fromString('x'); expect($minKey->toString())->toBe('x'); $maxKey = LockKey::fromString(str_repeat('a', 255)); expect($maxKey->toString())->toBe(str_repeat('a', 255)); }); it('validates prefix and suffix additions', function () { $baseKey = LockKey::fromString(str_repeat('a', 250)); // Near max length // This should work (255 chars total) $withShortPrefix = $baseKey->withPrefix('x'); expect(strlen($withShortPrefix->toString()))->toBe(252); // 'x' + '.' + 250 // This should fail (would exceed 255 chars) expect(fn() => $baseKey->withPrefix('toolong')) ->toThrow(\InvalidArgumentException::class); }); it('handles complex key structures', function () { $complexKey = LockKey::fromString('system.queue.email-processing.worker-123.batch-456'); expect($complexKey->matches('system.*'))->toBeTrue(); expect($complexKey->matches('*.queue.*'))->toBeTrue(); expect($complexKey->matches('*email-processing*'))->toBeTrue(); expect($complexKey->matches('*batch-456'))->toBeTrue(); }); }); }); describe('Distributed Lock Mock Implementation', function () { beforeEach(function () { // Create a mock distributed lock for testing $this->distributedLock = new class { private array $locks = []; public function acquire(LockKey $key, WorkerId $workerId, Duration $ttl): bool { $keyStr = $key->toString(); $now = time(); // Check if lock already exists and is not expired if (isset($this->locks[$keyStr])) { $lock = $this->locks[$keyStr]; if ($lock['expires_at'] > $now) { return false; // Lock already held } } // Acquire lock $this->locks[$keyStr] = [ 'worker_id' => $workerId->toString(), 'acquired_at' => $now, 'expires_at' => $now + $ttl->toSeconds(), 'ttl' => $ttl->toSeconds() ]; return true; } public function extend(LockKey $key, WorkerId $workerId, Duration $ttl): bool { $keyStr = $key->toString(); $now = time(); if (!isset($this->locks[$keyStr])) { return false; // Lock doesn't exist } $lock = $this->locks[$keyStr]; // Only the lock owner can extend it and it must not be expired if ($lock['worker_id'] !== $workerId->toString() || $lock['expires_at'] <= $now) { return false; } // Extend the lock $this->locks[$keyStr]['expires_at'] = $now + $ttl->toSeconds(); $this->locks[$keyStr]['ttl'] = $ttl->toSeconds(); return true; } public function release(LockKey $key, WorkerId $workerId): bool { $keyStr = $key->toString(); if (!isset($this->locks[$keyStr])) { return false; // Lock doesn't exist } $lock = $this->locks[$keyStr]; // Only the lock owner can release it if ($lock['worker_id'] !== $workerId->toString()) { return false; } unset($this->locks[$keyStr]); return true; } public function exists(LockKey $key): bool { $keyStr = $key->toString(); $now = time(); if (!isset($this->locks[$keyStr])) { return false; } $lock = $this->locks[$keyStr]; return $lock['expires_at'] > $now; } public function getLockInfo(LockKey $key): ?array { $keyStr = $key->toString(); $now = time(); if (!isset($this->locks[$keyStr])) { return null; } $lock = $this->locks[$keyStr]; if ($lock['expires_at'] <= $now) { return null; // Expired } return [ 'lock_key' => $keyStr, 'worker_id' => $lock['worker_id'], 'acquired_at' => date('Y-m-d H:i:s', $lock['acquired_at']), 'expires_at' => date('Y-m-d H:i:s', $lock['expires_at']), 'ttl_seconds' => $lock['expires_at'] - $now ]; } public function acquireWithTimeout(LockKey $key, WorkerId $workerId, Duration $ttl, Duration $timeout): bool { $startTime = microtime(true); $timeoutSeconds = $timeout->toSeconds(); while ((microtime(true) - $startTime) < $timeoutSeconds) { if ($this->acquire($key, $workerId, $ttl)) { return true; } usleep(100000); // 100ms } return false; } public function releaseAllWorkerLocks(WorkerId $workerId): int { $workerIdStr = $workerId->toString(); $released = 0; foreach ($this->locks as $key => $lock) { if ($lock['worker_id'] === $workerIdStr) { unset($this->locks[$key]); $released++; } } return $released; } public function cleanupExpiredLocks(): int { $now = time(); $cleaned = 0; foreach ($this->locks as $key => $lock) { if ($lock['expires_at'] <= $now) { unset($this->locks[$key]); $cleaned++; } } return $cleaned; } public function getLockStatistics(): array { $now = time(); $activeLocks = 0; $expiredLocks = 0; $workers = []; foreach ($this->locks as $lock) { if ($lock['expires_at'] > $now) { $activeLocks++; $workers[$lock['worker_id']] = true; } else { $expiredLocks++; } } return [ 'total_locks' => count($this->locks), 'active_locks' => $activeLocks, 'expired_locks' => $expiredLocks, 'unique_workers' => count($workers), 'avg_ttl_seconds' => $activeLocks > 0 ? array_sum(array_map(fn($lock) => $lock['ttl'], array_filter($this->locks, fn($lock) => $lock['expires_at'] > $now))) / $activeLocks : 0 ]; } }; }); describe('Basic Lock Operations', function () { it('can acquire and release locks', function () { $lockKey = LockKey::fromString('test-lock'); $workerId = WorkerId::fromString('worker-1'); $ttl = Duration::fromSeconds(300); // Initially lock should not exist expect($this->distributedLock->exists($lockKey))->toBeFalse(); // Acquire lock $acquired = $this->distributedLock->acquire($lockKey, $workerId, $ttl); expect($acquired)->toBeTrue(); // Lock should now exist expect($this->distributedLock->exists($lockKey))->toBeTrue(); // Get lock info $info = $this->distributedLock->getLockInfo($lockKey); expect($info)->not->toBeNull(); expect($info['worker_id'])->toBe('worker-1'); expect($info['ttl_seconds'])->toBeGreaterThan(299); // Release lock $released = $this->distributedLock->release($lockKey, $workerId); expect($released)->toBeTrue(); // Lock should no longer exist expect($this->distributedLock->exists($lockKey))->toBeFalse(); }); it('prevents double acquisition of same lock', function () { $lockKey = LockKey::fromString('exclusive-lock'); $worker1 = WorkerId::fromString('worker-1'); $worker2 = WorkerId::fromString('worker-2'); $ttl = Duration::fromSeconds(300); // Worker 1 acquires lock $acquired1 = $this->distributedLock->acquire($lockKey, $worker1, $ttl); expect($acquired1)->toBeTrue(); // Worker 2 cannot acquire same lock $acquired2 = $this->distributedLock->acquire($lockKey, $worker2, $ttl); expect($acquired2)->toBeFalse(); // Worker 1 cannot acquire it again $acquiredAgain = $this->distributedLock->acquire($lockKey, $worker1, $ttl); expect($acquiredAgain)->toBeFalse(); }); it('only allows lock owner to release lock', function () { $lockKey = LockKey::fromString('owner-lock'); $owner = WorkerId::fromString('owner-worker'); $other = WorkerId::fromString('other-worker'); $ttl = Duration::fromSeconds(300); // Owner acquires lock $this->distributedLock->acquire($lockKey, $owner, $ttl); // Other worker cannot release it $released = $this->distributedLock->release($lockKey, $other); expect($released)->toBeFalse(); // Lock should still exist expect($this->distributedLock->exists($lockKey))->toBeTrue(); // Owner can release it $released = $this->distributedLock->release($lockKey, $owner); expect($released)->toBeTrue(); // Lock should no longer exist expect($this->distributedLock->exists($lockKey))->toBeFalse(); }); }); describe('Lock Extension', function () { it('can extend lock TTL', function () { $lockKey = LockKey::fromString('extend-test'); $workerId = WorkerId::fromString('worker-1'); $initialTtl = Duration::fromSeconds(300); $extendedTtl = Duration::fromSeconds(600); // Acquire lock $this->distributedLock->acquire($lockKey, $workerId, $initialTtl); $initialInfo = $this->distributedLock->getLockInfo($lockKey); expect($initialInfo['ttl_seconds'])->toBeGreaterThan(299); // Extend lock $extended = $this->distributedLock->extend($lockKey, $workerId, $extendedTtl); expect($extended)->toBeTrue(); $extendedInfo = $this->distributedLock->getLockInfo($lockKey); expect($extendedInfo['ttl_seconds'])->toBeGreaterThan(599); }); it('only allows lock owner to extend lock', function () { $lockKey = LockKey::fromString('extend-owner-test'); $owner = WorkerId::fromString('owner-worker'); $other = WorkerId::fromString('other-worker'); $ttl = Duration::fromSeconds(300); // Owner acquires lock $this->distributedLock->acquire($lockKey, $owner, $ttl); // Other worker cannot extend it $extended = $this->distributedLock->extend($lockKey, $other, $ttl); expect($extended)->toBeFalse(); // Owner can extend it $extended = $this->distributedLock->extend($lockKey, $owner, $ttl); expect($extended)->toBeTrue(); }); it('cannot extend non-existent lock', function () { $lockKey = LockKey::fromString('non-existent-lock'); $workerId = WorkerId::fromString('worker-1'); $ttl = Duration::fromSeconds(300); $extended = $this->distributedLock->extend($lockKey, $workerId, $ttl); expect($extended)->toBeFalse(); }); }); describe('Lock Timeout and Acquisition', function () { it('can acquire lock with timeout', function () { $lockKey = LockKey::fromString('timeout-test'); $workerId = WorkerId::fromString('worker-1'); $ttl = Duration::fromSeconds(300); $timeout = Duration::fromSeconds(1); // Should acquire immediately since lock doesn't exist $start = microtime(true); $acquired = $this->distributedLock->acquireWithTimeout($lockKey, $workerId, $ttl, $timeout); $elapsed = microtime(true) - $start; expect($acquired)->toBeTrue(); expect($elapsed)->toBeLessThan(0.1); // Should be immediate }); it('times out when lock is held by another worker', function () { $lockKey = LockKey::fromString('timeout-fail-test'); $worker1 = WorkerId::fromString('worker-1'); $worker2 = WorkerId::fromString('worker-2'); $ttl = Duration::fromSeconds(300); $timeout = Duration::fromSeconds(0.5); // Worker 1 acquires lock $this->distributedLock->acquire($lockKey, $worker1, $ttl); // Worker 2 tries to acquire with timeout $start = microtime(true); $acquired = $this->distributedLock->acquireWithTimeout($lockKey, $worker2, $ttl, $timeout); $elapsed = microtime(true) - $start; expect($acquired)->toBeFalse(); expect($elapsed)->toBeGreaterThan(0.4); // Should have waited expect($elapsed)->toBeLessThan(0.7); // But not too long }); }); describe('Bulk Operations', function () { it('can release all locks for a worker', function () { $worker1 = WorkerId::fromString('worker-1'); $worker2 = WorkerId::fromString('worker-2'); $ttl = Duration::fromSeconds(300); // Worker 1 acquires multiple locks $lock1 = LockKey::fromString('lock-1'); $lock2 = LockKey::fromString('lock-2'); $lock3 = LockKey::fromString('lock-3'); $this->distributedLock->acquire($lock1, $worker1, $ttl); $this->distributedLock->acquire($lock2, $worker1, $ttl); $this->distributedLock->acquire($lock3, $worker2, $ttl); // Different worker // Verify locks exist expect($this->distributedLock->exists($lock1))->toBeTrue(); expect($this->distributedLock->exists($lock2))->toBeTrue(); expect($this->distributedLock->exists($lock3))->toBeTrue(); // Release all worker1 locks $released = $this->distributedLock->releaseAllWorkerLocks($worker1); expect($released)->toBe(2); // Worker1 locks should be gone, worker2 lock should remain expect($this->distributedLock->exists($lock1))->toBeFalse(); expect($this->distributedLock->exists($lock2))->toBeFalse(); expect($this->distributedLock->exists($lock3))->toBeTrue(); }); it('can cleanup expired locks', function () { $lockKey = LockKey::fromString('expiring-lock'); $workerId = WorkerId::fromString('worker-1'); $shortTtl = Duration::fromSeconds(1); // Acquire lock with short TTL $this->distributedLock->acquire($lockKey, $workerId, $shortTtl); expect($this->distributedLock->exists($lockKey))->toBeTrue(); // Wait for expiration sleep(2); // Lock should appear expired but still be in storage expect($this->distributedLock->exists($lockKey))->toBeFalse(); // Cleanup should remove expired locks $cleaned = $this->distributedLock->cleanupExpiredLocks(); expect($cleaned)->toBe(1); }); }); describe('Lock Statistics', function () { it('provides accurate lock statistics', function () { $worker1 = WorkerId::fromString('worker-1'); $worker2 = WorkerId::fromString('worker-2'); $ttl = Duration::fromSeconds(300); // Initially no locks $stats = $this->distributedLock->getLockStatistics(); expect($stats['total_locks'])->toBe(0); expect($stats['active_locks'])->toBe(0); expect($stats['unique_workers'])->toBe(0); // Add some locks $this->distributedLock->acquire(LockKey::fromString('lock-1'), $worker1, $ttl); $this->distributedLock->acquire(LockKey::fromString('lock-2'), $worker1, $ttl); $this->distributedLock->acquire(LockKey::fromString('lock-3'), $worker2, $ttl); $stats = $this->distributedLock->getLockStatistics(); expect($stats['total_locks'])->toBe(3); expect($stats['active_locks'])->toBe(3); expect($stats['unique_workers'])->toBe(2); expect($stats['avg_ttl_seconds'])->toBe(300.0); }); it('distinguishes between active and expired locks', function () { $workerId = WorkerId::fromString('worker-1'); $longTtl = Duration::fromSeconds(300); $shortTtl = Duration::fromSeconds(1); // Add active and soon-to-expire locks $this->distributedLock->acquire(LockKey::fromString('active-lock'), $workerId, $longTtl); $this->distributedLock->acquire(LockKey::fromString('expiring-lock'), $workerId, $shortTtl); // Initially both active $stats = $this->distributedLock->getLockStatistics(); expect($stats['active_locks'])->toBe(2); expect($stats['expired_locks'])->toBe(0); // Wait for one to expire sleep(2); $stats = $this->distributedLock->getLockStatistics(); expect($stats['total_locks'])->toBe(2); // Still in storage expect($stats['active_locks'])->toBe(1); // One still active expect($stats['expired_locks'])->toBe(1); // One expired }); }); }); describe('Distributed Lock Integration Scenarios', function () { beforeEach(function () { $this->distributedLock = new class { private array $locks = []; public function acquire(LockKey $key, WorkerId $workerId, Duration $ttl): bool { $keyStr = $key->toString(); $now = time(); if (isset($this->locks[$keyStr]) && $this->locks[$keyStr]['expires_at'] > $now) { return false; } $this->locks[$keyStr] = [ 'worker_id' => $workerId->toString(), 'acquired_at' => $now, 'expires_at' => $now + $ttl->toSeconds() ]; return true; } public function release(LockKey $key, WorkerId $workerId): bool { $keyStr = $key->toString(); if (!isset($this->locks[$keyStr]) || $this->locks[$keyStr]['worker_id'] !== $workerId->toString()) { return false; } unset($this->locks[$keyStr]); return true; } public function exists(LockKey $key): bool { $keyStr = $key->toString(); return isset($this->locks[$keyStr]) && $this->locks[$keyStr]['expires_at'] > time(); } }; $this->emailJob = new class { public function __construct( public string $batchId = 'email-batch-123', public int $recipientCount = 1000 ) {} }; $this->reportJob = new class { public function __construct( public string $reportId = 'monthly-sales-2024', public string $resourceType = 'database' ) {} }; }); it('demonstrates job-level locking for email batches', function () { $batchLockKey = LockKey::forBatch($this->emailJob->batchId); $worker1 = WorkerId::fromString('email-worker-1'); $worker2 = WorkerId::fromString('email-worker-2'); $processingTime = Duration::fromMinutes(30); // Worker 1 acquires lock for batch processing $acquired = $this->distributedLock->acquire($batchLockKey, $worker1, $processingTime); expect($acquired)->toBeTrue(); // Worker 2 cannot process the same batch $blocked = $this->distributedLock->acquire($batchLockKey, $worker2, $processingTime); expect($blocked)->toBeFalse(); // After processing, worker 1 releases the lock $released = $this->distributedLock->release($batchLockKey, $worker1); expect($released)->toBeTrue(); // Now worker 2 can acquire the lock $nowAvailable = $this->distributedLock->acquire($batchLockKey, $worker2, $processingTime); expect($nowAvailable)->toBeTrue(); }); it('demonstrates resource-level locking for reports', function () { $resourceLockKey = LockKey::forResource($this->reportJob->resourceType, 'primary'); $reportWorker = WorkerId::fromString('report-worker-1'); $maintenanceWorker = WorkerId::fromString('maintenance-worker'); $reportTime = Duration::fromMinutes(15); $maintenanceTime = Duration::fromHours(2); // Report worker acquires database resource $reportAcquired = $this->distributedLock->acquire($resourceLockKey, $reportWorker, $reportTime); expect($reportAcquired)->toBeTrue(); // Maintenance worker cannot access the resource $maintenanceBlocked = $this->distributedLock->acquire($resourceLockKey, $maintenanceWorker, $maintenanceTime); expect($maintenanceBlocked)->toBeFalse(); // Report completes and releases resource $reportReleased = $this->distributedLock->release($resourceLockKey, $reportWorker); expect($reportReleased)->toBeTrue(); // Maintenance can now proceed $maintenanceAcquired = $this->distributedLock->acquire($resourceLockKey, $maintenanceWorker, $maintenanceTime); expect($maintenanceAcquired)->toBeTrue(); }); it('demonstrates queue-level locking for exclusive processing', function () { $emailQueueKey = LockKey::forQueue(QueueName::fromString('email-queue')); $priorityWorker = WorkerId::fromString('priority-worker'); $normalWorker = WorkerId::fromString('normal-worker'); $exclusiveTime = Duration::fromMinutes(5); // Priority worker takes exclusive access to queue $exclusiveAcquired = $this->distributedLock->acquire($emailQueueKey, $priorityWorker, $exclusiveTime); expect($exclusiveAcquired)->toBeTrue(); // Normal worker must wait $normalBlocked = $this->distributedLock->acquire($emailQueueKey, $normalWorker, Duration::fromMinutes(1)); expect($normalBlocked)->toBeFalse(); // After priority processing, queue becomes available $exclusiveReleased = $this->distributedLock->release($emailQueueKey, $priorityWorker); expect($exclusiveReleased)->toBeTrue(); $normalAcquired = $this->distributedLock->acquire($emailQueueKey, $normalWorker, Duration::fromMinutes(10)); expect($normalAcquired)->toBeTrue(); }); it('demonstrates worker-level locking for maintenance operations', function () { $worker1 = WorkerId::fromString('maintenance-worker-1'); $worker1LockKey = LockKey::forWorker($worker1); $maintenanceSystem = WorkerId::fromString('maintenance-system'); $maintenanceTime = Duration::fromHours(1); // Worker is performing normal operations expect($this->distributedLock->exists($worker1LockKey))->toBeFalse(); // Maintenance system needs to pause the worker $maintenanceLock = $this->distributedLock->acquire($worker1LockKey, $maintenanceSystem, $maintenanceTime); expect($maintenanceLock)->toBeTrue(); // Worker cannot proceed with new jobs while maintenance lock is held $workerBlocked = $this->distributedLock->acquire($worker1LockKey, $worker1, Duration::fromMinutes(1)); expect($workerBlocked)->toBeFalse(); // After maintenance, worker can resume $maintenanceReleased = $this->distributedLock->release($worker1LockKey, $maintenanceSystem); expect($maintenanceReleased)->toBeTrue(); $workerResumed = $this->distributedLock->acquire($worker1LockKey, $worker1, Duration::fromMinutes(30)); expect($workerResumed)->toBeTrue(); }); it('demonstrates hierarchical locking patterns', function () { $systemLock = LockKey::fromString('system.maintenance'); $queueLock = LockKey::fromString('system.queue.email'); $jobLock = LockKey::fromString('system.queue.email.job-123'); $maintenanceWorker = WorkerId::fromString('maintenance-worker'); $queueWorker = WorkerId::fromString('queue-worker'); $jobWorker = WorkerId::fromString('job-worker'); // System-wide maintenance lock $systemAcquired = $this->distributedLock->acquire($systemLock, $maintenanceWorker, Duration::fromHours(1)); expect($systemAcquired)->toBeTrue(); // Queue-level operations should be blocked during system maintenance $queueBlocked = $this->distributedLock->acquire($queueLock, $queueWorker, Duration::fromMinutes(30)); expect($queueBlocked)->toBeFalse(); // Job-level operations should also be blocked $jobBlocked = $this->distributedLock->acquire($jobLock, $jobWorker, Duration::fromMinutes(5)); expect($jobBlocked)->toBeFalse(); // After system maintenance $this->distributedLock->release($systemLock, $maintenanceWorker); // Lower-level operations can proceed $queueNowAcquired = $this->distributedLock->acquire($queueLock, $queueWorker, Duration::fromMinutes(30)); expect($queueNowAcquired)->toBeTrue(); }); });