phalcon/cphalcon · warning · Phalcon\Acl\Exceptions\CircularInheritanceError

Role '{roleName}' (to inherit) produces an infinite loop

Error message

Role '{roleName}' (to inherit) produces an infinite loop

What it means

Queue producers extend Phalcon\Queue\Adapter\AbstractProducer, whose default setDeliveryDelay() treats any non-null argument as unsupported and throws DeliveryDelayNotSupportedException. A null value (the default) is accepted and is a no-op; only requesting an actual delay triggers the throw. Transports that do support delayed delivery override this method.

Source

Thrown at phalcon/Acl/Adapter/Memory.zep:368

                 * Walk the inheritance queue with an integer cursor instead
                 * of `array_shift`. New roles enqueued by the body land at
                 * the end of `checkRoleToInherits`, so advancing `pendingIndex`
                 * preserves FIFO order without paying `array_shift`'s O(n)
                 * reindex per pop.
                 */
                let pendingIndex = 0;
                while pendingIndex < count(checkRoleToInherits) {
                    let checkRoleToInherit = checkRoleToInherits[pendingIndex];
                    let pendingIndex++;

                    if isset usedRoleToInherits[checkRoleToInherit] {
                        continue;
                    }

                    let usedRoleToInherits[checkRoleToInherit] = true;

                    if unlikely roleName == checkRoleToInherit {
                        throw new CircularInheritanceError(roleInheritName);
                    }

                    /**
                     * Push inherited roles
                     */
                    if isset this->roleInherits[checkRoleToInherit] {
                        for usedRoleToInherit in this->roleInherits[checkRoleToInherit] {
                            array_push(checkRoleToInherits, usedRoleToInherit);
                        }
                    }
                }
            }

            let this->roleInherits[roleName][] = roleInheritName;
        }

        return true;
    }

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Do not call setDeliveryDelay() on transports without delay support, or call it with null explicitly.
  2. Check whether your concrete producer class overrides setDeliveryDelay() before relying on the feature.
  3. If delayed delivery is a hard requirement, switch to a transport that implements it (e.g. an AMQP-based backend).

Example fix

// before
$producer->setDeliveryDelay(5000); // throws on transports without delay support

// after
// only set a delay when the transport advertises support
if (get_class($producer) !== \Phalcon\Queue\Adapter\AbstractProducer::class
    && method_exists($producer, 'setDeliveryDelay')) {
    // still guard: the base implementation throws for non-null values
}
// safest: simply omit the call on unsupported transports
$producer->send($queue, $message);
Defensive patterns

Strategy: try-catch

Try / catch

try {
    $producer->setDeliveryDelay(5000);
} catch (\Phalcon\Queue\Exceptions\DeliveryDelayNotSupportedException $e) {
    // transport cannot delay: enqueue now, or choose another transport
    $logger->warning('Delay unsupported by this transport; sending immediately');
}

Prevention

When it happens

Trigger: $producer->setDeliveryDelay(5000) (or any non-null value) on a producer whose transport inherits the base implementation, e.g. the Beanstalk or Redis queue adapters in this codebase.

Common situations: Writing transport-agnostic code against ProducerInterface that calls all feature setters; porting code from a broker supporting delays (RabbitMQ, SQS) to beanstalk-style transports; feature flags turning delay on globally.

Related errors


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/6ce840d2d7925dd5. Report an issue: GitHub.