User:AnomieBOT/source/bot-instance.pl

  1. !/usr/bin/perl -w

use strict;

  1. binmodes

$|=1;

binmode STDOUT, ':utf8';

binmode STDERR, ':utf8';

die "Cannot run as root\n" if $<==0;

my $scriptname=$0;

my @args=@ARGV;

my $botnum = shift or die "USAGE: $0 botnum task-dir\n";

my $dir = shift or die "USAGE: $0 botnum task-dir\n";

$dir.='/' unless substr($dir,-1) eq '/';

use Cwd;

use File::Basename;

use lib File::Basename::dirname( Cwd::realpath( __FILE__ ) );

use AnomieBOT::API;

use Sys::Hostname;

use POSIX ':sys_wait_h';

use sort 'stable';

my @tasks=();

my @tasknames=();

my %jobstatuses=();

chdir($AnomieBOT::API::basedir);

sub warnTS {

warn POSIX::strftime('[%F %T] ', localtime), @_;

}

sub die2 {

warnTS @_;

exit(0);

}

my $api=AnomieBOT::API->new('conf.ini', $botnum);

$api->read_command();

$api->reopen_logs();

opendir(D, $dir) or die2 "($botnum) Could not open task directory: $!\n";

while(my $file=readdir(D)){

next if -d $dir.$file;

next if substr($file,0,1) eq '.';

my $task='';

if(!open(X, '<:utf8', $dir.$file)){

warnTS "($botnum) Could not open task file $file: $!\n";

next;

}

while(){

$task=$1 if /^package (.*);$/;

}

close(X);

if($task eq ''){

warnTS "($botnum) Invalid task file $file\n";

next;

}

AnomieBOT::API::load($dir.$file);

my $t=$task->new();

next unless $t->approved == $botnum;

push @tasks, $t;

$task=~s/.*:://;

push @tasknames, $task;

}

closedir(D);

unless(@tasks){

warnTS "($botnum) No tasks\n";

exit(0);

}

  1. Replace spaces with NBSP, gridengine doesn't handle spaces in $0 well.

($0=$api->user." ($botnum): ".join(' ', @tasknames))=~s/\s/\N{U+00A0}/g;

warnTS "Bot instance $botnum starting, pid $$ on " . hostname . "\n";

$SIG{QUIT}=sub { warnTS "($botnum) QUIT received!\n"; $api->halting('term'); };

$SIG{TERM}=sub { warnTS "($botnum) TERM received!\n"; $api->halting('term'); };

$SIG{INT}=sub { warnTS "($botnum) INT received!\n"; $api->halting('term'); };

$SIG{HUP}=sub { warnTS "($botnum) HUP received!\n"; $api->halting('restart'); };

$SIG{USR1}=sub { warnTS "($botnum) USR1 received! (ignoring)\n"; };

$SIG{USR2}=sub { warnTS "($botnum) (M) USR2 received!\n"; $api->halting('term'); };

  1. We need to somehow sort both @tasks and @tasknames. This does it by sorting
  2. indices first and then taking slices of both arrays.

my @i = sort { $tasks[$a]->{'order'} <=> $tasks[$b]->{'order'} } ( 0 .. $#tasks );

@tasks = @tasks[@i];

@tasknames = @tasknames[@i];

my @next=map { 0 } @tasks;

for my $taskname (@tasknames) {

$jobstatuses{$taskname} = $api->cache->get( "status:$taskname" ) // { lastrun => 0, nextrun => 0 };

$jobstatuses{$taskname}{'hostname'} = hostname;

$jobstatuses{$taskname}{'botnum'} = $botnum;

$jobstatuses{$taskname}{'status'} = 'job starting';

$api->cache->set( "status:$taskname", $jobstatuses{$taskname} );

}

$api->onpause( sub {

my $flag = shift;

for(my $i=0; $i<@tasks; $i++){

my $taskname = $tasknames[$i];

next if $next[$i]<0;

if ( $flag ) {

next if $jobstatuses{$taskname}{'status'} eq 'paused';

$jobstatuses{$taskname}{'pause saved status'} = $jobstatuses{$taskname}{'status'};

$jobstatuses{$taskname}{'status'} = 'paused';

} elsif ( exists( $jobstatuses{$taskname}{'pause saved status'} ) ) {

$jobstatuses{$taskname}{'status'} = $jobstatuses{$taskname}{'pause saved status'};

delete $jobstatuses{$taskname}{'pause saved status'};

}

$api->cache->set( "status:$taskname", $jobstatuses{$taskname} );

}

} );

$api->login();

while(!$api->halting){

while ( !$api->halting ) {

my ($statuslist, $cas) = $api->cache->gets( 'joblist' );

my $any = 0;

for my $taskname (keys %jobstatuses) {

$api->cache->add( "status:$taskname", $jobstatuses{$taskname} );

unless ( grep $_ eq $taskname, @$statuslist ) {

$any = 1;

push @$statuslist, $taskname;

}

}

last if !$any;

warnTS "($botnum) Updating joblist\n";

last if $api->cache->cas( 'joblist', $statuslist, $cas );

sleep 1;

}

last if $api->halting;

my $wait=60; # maximum wait time

my $realwait=1e100;

for(my $i=0; $i<@tasks; $i++){

my $taskname = $tasknames[$i];

next if($next[$i]<0 || $next[$i]>time());

warnTS "($botnum) Starting task $taskname (".ref($tasks[$i]).")\n" if($api->DEBUG & 1);

$jobstatuses{$taskname}{'status'} = 'running';

$jobstatuses{$taskname}{'lastrun'} = time();

$api->cache->set( "status:$taskname", $jobstatuses{$taskname} );

my $w;

my $terminated = 0;

eval { $w=$tasks[$i]->run($api); };

if($@){

warnTS "($botnum) Caught error from task $taskname: $@\n";

$jobstatuses{$taskname}{'status'} = 'error';

$jobstatuses{$taskname}{'nextrun'} = 0;

$terminated = 1;

} elsif(!defined($w)){

warnTS "($botnum) Task $taskname returned undef\n" if($api->DEBUG & 1);

$jobstatuses{$taskname}{'status'} = 'ended';

$jobstatuses{$taskname}{'nextrun'} = 0;

$terminated = 1;

} else {

warnTS "($botnum) Task $taskname returned $w\n" if($api->DEBUG & 1);

$next[$i]=time()+$w;

$wait=$w if $w<$wait;

$realwait=$w if $w<$realwait;

$jobstatuses{$taskname}{'status'} = $tasks[$i]->status;

$jobstatuses{$taskname}{'nextrun'} = $next[$i];

}

# Update job status

$api->cache->set( "status:$taskname", $jobstatuses{$taskname} );

if ( $terminated ) {

delete $tasks[$i];

# Indicate ended task in $0

$tasknames[$i] = '(' . $tasknames[$i] . ')';

($0=$api->user." ($botnum): ".join(' ', @tasknames))=~s/\s/\N{U+00A0}/g;

$next[$i]=-1;

}

last if $api->halting;

}

die2 "($botnum) No tasks" unless @tasks;

warnTS "($botnum) Sleeping for $wait seconds\n" if($wait>0 && ($api->DEBUG & 1));

last if $api->halting;

$api->drop_connections() if $realwait>300;

sleep($wait) if $wait>0;

}

for(my $i=0; $i<@tasks; $i++){

my $taskname = $tasknames[$i];

next if $next[$i]<0;

$jobstatuses{$taskname}{'status'} = 'job ended';

$jobstatuses{$taskname}{'nextrun'} = 0;

$api->cache->set( "status:$taskname", $jobstatuses{$taskname} );

}

my $haltcode = $api->halting;

if($haltcode eq 'term'){

warnTS "($botnum) Exiting now...\n";

$api->DESTROY;

warnTS "($botnum) Exited!\n";

exit(0);

} elsif($haltcode =~ /^restart/){

warnTS "($botnum) Restarting bot...\n";

$api->DESTROY;

warnTS "($botnum) Exited!\n";

exec {$scriptname} ($scriptname,@args);

warn "($botnum) Could not re-exec $scriptname: $!\n";

exit(1);

} else {

warnTS "($botnum) Exiting for unknown halt code $haltcode...\n";

$api->DESTROY;

warnTS "($botnum) Exited!\n";

exit(2);

}