# Queue Daemon (`queue:run --daemon`)

How to run the custom queue as a long-lived worker instead of a per-minute
one-shot, and how to keep it alive on each supported OS.

This drives the **custom** queue (`Services\Helpers\QueueManager`), not Laravel's
built-in queue (which has no database driver on Laravel 4.1 anyway).

## Two ways to run

| Mode | Command | Behavior |
|------|---------|----------|
| One-shot (default, unchanged) | `php artisan queue:run` | Processes a single job, then exits. Schedule every minute via cron / Task Scheduler. |
| Daemon | `php artisan queue:run --daemon` | Stays resident, drains the queue continuously, exits cleanly when a lifecycle ceiling is hit so a supervisor restarts it. |

**Pick one per machine — never both.** If you run the daemon, disable that
machine's per-minute one-shot cron / Task Scheduler entry, or they will fight
over the same jobs.

## Configuration

All defaults live in `app/config/queue_daemon.php` and are overridable from
`.env` (keys mirrored in `.env.example`):

| `.env` key | Default | Meaning |
|------------|---------|---------|
| `QUEUE_DAEMON_ENABLED` | `true` | Master switch. `false` makes `--daemon` refuse to start. |
| `QUEUE_DAEMON_SLEEP` | `10` | Seconds to sleep when the queue is empty. |
| `QUEUE_DAEMON_MEMORY` | `128` | MB ceiling; clean restart when exceeded. |
| `QUEUE_DAEMON_MAX_JOBS` | `1000` | Jobs per process life; clean restart after. |
| `QUEUE_DAEMON_MAX_TIME` | `3600` | Seconds of uptime; clean restart after. |
| `QUEUE_DAEMON_LOCK_FILE` | `meta/queue-daemon.lock` | Single-instance lock, relative to `storage_path()`. |
| `QUEUE_DAEMON_HEARTBEAT` | `60` | Seconds between "alive" lines in `queue.log` (`0` disables). |
| `QUEUE_DAEMON_REAPER_ENABLED` | `true` | Reset jobs wedged in `processing`. |
| `QUEUE_DAEMON_REAPER_TIMEOUT` | `3600` | A `processing` row older than this (seconds) is reset to `pending` (or `failed` past `jobs.maxAttempts`). |

CLI flags override config per run: `--sleep`, `--memory`, `--max-jobs`, `--max-time`.
After editing `.env`, clear the env cache: `php artisan cache:clear`.

## Why it restarts itself

A long-lived PHP process leaks memory and holds stale code after a deploy. The
daemon deliberately exits when it crosses `memory`, `max-jobs`, or `max-time`,
and the supervisor relaunches it fresh. Exits are status `0` (planned) so the
supervisor does not treat them as failures. On startup it reaps any jobs a
previously-killed worker left stuck in `processing`.

## Supervision

**Quick setup:** the `deployment_scripts/` folder has ready-made installers —
`afaq_queues_as_service.sh` (Ubuntu/AlmaLinux, auto-detects the OS and installs
Supervisor) and `afaq_queues_as_service.ps1` (Windows). See `deployment_scripts/README.md`.
The manual configs below are for reference or custom setups.

### AlmaLinux / LAMP — systemd (preferred)

`/etc/systemd/system/afaq-queue.service`:

```ini
[Unit]
Description=Afaq custom queue daemon
After=network.target mysqld.service

[Service]
Type=simple
User=apache
WorkingDirectory=/var/www/afaq
ExecStart=/usr/bin/php artisan queue:run --daemon
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now afaq-queue
sudo systemctl status afaq-queue
```

`systemctl stop` sends SIGTERM; the daemon finishes its current job, then exits.

### AlmaLinux — supervisord (alternative)

```ini
[program:afaq-queue]
command=/usr/bin/php artisan queue:run --daemon
directory=/var/www/afaq
user=apache
autostart=true
autorestart=true
numprocs=1
stopsignal=TERM
```

### Windows (IIS / Laragon) — NSSM (preferred)

`pcntl` does not exist on Windows, so graceful SIGTERM is unavailable; the
lifecycle ceilings (memory / max-jobs / max-time) are the clean stop, and the
single-instance lock prevents duplicates.

```powershell
nssm install AfaqQueue "C:\php\php.exe" "artisan queue:run --daemon"
nssm set AfaqQueue AppDirectory "D:\LiveProjects\Laragonwww\afaq"
nssm set AfaqQueue AppExit Default Restart
nssm start AfaqQueue
```

### Windows — guarded Task Scheduler (alternative)

Keep a per-minute task that runs `php artisan queue:run --daemon`. The
single-instance lock means a second launch exits immediately, so the scheduler
effectively becomes the "restart if not running" mechanism.

## Observability

- `storage/logs/queue_YYYY_MM_DD.log` — startup, heartbeat, stop reason, per-job
  lines, and reaper activity.
- Heartbeat line example: `Alive: processed=42, uptime=900s, mem=96MB.`

## Rollback

Because the one-shot path is untouched, rollback is instant:

1. Stop the service (`systemctl stop afaq-queue` / `nssm stop AfaqQueue`).
2. Re-enable the per-minute `php artisan queue:run` cron / Task Scheduler entry.

## Known limitations

- **Single machine only.** The lock and the reaper assume one daemon per host.
  Running daemons on multiple hosts against the same DB can double-process a job,
  because `QueueManager::executeNextJob()` claims rows with a non-atomic
  read-then-write. Multi-host needs the atomic-claim change (Phase 3b in
  `tmp/queue-daemon-plan.txt`), which is intentionally not yet implemented.
