icom/ framework

Background work that survives a process.

A persistent SQL queue with ownership leases, bounded workers and visible failure recovery.

Register trusted handlers

config/jobs.php maps an explicit name to trusted application code. Payloads are JSON up to 64 KiB; they never name an executable PHP class. Inject services into the handler as usual. A new container scope is created and cleared for every job.

example
// config/jobs.php
return [
    'system.ping' => static function (array $payload, \Psr\Log\LoggerInterface $logger): void {
        $logger->info('queue.ping');
    },
];

Push and run

example
php bin/icom queue:push system.ping '{"source":"cli"}'
php bin/icom queue:work default 100 300 --lease=120 --memory=128 --stop-when-empty
php bin/icom queue:status
php bin/icom queue:failed
php bin/icom queue:retry FAILED_JOB_ID

// From trusted application code:
$queue = new \Icom\Queue\DatabaseQueue($db);
$id = $queue->push('system.ping', ['source' => 'app'],
    queue: 'default', delay: 10, maxAttempts: 5);

Delivery contract

Delivery is at least once. A worker atomically claims a due row using a unique lease token; stale or expired owners cannot acknowledge, release or extend it. Process loss makes the job available after lease expiry. A crashed final attempt becomes failed when another worker scans the queue. Handlers must tolerate duplicate delivery: use an application idempotency key and database uniqueness for effects. A lease cannot undo an external payment/email already sent.

Leases and bounded execution

Reservations default to 60 seconds; queue:work accepts --lease=120 and --memory=128 (MiB; 0 disables memory retirement). Long handlers must explicitly extend ownership with DatabaseQueue::heartbeat($job, seconds: 60), using the injected ReservedJob, or arrange shorter tasks. Automatic heartbeats and hard job cancellation are not provided. Worker job/time/memory bounds are checked between jobs; they do not interrupt a stuck handler. SIGTERM/SIGINT stop admission after the current job when pcntl is installed. Configure supervisor stop-grace accordingly.

Failures and operations

Exceptions use exponential retry delay starting at 5 seconds, capped at one hour. Exhausted attempts and malformed payloads become failed rows. Failure inspection omits payloads and exception messages, retaining exception class only. Retry resets attempts and makes the failed job available again. The UTC scheduler and transactional outbox are included in 0.5. A dashboard, dead-letter exporter, automatic failed-job pruning and external broker integration remain separate work. SQLite and MariaDB contention with competing workers are tested. Standalone queue operations retry a confirmed InnoDB deadlock up to four times with jitter; outer transactions and connection/ambiguous-commit errors are not retried. Production topology and other SQL engines still require tests.

Developer preview — benchmark claims require reproducible evidence.

Explore the architecture