robotomarvin/symfony-process-manager
A lightweight PHP manager for Symfony Process that simplifies running, tracking, and controlling multiple concurrent processes. Handle output, errors, exit codes, and lifecycle events through a clean, fluent API.
This release (v0.0.2) is a breaking update for anyone on v0.0.1: the config root key and several metric labels changed. Treat the upgrade as a code+config+dashboard change, not a drop-in bump.
transports: becomes consumers:. Each consumer now owns one or more transports::// config/... (package config)
'consumers' => [
'default' => [
'transports' => 'default', // scalar: single transport
'autoscaler' => ['strategy' => 'fixed', 'workers' => 2],
],
'priority' => [
'transports' => ['main', 'failed'], // list: main drained first, failed as fallback
],
],
messenger:consume t1 t2 ..., so list order is your scheduling policy, not a detail./metrics. AutoscalerLoop::emitPoolGauges() now runs at start(), so gauges exist immediately — you can validate a deploy without waiting for the first periodic tick./metrics, import the bundled dashboard, confirm consumer (not transport) is the template variable.Priority vs. fair sharing is a config decision, not a runtime one. The worker drains transports in list order. Use lists only for primary/fallback:
'transports' => ['main', 'failed'], // main first; 'failed' only gets attention when main is empty
For genuine sharing (two transports both deserve ~50% throughput), declare two consumers, each with one transport. A single consumer with ['t1', 't2'] will starve t2 under sustained t1 load — that's the intended semantics.
Migration workflow (do it in this order):
# 1. config rename
grep -rn "transports:" config/ # find root-level usage, not per-consumer lists
# 2. metric label rename in dashboards/alerts
grep -rn "transport" dashboards/ monitoring/ alerts/
# 3. re-import bundled dashboard if you imported it ($transport -> $consumer)
Update PromQL deliberately. Lifecycle counters are now {consumer} only; per-transport fan-out was removed on purpose so rate() doesn't multiply by transport-list length:
sum by (consumer) (rate(worker_failures_total[5m]))
sum by (consumer) (rate(worker_backoffs_total[5m]))
sum by (consumer, exit_code) (increase(worker_exits_total[1h]))
Keep transport where it's still real — messages_processed_total and worker_busy source it from WorkerMessage*Event::getReceiverName(), and transport-level failure visibility is still:
sum by (transport, message_class) (rate(messenger_messages_failed_total[5m]))
Autoscaler visibility pattern. Because pool gauges are emitted at start(), a Fixed-strategy pool is observable from second zero. Use this for deploy verification: after a rollout, assert autoscaler_target_workers{consumer="x"} equals expected before trusting the HPA/alerting layer.
Log correlation. Worker logs are prefixed [worker N consumer-label] and JSON context carries extra.consumer. If you parse worker logs, update the regex/parser; if you aggregate by consumer in Loki/CloudWatch, key on extra.consumer rather than the prefix string.
transports: root key means consumers you think you declared don't exist. After upgrading, dump the resolved worker command per consumer and confirm each one is what you expect before deploying.transport on lifecycle counters return no data rather than erroring. Search for the label, not for a broken panel — an unrelated Grafana refresh won't surface it.$transport template variable is gone from the bundled dashboard. Re-import; don't hand-edit, since panels were reflowed too.absent() alerts will change behaviour. Pool gauges now exist immediately, so alerts written as absent(autoscaler_target_workers{...}) will stop firing — that's a fix, but it also means they no longer catch a genuinely dead autoscaler loop. Re-scope to a staleness check on the metric timestamp if you relied on that.worker_exits_total{consumer, exit_code} — filter exit_code!="0" for crash alerting. Cardinality is bounded, and the per-code breakdown is worth keeping for distinguishing OOM kills from graceful exits.worker_starts_total etc. tell you which consumer restarted, not which transport inside it. If that granularity matters, one consumer per transport.[worker N] form will misfire; anchor on consumer instead.WorkerMessage*Event payloads carry the real receiver name, which is why messages_processed_total kept its transport label. If you add custom metrics or listeners, source transport from there rather than from consumer config, or you'll reintroduce the fan-out problem this release removed.How can I help you explore Laravel packages today?