icom/ framework

Schedule durable work in UTC.

Named schedules produce deduplicated outbox occurrences; registered queue handlers do the work.

Declare the schedule

config/schedule.php returns up to 100 immutable Schedule objects. Each name is unique and each job handler must be registered in config/jobs.php. Intervals and offsets use whole minutes; hourly/daily helpers use UTC. The default schedules prune expired identity records at minute 17, sessions at minute 23, and published outbox tombstones daily at 02:00 UTC, all through the maintenance queue.

example
use Icom\Queue\JobDefinition;
use Icom\Scheduling\Schedule;

return [
    new Schedule('catalog.refresh.v1',
        new JobDefinition('catalog.refresh', queue: 'catalog'), 300),
    Schedule::daily('report.daily.v1',
        new JobDefinition('report.build', queue: 'reports'), 2, 30),
];

Slots, downtime and changes

A short transaction claims the latest due UTC slot and records its outbox message together. Concurrent invocations enqueue one occurrence for that name/slot. First startup or recovery after downtime coalesces to the latest past slot; it does not replay every missed interval. Clock rollback cannot replay a slot already recorded. A persisted fingerprint rejects changes to the job/interval/offset under the same name: use a new versioned name when changing a schedule. Removal stops new occurrences but does not cancel already queued work. Persistent cursors are not automatically deleted. Keep clocks synchronized across processes.

Inspect and dispatch

example
php bin/icom schedule:list
php bin/icom schedule:run
php bin/icom outbox:relay 100
php bin/icom queue:work reports 100 300 --stop-when-empty

# Default maintenance pipeline: tick, relay <=100, run <=20 jobs / 40s.
php bin/icom maintenance:run

Install one hosting cron

Back up crontab first. Replace only the old framework security/session pruning lines with this command, preserving all unrelated applications. maintenance:run relays all outbox queues but executes only maintenance jobs; supervise separate workers for application queues. The optional local flock below prevents overlapping cron processes on this host. It does not create a distributed execution mutex.

example
* * * * * /usr/bin/flock -n /home/icom/icom-framework/shared/storage/maintenance.lock /opt/cpanel/ea-php84/root/usr/bin/php /home/icom/icom-framework/current/bin/icom maintenance:run >> /home/icom/icom-framework/shared/storage/logs/maintenance.log 2>&1

Execution and monitoring

The scheduler deduplicates enqueue operations, not running handlers. Different occurrences may execute concurrently, and queue retries may repeat one job. Make handlers idempotent and use a resource-specific lock if required. Worker time/job/memory bounds apply between jobs, so custom blocking handlers need their own deadlines, heartbeat and supervisor. Use ops:check and ops:metrics from the Operations chapter for queue eligibility/age, outbox lag and schedule stalls; inspect failed rows and logs; rotate the maintenance log. No arbitrary cron parser, DST/local-time scheduling, email delivery, dashboard or remote shell execution is included.

Repeat the relevant tests

The full suite includes SQLite/MariaDB producer/relay/scheduler races, rollback, malformed records and process exit around the relay commit. A filtered run is explicitly labeled and is not a full regression result.

example
composer test
ICOM_TEST_FILTER=Background php tests/run.php
# A filter matching zero tests exits with status 2.

Developer preview — benchmark claims require reproducible evidence.

Explore the architecture