Minion-Backend-SQLite

 view release on metacpan or  search on metacpan

lib/Minion/Backend/SQLite.pm  view on Meta::CPAN

package Minion::Backend::SQLite;
use Mojo::Base 'Minion::Backend';

use Carp 'croak';
use DBD::SQLite::Constants ':result_codes';
use List::Util 'min';
use Minion::Util 'next_cron_time';
use Mojo::SQLite;
use Mojo::Util 'steady_time';
use Sys::Hostname 'hostname';
use Time::HiRes 'usleep';

our $VERSION = 'v6.0.0';

has dequeue_interval => 0.5;
has 'sqlite';

sub new {
  my $self = shift->SUPER::new(sqlite => Mojo::SQLite->new(@_));
  $self->sqlite->auto_migrate(1)->migrations->name('minion')->from_data;
  return $self;
}

sub broadcast {
  my ($self, $command, $args, $ids) = (shift, shift, shift || [], shift || []);
  my $ids_in = join ',', ('?')x@$ids;
  return !!$self->sqlite->db->query(
    q{update minion_workers set inbox =
      json_set(inbox, '$[' || json_array_length(inbox) || ']', json(?))} .
      (@$ids ? " where id in ($ids_in)" : ''),
      {json => [$command, @$args]}, @$ids
  )->rows;
}

sub dequeue {
  my ($self, $id, $wait, $options) = @_;
  my $job = $self->_try($id, $options);
  unless ($job) {
    my $int = $self->dequeue_interval;
    my $end = steady_time + $wait;
    my $remaining = $wait;
    usleep(min($int, $remaining) * 1000000)
      until ($remaining = $end - steady_time) <= 0
      or $job = $self->_try($id, $options);
  }
  return $job || $self->_try($id, $options);
}

sub dispatch_schedules {
  my $self = shift;

  my $db = $self->sqlite->db;
  my $timeout = $db->dbh->sqlite_busy_timeout // 0;
  $db->dbh->sqlite_busy_timeout(1000);
  my ($tx, $errored, $error);
  {
    local $@;
    unless (eval { $tx = $db->begin('immediate'); 1 }) {
      my $errcode = $db->dbh->err;
      if ($errcode and $errcode == SQLITE_BUSY) {
        $db->dbh->sqlite_busy_timeout($timeout);
        $db->dbh->rollback; # clear internal transaction state
        return [];
      } else {
        $errored = 1;
        $error = $@;
      }
    }
  }
  $db->dbh->sqlite_busy_timeout($timeout);
  die $error if $errored;

  my $due = $db->query(
    q{select id, args, attempts, cron, expire, lax, name, strftime('%s',next_run) as next_run,
        strftime('%s','now') as now, notes, priority, queue, task
      from minion_schedules where next_run <= datetime('now') and not paused}
  )->expand(json => [qw(args notes)])->hashes;

  my @dispatched;
  for my $s (@$due) {
    my $job_id = $self->_enqueue(
      $db,
      $s->{task},
      $s->{args},
      {
        attempts => $s->{attempts},
        expire   => $s->{expire},
        lax      => $s->{lax},
        notes    => $s->{notes},
        priority => $s->{priority},
        queue    => $s->{queue}
      }
    );
    my $basis = $s->{next_run} > $s->{now} ? $s->{next_run} : $s->{now};
    my $next  = next_cron_time $s->{cron}, $basis;
    $db->query(q{update minion_schedules set last_job = ?, last_run = datetime('now'),
      next_run = datetime(?,'unixepoch') where id = ?},
      $job_id, $next, $s->{id});
    push @dispatched, {id => $s->{id}, name => $s->{name}, job => $job_id};
  }
  $tx->commit;

  return \@dispatched;
}

sub enqueue {
  my $self = shift;
  return $self->_enqueue($self->sqlite->db, @_);
}

sub fail_job   { shift->_update(1, @_) }
sub finish_job { shift->_update(0, @_) }

sub history {
  my $self = shift;

  my $db = $self->sqlite->db;
  my $steps = $db->query(
    q{with recursive generate_series(ts) as (
        select datetime('now','-23 hours')
        union all
        select datetime(ts,'+1 hour') from generate_series
        where datetime(ts,'+1 hour') <= datetime('now')
      ) select ts, strftime('%s',ts) as epoch,
        strftime('%d',ts,'localtime') as day,
        strftime('%H',ts,'localtime') as hour
      from generate_series order by epoch})->hashes;

  my $counts = $db->query(
    q{select strftime('%d',finished,'localtime') as day,



( run in 2.999 seconds using v1.01-cache-2.11-cpan-600a1bdf6e4 )