Geezer¶
What is Geezer¶
Graceful shutdown handling with signal management
Leadership election for distributed coordination
Memory management and garbage collection
Exponential backoff and wait strategies
Process state management and fault tolerance
Features and characteristics:
Built specifically for long-running PHP processes
Integrated with the recruiterphp/concurrency library for distributed locking
Signal handling for graceful shutdowns (SIGINT, SIGTERM, SIGHUP, SIGQUIT)
Leadership strategies for single-instance processes in distributed environments
Configurable wait strategies with exponential backoff
Memory leak prevention with periodic garbage collection
Structured logging with process information
At high level, it provides these major components:
A RobustCommand: interface for implementing resilient long-running commands
A RobustCommandRunner: Symfony Console command wrapper that adds robustness features
Leadership strategies: for coordinating multiple instances of the same process
Wait strategies: for implementing backoff and pause behavior
Hello World¶
RobustCommand interface and run it with RobustCommandRunner.<?php
use Recruiter\Geezer\Command\RobustCommand;
use Recruiter\Geezer\Command\RobustCommandRunner;
use Recruiter\Geezer\Leadership\Anarchy;
use Recruiter\Geezer\Timing\ConstantPause;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Psr\Log\NullLogger;
class MyLongRunningProcess implements RobustCommand
{
private bool $shouldTerminate = false;
public function leadershipStrategy(): LeadershipStrategy
{
return new Anarchy(); // No leadership coordination
}
public function waitStrategy(): WaitStrategy
{
return new ConstantPause(5000); // 5 second pause between iterations
}
public function name(): string
{
return 'my:long-running-process';
}
public function description(): string
{
return 'A robust long-running process example';
}
public function definition(): InputDefinition
{
return new InputDefinition();
}
public function init(InputInterface $input): void
{
// Initialize based on input parameters
}
public function hasTerminated(): bool
{
return $this->shouldTerminate;
}
public function execute(): bool
{
// Your business logic here
echo "Processing...\n";
// Return true if work was done, false if nothing to do
return true;
}
public function shutdown(?\Throwable $e = null): bool
{
echo "Shutting down gracefully...\n";
return true;
}
}
// Create and run the robust command
$command = new MyLongRunningProcess();
$runner = new RobustCommandRunner($command, new NullLogger());
This creates a long-running process that:
Runs in a loop, executing the
execute()method repeatedlyWaits 5 seconds between iterations
Handles shutdown signals gracefully
Logs process information
Manages memory with periodic garbage collection
Leadership Strategies¶
Anarchy¶
Anarchy strategy provides no coordination - every process instance will run independently.<?php
use Recruiter\Geezer\Leadership\Anarchy;
public function leadershipStrategy(): LeadershipStrategy
{
return new Anarchy();
}
Dictatorship¶
Dictatorship strategy uses distributed locking to ensure only one process instance is active.<?php
use Recruiter\Geezer\Leadership\Dictatorship;
use Recruiter\Concurrency\MongoLock;
use Recruiter\Concurrency\MongoLockRepository;
$mongoCollection = $mongodb->selectCollection('locks', 'geezer_locks');
$lockRepository = new MongoLockRepository($mongoCollection);
$lock = new MongoLock($lockRepository, 'my-process-lock');
public function leadershipStrategy(): LeadershipStrategy
{
return new Dictatorship($lock, 60); // 60 second term of office
}
Dictatorship strategy:Attempts to acquire the lock before allowing process execution
Refreshes the lock periodically during execution
Releases the lock on shutdown
If lock acquisition fails, the process waits and retries
Wait Strategies¶
Iterator interface to provide flexible timing behavior.ConstantPause¶
<?php
use Recruiter\Geezer\Timing\ConstantPause;
public function waitStrategy(): WaitStrategy
{
return new ConstantPause(1000); // 1 second pause
}
ExponentialBackoffStrategy¶
<?php
use Recruiter\Geezer\Timing\ExponentialBackoffStrategy;
public function waitStrategy(): WaitStrategy
{
return new ExponentialBackoffStrategy(100, 5000); // From 100ms to 5s max
}
Starts with no wait on the first iteration
Doubles the wait time on each subsequent iteration
Caps at the maximum specified value
Resets to zero when
execute()returnstrue(indicating successful work)
This pattern is ideal for processes that poll external resources - when work is available, the process responds quickly, but when idle, it gradually backs off to reduce resource usage.
RobustCommand Interface¶
RobustCommand interface defines the contract for long-running processes.Key Methods¶
execute(): boolThe main business logic. Return
trueif work was performed,falseif nothing was done. When returningtrue, wait strategies will reset their backoff.hasTerminated(): boolReturn
truewhen the process should stop running. This allows for graceful termination based on business logic (e.g., processing a finite queue).shutdown(?\Throwable $e = null): boolCalled when the process is shutting down, either gracefully or due to an exception. Perform cleanup operations here.
leadershipStrategy(): LeadershipStrategyReturn the leadership coordination strategy for this process.
waitStrategy(): WaitStrategyReturn the timing strategy for pauses between execution cycles.
Leadership Events¶
LeadershipEventsHandler to receive notifications about leadership changes:<?php
use Recruiter\Geezer\Command\LeadershipEventsHandler;
class MyProcess implements RobustCommand, LeadershipEventsHandler
{
public function leadershipAcquired(): void
{
echo "I am now the leader!\n";
}
public function leadershipLost(): void
{
echo "Leadership lost, stepping down...\n";
}
}
RobustCommandRunner¶
RobustCommandRunner is a Symfony Console command that wraps your RobustCommandFeatures:
Signal Handling: Gracefully handles SIGINT, SIGTERM, SIGHUP, and SIGQUIT
Leadership Election: Manages leadership acquisition and release
Memory Management: Performs garbage collection every 100 cycles
Structured Logging: Includes hostname, PID, and process name in log messages
Exception Handling: Catches and logs exceptions, ensuring graceful shutdown
Usage with Symfony Console Application:
<?php
use Symfony\Component\Console\Application;
use Psr\Log\LoggerInterface;
$application = new Application();
$logger = // ... your PSR-3 logger
$command = new MyLongRunningProcess();
$runner = new RobustCommandRunner($command, $logger);
$application->add($runner);
$application->run();
Production Considerations¶
Memory Management¶
Explicitly unsetting large variables after use
Using generators for processing large datasets
Monitoring memory usage and implementing additional cleanup in your
execute()method
Signal Handling¶
Leadership Coordination¶
Dictatorship with distributed locking:Choose appropriate lock timeout values based on your execution cycle duration
Monitor lock acquisition failures in your logs
Consider lock refresh failures as indication of network or database issues
Logging¶
Hostname and process ID for debugging in distributed environments
Timestamp and program name for correlation
Leadership status changes for coordination debugging
Example log output:
[hostname:12345] Leadership election
[hostname:12345] Leadership status changed in: `acquired`
[hostname:12345] Processing work...