ac/fiendish-bundle
Symfony2 bundle for writing and controlling daemons. Integrates with RabbitMQ and Supervisor to start/stop and dynamically manage processes by group, with a BaseDaemon class and heartbeat support for long-running workers.
Install Dependencies:
RabbitMQ, Supervisor, and Twiddler are installed and running.php-cli is available (php -v in terminal).Composer Install:
composer require americancouncils/fiendish-bundle:dev-master
Register Bundles (app/AppKernel.php):
new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
new AC\FiendishBundle\ACFiendishBundle(),
Database Migration:
vendor/americancouncils/fiendish-bundle/DoctrineMigrations/.php app/console doctrine:migrations:execute --query="CREATE TABLE Process (...)"
Supervisor Configuration:
/etc/supervisor/conf.d/<project>.conf (see example below).supervisorctl reread
supervisorctl update
supervisorctl start <project>_master
First Daemon:
app/config/services.yml):
services:
my_daemon:
class: AppBundle\Daemon\MyDaemon
tags:
- { name: fiendish.daemon, daemon: "my_daemon" }
AC\FiendishBundle\Daemon\DaemonInterface in MyDaemon.php.Define Daemon Class:
namespace AppBundle\Daemon;
use AC\FiendishBundle\Daemon\DaemonInterface;
class MyDaemon implements DaemonInterface {
public function run() {
while (true) {
// Business logic (e.g., fetch data, process, sleep)
sleep(60);
}
}
}
Register as Service:
# app/config/services.yml
services:
app.my_daemon:
class: AppBundle\Daemon\MyDaemon
tags:
- { name: fiendish.daemon, daemon: "my_daemon" }
Configure Supervisor (/etc/supervisor/conf.d/<project>.conf):
[program:my_daemon]
command=/usr/bin/php /var/www/<project>/app/console fiendish:daemon my_daemon
directory=/var/www/<project>
autostart=true
autorestart=true
user=www-data
stderr_logfile=/var/log/<project>/my_daemon.err.log
stdout_logfile=/var/log/<project>/my_daemon.out.log
Start Daemon:
supervisorctl start my_daemon
RabbitMQ Integration:
Use OldSoundRabbitMqBundle to queue tasks for daemons to process asynchronously.
Example:
// In a controller/service
$this->get('old_sound_rabbit_mq.producer.my_queue')->publish(
json_encode(['task' => 'process_data'])
);
Logging:
Inject Psr\Log\LoggerInterface into your daemon for structured logging:
use Psr\Log\LoggerInterface;
class MyDaemon implements DaemonInterface {
private $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
public function run() {
$this->logger->info('Daemon started');
// ...
}
}
Graceful Shutdown:
Handle SIGTERM/SIGINT in run() to exit cleanly:
declare(ticks = 1);
pcntl_signal(SIGTERM, function() {
exit(0);
});
Environment Awareness:
Use %kernel.environment% in Supervisor configs to differentiate staging/prod:
[program:my_daemon_%(ENVIRONMENT)s]
command=/usr/bin/php /var/www/<project>/app/console fiendish:daemon my_daemon --env=%(ENVIRONMENT)s
Supervisor Misconfiguration:
directory, user, or command paths.chown -R www-data:www-data /var/www/<project>)./var/log/<project>/*.log and run supervisorctl status.RabbitMQ Connection Drops:
run():
while (true) {
try {
$this->getRabbitMQConnection()->connect();
break;
} catch (\Exception $e) {
sleep(min(10, pow(2, $attempt++))); // Exponential backoff
}
}
Database Locking:
Daemon Registration:
fiendish.daemon tag.services.yml and clear cache:
php app/console cache:clear
Supervisor Commands:
supervisorctl restart my_daemon
tail -f /var/log/<project>/my_daemon.out.log
Daemon Lifecycle:
fiendish:list-daemons to check registered daemons:
php app/console fiendish:list-daemons
php app/console fiendish:daemon my_daemon --once
RabbitMQ Debugging:
php app/console rabbitmq:consumers:list
php app/console rabbitmq:queue:purge my_queue
Custom Daemon States:
Extend the Process table to track custom states (e.g., paused, maintenance):
// In a migration
$this->addSql("ALTER TABLE Process ADD state VARCHAR(20) DEFAULT 'running'");
run() to check the state and act accordingly.Dynamic Daemon Scaling:
Use Supervisor’s numprocs to run multiple instances of a daemon:
[program:my_daemon]
numprocs=4
process_name=my_daemon_%(process_num)02d
Health Checks:
Add a /health endpoint to your daemon to expose its status (e.g., last run time, queue length):
class MyDaemon implements DaemonInterface {
public function healthCheck() {
return [
'last_run' => $this->lastRunAt,
'queue_length' => $this->getQueueLength(),
];
}
}
swoole).Metrics Integration:
Integrate with Prometheus or Datadog by exposing metrics in run():
$metrics->gauge('daemon.processed_tasks', $processedTasks);
How can I help you explore Laravel packages today?