- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
260 lines
9.7 KiB
PHP
260 lines
9.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../src/Framework/Database/Locks/LockKey.php';
|
|
require_once __DIR__ . '/../../src/Framework/Database/Locks/AdvisoryLockService.php';
|
|
|
|
use App\Framework\Database\Locks\LockKey;
|
|
use App\Framework\Database\Locks\AdvisoryLockService;
|
|
|
|
echo "Testing PostgreSQL Advisory Locks\n";
|
|
echo "==================================\n\n";
|
|
|
|
try {
|
|
// Create two separate PDO connections to simulate concurrent access
|
|
$pdo1 = new PDO(
|
|
'pgsql:host=db;dbname=michaelschiemer',
|
|
'postgres',
|
|
'StartSimple2024!'
|
|
);
|
|
$pdo1->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);
|
|
}
|