Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Fiendish Bundle Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install Dependencies:

    • Ensure RabbitMQ, Supervisor, and Twiddler are installed and running.
    • Verify php-cli is available (php -v in terminal).
  2. Composer Install:

    composer require americancouncils/fiendish-bundle:dev-master
    
  3. Register Bundles (app/AppKernel.php):

    new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
    new AC\FiendishBundle\ACFiendishBundle(),
    
  4. Database Migration:

    • Manually run the migration in vendor/americancouncils/fiendish-bundle/DoctrineMigrations/.
    • Example:
      php app/console doctrine:migrations:execute --query="CREATE TABLE Process (...)"
      
  5. Supervisor Configuration:

    • Create /etc/supervisor/conf.d/<project>.conf (see example below).
    • Reload Supervisor:
      supervisorctl reread
      supervisorctl update
      supervisorctl start <project>_master
      
  6. First Daemon:

    • Define a daemon in a service (e.g., app/config/services.yml):
      services:
          my_daemon:
              class: AppBundle\Daemon\MyDaemon
              tags:
                  - { name: fiendish.daemon, daemon: "my_daemon" }
      
    • Implement AC\FiendishBundle\Daemon\DaemonInterface in MyDaemon.php.

Implementation Patterns

Workflow: Creating a Daemon

  1. 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);
            }
        }
    }
    
  2. Register as Service:

    # app/config/services.yml
    services:
        app.my_daemon:
            class: AppBundle\Daemon\MyDaemon
            tags:
                - { name: fiendish.daemon, daemon: "my_daemon" }
    
  3. 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
    
  4. Start Daemon:

    supervisorctl start my_daemon
    

Integration Tips

  • 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
    

Gotchas and Tips

Pitfalls

  1. Supervisor Misconfiguration:

    • Issue: Daemons fail silently due to incorrect directory, user, or command paths.
    • Fix: Verify paths are absolute and permissions are set (e.g., chown -R www-data:www-data /var/www/<project>).
    • Debug: Check logs in /var/log/<project>/*.log and run supervisorctl status.
  2. RabbitMQ Connection Drops:

    • Issue: Daemons crash if RabbitMQ is unreachable during startup.
    • Fix: Implement retry logic with exponential backoff in run():
      while (true) {
          try {
              $this->getRabbitMQConnection()->connect();
              break;
          } catch (\Exception $e) {
              sleep(min(10, pow(2, $attempt++))); // Exponential backoff
          }
      }
      
  3. Database Locking:

    • Issue: Long-running daemons may lock database connections.
    • Fix: Use transactions sparingly and release locks promptly. Consider read replicas for heavy queries.
  4. Daemon Registration:

    • Issue: Daemons not starting due to missing fiendish.daemon tag.
    • Fix: Double-check services.yml and clear cache:
      php app/console cache:clear
      

Debugging

  1. Supervisor Commands:

    • Restart a daemon:
      supervisorctl restart my_daemon
      
    • View logs in real-time:
      tail -f /var/log/<project>/my_daemon.out.log
      
  2. Daemon Lifecycle:

    • Use fiendish:list-daemons to check registered daemons:
      php app/console fiendish:list-daemons
      
    • Manually trigger a daemon run for testing:
      php app/console fiendish:daemon my_daemon --once
      
  3. RabbitMQ Debugging:

    • Check queue status:
      php app/console rabbitmq:consumers:list
      
    • Purge a queue (for testing):
      php app/console rabbitmq:queue:purge my_queue
      

Extension Points

  1. 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'");
    
    • Update run() to check the state and act accordingly.
  2. 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
    
  3. 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(),
            ];
        }
    }
    
    • Expose via Symfony’s router or a separate HTTP server (e.g., swoole).
  4. Metrics Integration: Integrate with Prometheus or Datadog by exposing metrics in run():

    $metrics->gauge('daemon.processed_tasks', $processedTasks);
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
emuniq/filament-browser-notifications
syriable/filament-translator
hungnm28/livewire-form
wenprise/eloquent
crudly/encrypted
fadion/bouncy
cuci/prototurk-sdk
gos/pubsub-router-bundle
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle
codeflextech/permission-manager
karnoweb/livewire-datepicker
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui