setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo2 = new PDO( 'pgsql:host=db;dbname=michaelschiemer', 'postgres', 'StartSimple2024!' ); $pdo2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); echo "✅ Two database connections established\n\n"; $lockService1 = new AdvisoryLockService($pdo1); $lockService2 = new AdvisoryLockService($pdo2); // Test 1: Basic lock acquisition and release echo "Test 1: Basic Lock Acquisition and Release\n"; echo "===========================================\n"; $key1 = LockKey::fromString('test-resource-1'); echo "Lock key generated: {$key1->toInt()}\n"; // Connection 1 acquires lock $acquired = $lockService1->tryLock($key1); echo $acquired ? "✅ Connection 1: Lock acquired\n" : "❌ Connection 1: Failed to acquire lock\n"; // Connection 2 tries to acquire same lock (should fail) $acquired2 = $lockService2->tryLock($key1); echo !$acquired2 ? "✅ Connection 2: Lock correctly blocked\n" : "❌ Connection 2: Should not have acquired lock\n"; // Connection 1 releases lock $released = $lockService1->unlock($key1); echo $released ? "✅ Connection 1: Lock released\n" : "❌ Connection 1: Failed to release lock\n"; // Connection 2 tries again (should succeed now) $acquired2 = $lockService2->tryLock($key1); echo $acquired2 ? "✅ Connection 2: Lock acquired after release\n" : "❌ Connection 2: Failed to acquire lock\n"; $lockService2->unlock($key1); echo "\n"; // Test 2: Blocking lock behavior echo "Test 2: Non-Blocking vs Blocking Locks\n"; echo "=======================================\n"; $key2 = LockKey::fromInt(12345); // Connection 1 acquires lock $lockService1->tryLock($key2); echo "✅ Connection 1: Lock acquired (non-blocking)\n"; // Connection 2 tries non-blocking (should fail immediately) $start = microtime(true); $acquired2 = $lockService2->tryLock($key2); $duration = microtime(true) - $start; echo !$acquired2 ? "✅ Connection 2: tryLock() failed immediately (took " . round($duration * 1000, 2) . "ms)\n" : "❌ Should not acquire lock\n"; $lockService1->unlock($key2); echo "✅ Connection 1: Lock released\n\n"; // Test 3: Transaction-scoped locks echo "Test 3: Transaction-Scoped Locks (Auto-Release)\n"; echo "================================================\n"; $key3 = LockKey::fromString('transaction-lock'); // Connection 1 begins transaction and acquires transaction lock $pdo1->beginTransaction(); $lockService1->lockTransaction($key3); echo "✅ Connection 1: Transaction lock acquired\n"; // Connection 2 tries to acquire (should fail) $acquired2 = $lockService2->tryLockTransaction($key3); echo !$acquired2 ? "✅ Connection 2: Transaction lock correctly blocked\n" : "❌ Should not acquire lock\n"; // Connection 1 commits (auto-releases lock) $pdo1->commit(); echo "✅ Connection 1: Transaction committed (lock auto-released)\n"; // Connection 2 tries again (should succeed now) $pdo2->beginTransaction(); $acquired2 = $lockService2->tryLockTransaction($key3); echo $acquired2 ? "✅ Connection 2: Transaction lock acquired after auto-release\n" : "❌ Failed to acquire lock\n"; $pdo2->commit(); echo "\n"; // Test 4: withLock helper method echo "Test 4: withLock() Helper Method\n"; echo "=================================\n"; $key4 = LockKey::fromString('helper-lock'); $counter = 0; $result = $lockService1->withLock($key4, function() use (&$counter) { $counter++; echo "✅ Inside withLock() callback: counter = {$counter}\n"; return "operation-result"; }); echo "✅ withLock() returned: {$result}\n"; echo "✅ Counter after operation: {$counter}\n"; // Verify lock was released $acquired2 = $lockService2->tryLock($key4); echo $acquired2 ? "✅ Lock was automatically released by withLock()\n" : "❌ Lock should have been released\n"; $lockService2->unlock($key4); echo "\n"; // Test 5: tryWithLock helper (non-blocking) echo "Test 5: tryWithLock() Helper (Non-Blocking)\n"; echo "============================================\n"; $key5 = LockKey::fromString('try-helper-lock'); // Connection 1 holds lock $lockService1->lock($key5); echo "✅ Connection 1: Lock acquired\n"; // Connection 2 tries with tryWithLock (should return null) $result2 = $lockService2->tryWithLock($key5, function() { echo "❌ This callback should NOT execute\n"; return "should-not-happen"; }); echo ($result2 === null) ? "✅ Connection 2: tryWithLock() returned null (lock held by other connection)\n" : "❌ Should have returned null\n"; $lockService1->unlock($key5); echo "✅ Connection 1: Lock released\n"; // Now Connection 2 tries again (should succeed) $result2 = $lockService2->tryWithLock($key5, function() { echo "✅ Inside tryWithLock() callback\n"; return "success"; }); echo ($result2 === "success") ? "✅ Connection 2: tryWithLock() executed and returned result\n" : "❌ Should have executed callback\n"; echo "\n"; // Test 6: Lock key generation methods echo "Test 6: Lock Key Generation Methods\n"; echo "====================================\n"; $stringKey = LockKey::fromString('my-resource'); echo "✅ String-based key: {$stringKey->toInt()}\n"; $intKey = LockKey::fromInt(999999); echo "✅ Integer-based key: {$intKey->toInt()}\n"; $pairKey = LockKey::fromPair(100, 200); echo "✅ Pair-based key: {$pairKey->toInt()}\n"; // Verify same string produces same key (deterministic) $stringKey2 = LockKey::fromString('my-resource'); echo $stringKey->equals($stringKey2) ? "✅ String keys are deterministic (same string = same key)\n" : "❌ Keys should match\n"; echo "\n"; // Test 7: withTransactionLock helper echo "Test 7: withTransactionLock() Helper\n"; echo "====================================\n"; $key7 = LockKey::fromString('transaction-helper'); // Create a test table for transaction $pdo1->exec("DROP TABLE IF EXISTS test_lock_counter"); $pdo1->exec("CREATE TABLE test_lock_counter (value INTEGER)"); $pdo1->exec("INSERT INTO test_lock_counter VALUES (0)"); $result = $lockService1->withTransactionLock($key7, function() use ($pdo1) { // Read current value $stmt = $pdo1->query("SELECT value FROM test_lock_counter"); $current = $stmt->fetch(PDO::FETCH_ASSOC)['value']; // Increment $pdo1->exec("UPDATE test_lock_counter SET value = " . ($current + 1)); echo "✅ Inside withTransactionLock(): incremented counter from {$current} to " . ($current + 1) . "\n"; return $current + 1; }); echo "✅ withTransactionLock() committed and returned: {$result}\n"; // Verify transaction was committed $stmt = $pdo1->query("SELECT value FROM test_lock_counter"); $final = $stmt->fetch(PDO::FETCH_ASSOC)['value']; echo "✅ Final counter value in database: {$final}\n"; // Cleanup $pdo1->exec("DROP TABLE test_lock_counter"); echo "\n"; // Test 8: unlockAll() functionality echo "Test 8: unlockAll() Functionality\n"; echo "==================================\n"; $key8a = LockKey::fromString('multi-lock-1'); $key8b = LockKey::fromString('multi-lock-2'); $key8c = LockKey::fromString('multi-lock-3'); // Acquire multiple locks $lockService1->lock($key8a); $lockService1->lock($key8b); $lockService1->lock($key8c); echo "✅ Connection 1: Acquired 3 locks\n"; // Verify locks are held $locked = !$lockService2->tryLock($key8a) && !$lockService2->tryLock($key8b) && !$lockService2->tryLock($key8c); echo $locked ? "✅ Connection 2: All 3 locks are correctly held by Connection 1\n" : "❌ Locks should be held\n"; // Release all locks at once $lockService1->unlockAll(); echo "✅ Connection 1: Released all locks with unlockAll()\n"; // Verify all locks are released $released = $lockService2->tryLock($key8a) && $lockService2->tryLock($key8b) && $lockService2->tryLock($key8c); echo $released ? "✅ Connection 2: All 3 locks were successfully released\n" : "❌ All locks should be released\n"; $lockService2->unlock($key8a); $lockService2->unlock($key8b); $lockService2->unlock($key8c); echo "\n"; echo "✅ All Advisory Lock tests passed!\n"; echo "\nSummary:\n"; echo "========\n"; echo "✅ Basic lock acquisition and release works\n"; echo "✅ Non-blocking tryLock() works correctly\n"; echo "✅ Transaction-scoped locks auto-release on commit\n"; echo "✅ withLock() helper method works\n"; echo "✅ tryWithLock() non-blocking helper works\n"; echo "✅ Multiple lock key generation methods work\n"; echo "✅ withTransactionLock() helper with auto-commit works\n"; echo "✅ unlockAll() releases multiple locks\n"; } catch (\Exception $e) { echo "❌ Error: " . $e->getMessage() . "\n"; echo "Stack trace:\n" . $e->getTraceAsString() . "\n"; exit(1); }