forked from lix-project/hydra
Remove Hydra::Helper::nix::txn_do from the Perl code
To quote the function's comment: Awful hack to handle timeouts in SQLite: just retry the transaction. DBD::SQLite *has* a 30 second retry window, but apparently it doesn't work. Since SQLite is now dropped entirely, this wrapper can be removed completely.
This commit is contained in:
parent
efcbc08686
commit
721c764951
|
@ -214,7 +214,7 @@ sub scmdiff : Path('/api/scmdiff') Args(0) {
|
||||||
sub triggerJobset {
|
sub triggerJobset {
|
||||||
my ($self, $c, $jobset, $force) = @_;
|
my ($self, $c, $jobset, $force) = @_;
|
||||||
print STDERR "triggering jobset ", $jobset->get_column('project') . ":" . $jobset->name, "\n";
|
print STDERR "triggering jobset ", $jobset->get_column('project') . ":" . $jobset->name, "\n";
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
$jobset->update({ triggertime => time });
|
$jobset->update({ triggertime => time });
|
||||||
$jobset->update({ forceeval => 1 }) if $force;
|
$jobset->update({ forceeval => 1 }) if $force;
|
||||||
});
|
});
|
||||||
|
|
|
@ -90,7 +90,7 @@ sub news_submit : Chained('admin') PathPart('news/submit') Args(0) {
|
||||||
sub news_delete : Chained('admin') PathPart('news/delete') Args(1) {
|
sub news_delete : Chained('admin') PathPart('news/delete') Args(1) {
|
||||||
my ($self, $c, $id) = @_;
|
my ($self, $c, $id) = @_;
|
||||||
|
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
my $newsItem = $c->model('DB::NewsItems')->find($id)
|
my $newsItem = $c->model('DB::NewsItems')->find($id)
|
||||||
or notFound($c, "Newsitem with id $id doesn't exist.");
|
or notFound($c, "Newsitem with id $id doesn't exist.");
|
||||||
$newsItem->delete;
|
$newsItem->delete;
|
||||||
|
|
|
@ -526,7 +526,7 @@ sub keep : Chained('buildChain') PathPart Args(1) {
|
||||||
registerRoot $_->path foreach $build->buildoutputs;
|
registerRoot $_->path foreach $build->buildoutputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
$build->update({keep => $keep});
|
$build->update({keep => $keep});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ sub jobset_PUT {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defined $c->stash->{jobset}) {
|
if (defined $c->stash->{jobset}) {
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
updateJobset($c, $c->stash->{jobset});
|
updateJobset($c, $c->stash->{jobset});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ sub jobset_PUT {
|
||||||
|
|
||||||
else {
|
else {
|
||||||
my $jobset;
|
my $jobset;
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
# Note: $jobsetName is validated in updateProject, which will
|
# Note: $jobsetName is validated in updateProject, which will
|
||||||
# abort the transaction if the name isn't valid.
|
# abort the transaction if the name isn't valid.
|
||||||
$jobset = $c->stash->{project}->jobsets->create(
|
$jobset = $c->stash->{project}->jobsets->create(
|
||||||
|
@ -100,7 +100,7 @@ sub jobset_DELETE {
|
||||||
error($c, "can't modify jobset of declarative project", 403);
|
error($c, "can't modify jobset of declarative project", 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
$c->stash->{jobset}->jobsetevals->delete;
|
$c->stash->{jobset}->jobsetevals->delete;
|
||||||
$c->stash->{jobset}->builds->delete;
|
$c->stash->{jobset}->builds->delete;
|
||||||
$c->stash->{jobset}->delete;
|
$c->stash->{jobset}->delete;
|
||||||
|
|
|
@ -146,7 +146,7 @@ sub release : Chained('evalChain') PathPart('release') Args(0) {
|
||||||
|
|
||||||
my $release;
|
my $release;
|
||||||
|
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
|
|
||||||
$release = $c->stash->{project}->releases->create(
|
$release = $c->stash->{project}->releases->create(
|
||||||
{ name => $releaseName
|
{ name => $releaseName
|
||||||
|
|
|
@ -41,7 +41,7 @@ sub project_PUT {
|
||||||
if (defined $c->stash->{project}) {
|
if (defined $c->stash->{project}) {
|
||||||
requireProjectOwner($c, $c->stash->{project});
|
requireProjectOwner($c, $c->stash->{project});
|
||||||
|
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
updateProject($c, $c->stash->{project});
|
updateProject($c, $c->stash->{project});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ sub project_PUT {
|
||||||
requireMayCreateProjects($c);
|
requireMayCreateProjects($c);
|
||||||
|
|
||||||
my $project;
|
my $project;
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
# Note: $projectName is validated in updateProject,
|
# Note: $projectName is validated in updateProject,
|
||||||
# which will abort the transaction if the name isn't
|
# which will abort the transaction if the name isn't
|
||||||
# valid. Idem for the owner.
|
# valid. Idem for the owner.
|
||||||
|
@ -77,7 +77,7 @@ sub project_DELETE {
|
||||||
|
|
||||||
requireProjectOwner($c, $c->stash->{project});
|
requireProjectOwner($c, $c->stash->{project});
|
||||||
|
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
$c->stash->{project}->jobsetevals->delete;
|
$c->stash->{project}->jobsetevals->delete;
|
||||||
$c->stash->{project}->builds->delete;
|
$c->stash->{project}->builds->delete;
|
||||||
$c->stash->{project}->delete;
|
$c->stash->{project}->delete;
|
||||||
|
@ -198,7 +198,7 @@ sub create_release_submit : Chained('projectChain') PathPart('create-release/sub
|
||||||
my $releaseName = $c->request->params->{name};
|
my $releaseName = $c->request->params->{name};
|
||||||
|
|
||||||
my $release;
|
my $release;
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
# Note: $releaseName is validated in updateRelease, which will
|
# Note: $releaseName is validated in updateRelease, which will
|
||||||
# abort the transaction if the name isn't valid.
|
# abort the transaction if the name isn't valid.
|
||||||
$release = $c->stash->{project}->releases->create(
|
$release = $c->stash->{project}->releases->create(
|
||||||
|
|
|
@ -63,13 +63,13 @@ sub submit : Chained('release') PathPart('submit') Args(0) {
|
||||||
requireProjectOwner($c, $c->stash->{project});
|
requireProjectOwner($c, $c->stash->{project});
|
||||||
|
|
||||||
if (($c->request->params->{action} || "") eq "delete") {
|
if (($c->request->params->{action} || "") eq "delete") {
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
$c->stash->{release}->delete;
|
$c->stash->{release}->delete;
|
||||||
});
|
});
|
||||||
$c->res->redirect($c->uri_for($c->controller('Project')->action_for('project'),
|
$c->res->redirect($c->uri_for($c->controller('Project')->action_for('project'),
|
||||||
[$c->stash->{project}->name]));
|
[$c->stash->{project}->name]));
|
||||||
} else {
|
} else {
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
updateRelease($c, $c->stash->{release});
|
updateRelease($c, $c->stash->{release});
|
||||||
});
|
});
|
||||||
$c->res->redirect($c->uri_for($self->action_for("view"),
|
$c->res->redirect($c->uri_for($self->action_for("view"),
|
||||||
|
|
|
@ -163,7 +163,7 @@ sub register :Local Args(0) {
|
||||||
error($c, "Your user name is already taken.")
|
error($c, "Your user name is already taken.")
|
||||||
if $c->find_user({ username => $userName });
|
if $c->find_user({ username => $userName });
|
||||||
|
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('DB')->schema->txn_do(sub {
|
||||||
my $user = $c->model('DB::Users')->create(
|
my $user = $c->model('DB::Users')->create(
|
||||||
{ username => $userName
|
{ username => $userName
|
||||||
, password => "!"
|
, password => "!"
|
||||||
|
@ -261,7 +261,7 @@ sub edit_PUT {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
txn_do($c->model('DB')->schema, sub {
|
$c->model('Db')->schema->txn_do(sub {
|
||||||
updatePreferences($c, $user);
|
updatePreferences($c, $user);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ sub updateDeclarativeJobset {
|
||||||
$update{$key} = $declSpec->{$key};
|
$update{$key} = $declSpec->{$key};
|
||||||
delete $declSpec->{$key};
|
delete $declSpec->{$key};
|
||||||
}
|
}
|
||||||
txn_do($db, sub {
|
$db->txn_do(sub {
|
||||||
my $jobset = $project->jobsets->update_or_create(\%update);
|
my $jobset = $project->jobsets->update_or_create(\%update);
|
||||||
$jobset->jobsetinputs->delete;
|
$jobset->jobsetinputs->delete;
|
||||||
while ((my $name, my $data) = each %{$declSpec->{"inputs"}}) {
|
while ((my $name, my $data) = each %{$declSpec->{"inputs"}}) {
|
||||||
|
@ -79,7 +79,7 @@ sub handleDeclarativeJobsetBuild {
|
||||||
}
|
}
|
||||||
|
|
||||||
my $declSpec = decode_json($declText);
|
my $declSpec = decode_json($declText);
|
||||||
txn_do($db, sub {
|
$db->txn_do(sub {
|
||||||
my @kept = keys %$declSpec;
|
my @kept = keys %$declSpec;
|
||||||
push @kept, ".jobsets";
|
push @kept, ".jobsets";
|
||||||
$project->jobsets->search({ name => { "not in" => \@kept } })->update({ enabled => 0, hidden => 1 });
|
$project->jobsets->search({ name => { "not in" => \@kept } })->update({ enabled => 0, hidden => 1 });
|
||||||
|
|
|
@ -14,7 +14,7 @@ use IPC::Run;
|
||||||
|
|
||||||
our @ISA = qw(Exporter);
|
our @ISA = qw(Exporter);
|
||||||
our @EXPORT = qw(
|
our @EXPORT = qw(
|
||||||
getHydraHome getHydraConfig getBaseUrl txn_do
|
getHydraHome getHydraConfig getBaseUrl
|
||||||
getSCMCacheDir
|
getSCMCacheDir
|
||||||
registerRoot getGCRootsDir gcRootFor
|
registerRoot getGCRootsDir gcRootFor
|
||||||
jobsetOverview jobsetOverview_
|
jobsetOverview jobsetOverview_
|
||||||
|
@ -61,22 +61,6 @@ sub getBaseUrl {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Awful hack to handle timeouts in SQLite: just retry the transaction.
|
|
||||||
# DBD::SQLite *has* a 30 second retry window, but apparently it
|
|
||||||
# doesn't work.
|
|
||||||
sub txn_do {
|
|
||||||
my ($db, $coderef) = @_;
|
|
||||||
my $res;
|
|
||||||
while (1) {
|
|
||||||
eval {
|
|
||||||
$res = $db->txn_do($coderef);
|
|
||||||
};
|
|
||||||
return $res if !$@;
|
|
||||||
die $@ unless $@ =~ "database is locked";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub getSCMCacheDir {
|
sub getSCMCacheDir {
|
||||||
return Hydra::Model::DB::getHydraPath . "/scm" ;
|
return Hydra::Model::DB::getHydraPath . "/scm" ;
|
||||||
}
|
}
|
||||||
|
@ -446,7 +430,7 @@ sub getTotalShares {
|
||||||
|
|
||||||
sub cancelBuilds($$) {
|
sub cancelBuilds($$) {
|
||||||
my ($db, $builds) = @_;
|
my ($db, $builds) = @_;
|
||||||
return txn_do($db, sub {
|
return $db->txn_do(sub {
|
||||||
$builds = $builds->search({ finished => 0 });
|
$builds = $builds->search({ finished => 0 });
|
||||||
my $n = $builds->count;
|
my $n = $builds->count;
|
||||||
my $time = time();
|
my $time = time();
|
||||||
|
@ -473,7 +457,7 @@ sub restartBuilds($$) {
|
||||||
|
|
||||||
my $nrRestarted = 0;
|
my $nrRestarted = 0;
|
||||||
|
|
||||||
txn_do($db, sub {
|
$db->txn_do(sub {
|
||||||
# Reset the stats for the evals to which the builds belongs.
|
# Reset the stats for the evals to which the builds belongs.
|
||||||
# !!! Should do this in a trigger.
|
# !!! Should do this in a trigger.
|
||||||
$db->resultset('JobsetEvals')->search(
|
$db->resultset('JobsetEvals')->search(
|
||||||
|
|
|
@ -58,7 +58,7 @@ sub fetchInput {
|
||||||
# FIXME: time window between nix-prefetch-bzr and addTempRoot.
|
# FIXME: time window between nix-prefetch-bzr and addTempRoot.
|
||||||
addTempRoot($storePath);
|
addTempRoot($storePath);
|
||||||
|
|
||||||
txn_do($self->{db}, sub {
|
$self->{db}->txn_do(sub {
|
||||||
$self->{db}->resultset('CachedBazaarInputs')->create(
|
$self->{db}->resultset('CachedBazaarInputs')->create(
|
||||||
{ uri => $uri
|
{ uri => $uri
|
||||||
, revision => $revision
|
, revision => $revision
|
||||||
|
|
|
@ -77,7 +77,7 @@ sub fetchInput {
|
||||||
$sha256 = queryPathHash($storePath);
|
$sha256 = queryPathHash($storePath);
|
||||||
$sha256 =~ s/sha256://;
|
$sha256 =~ s/sha256://;
|
||||||
|
|
||||||
txn_do($self->{db}, sub {
|
$self->{db}->txn_do(sub {
|
||||||
$self->{db}->resultset('CachedDarcsInputs')->update_or_create(
|
$self->{db}->resultset('CachedDarcsInputs')->update_or_create(
|
||||||
{ uri => $uri
|
{ uri => $uri
|
||||||
, revision => $revision
|
, revision => $revision
|
||||||
|
|
|
@ -218,7 +218,7 @@ sub fetchInput {
|
||||||
# FIXME: time window between nix-prefetch-git and addTempRoot.
|
# FIXME: time window between nix-prefetch-git and addTempRoot.
|
||||||
addTempRoot($storePath);
|
addTempRoot($storePath);
|
||||||
|
|
||||||
txn_do($self->{db}, sub {
|
$self->{db}->txn_do(sub {
|
||||||
$self->{db}->resultset('CachedGitInputs')->update_or_create(
|
$self->{db}->resultset('CachedGitInputs')->update_or_create(
|
||||||
{ uri => $uri
|
{ uri => $uri
|
||||||
, branch => $branch
|
, branch => $branch
|
||||||
|
|
|
@ -85,7 +85,7 @@ sub fetchInput {
|
||||||
# FIXME: time window between nix-prefetch-hg and addTempRoot.
|
# FIXME: time window between nix-prefetch-hg and addTempRoot.
|
||||||
addTempRoot($storePath);
|
addTempRoot($storePath);
|
||||||
|
|
||||||
txn_do($self->{db}, sub {
|
$self->{db}->txn_do(sub {
|
||||||
$self->{db}->resultset('CachedHgInputs')->update_or_create(
|
$self->{db}->resultset('CachedHgInputs')->update_or_create(
|
||||||
{ uri => $uri
|
{ uri => $uri
|
||||||
, branch => $branch
|
, branch => $branch
|
||||||
|
|
|
@ -54,7 +54,7 @@ sub fetchInput {
|
||||||
# changes, we get a new "revision", but if it doesn't change
|
# changes, we get a new "revision", but if it doesn't change
|
||||||
# (or changes back), we don't get a new "revision".
|
# (or changes back), we don't get a new "revision".
|
||||||
if (!defined $cachedInput) {
|
if (!defined $cachedInput) {
|
||||||
txn_do($self->{db}, sub {
|
$self->{db}->txn_do(sub {
|
||||||
$self->{db}->resultset('CachedPathInputs')->update_or_create(
|
$self->{db}->resultset('CachedPathInputs')->update_or_create(
|
||||||
{ srcpath => $uri
|
{ srcpath => $uri
|
||||||
, timestamp => $timestamp
|
, timestamp => $timestamp
|
||||||
|
@ -65,7 +65,7 @@ sub fetchInput {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$timestamp = $cachedInput->timestamp;
|
$timestamp = $cachedInput->timestamp;
|
||||||
txn_do($self->{db}, sub {
|
$self->{db}->txn_do(sub {
|
||||||
$cachedInput->update({lastseen => time});
|
$cachedInput->update({lastseen => time});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ sub fetchInput {
|
||||||
|
|
||||||
$sha256 = queryPathHash($storePath); $sha256 =~ s/sha256://;
|
$sha256 = queryPathHash($storePath); $sha256 =~ s/sha256://;
|
||||||
|
|
||||||
txn_do($self->{db}, sub {
|
$self->{db}->txn_do(sub {
|
||||||
$self->{db}->resultset('CachedSubversionInputs')->update_or_create(
|
$self->{db}->resultset('CachedSubversionInputs')->update_or_create(
|
||||||
{ uri => $uri
|
{ uri => $uri
|
||||||
, revision => $revision
|
, revision => $revision
|
||||||
|
|
|
@ -54,7 +54,7 @@ die "$0: type must be `hydra' or `google'\n"
|
||||||
|
|
||||||
my $db = Hydra::Model::DB->new();
|
my $db = Hydra::Model::DB->new();
|
||||||
|
|
||||||
txn_do($db, sub {
|
$db->txn_do(sub {
|
||||||
my $user = $db->resultset('Users')->find({ username => $renameFrom // $userName });
|
my $user = $db->resultset('Users')->find({ username => $renameFrom // $userName });
|
||||||
if ($renameFrom) {
|
if ($renameFrom) {
|
||||||
die "$0: user `$renameFrom' does not exist\n" unless $user;
|
die "$0: user `$renameFrom' does not exist\n" unless $user;
|
||||||
|
|
|
@ -399,7 +399,7 @@ sub checkBuild {
|
||||||
|
|
||||||
my $build;
|
my $build;
|
||||||
|
|
||||||
txn_do($db, sub {
|
$db->txn_do(sub {
|
||||||
my $job = $jobset->jobs->update_or_create({
|
my $job = $jobset->jobs->update_or_create({
|
||||||
name => $jobName,
|
name => $jobName,
|
||||||
jobset_id => $jobset->id,
|
jobset_id => $jobset->id,
|
||||||
|
@ -501,7 +501,7 @@ sub setJobsetError {
|
||||||
my $prevError = $jobset->errormsg;
|
my $prevError = $jobset->errormsg;
|
||||||
|
|
||||||
eval {
|
eval {
|
||||||
txn_do($db, sub {
|
$db->txn_do(sub {
|
||||||
$jobset->update({ errormsg => $errorMsg, errortime => time, fetcherrormsg => undef });
|
$jobset->update({ errormsg => $errorMsg, errortime => time, fetcherrormsg => undef });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -603,7 +603,7 @@ sub checkJobsetWrapped {
|
||||||
if ($fetchError) {
|
if ($fetchError) {
|
||||||
Net::Statsd::increment("hydra.evaluator.failed_checkouts");
|
Net::Statsd::increment("hydra.evaluator.failed_checkouts");
|
||||||
print STDERR $fetchError;
|
print STDERR $fetchError;
|
||||||
txn_do($db, sub {
|
$db->txn_do(sub {
|
||||||
$jobset->update({ lastcheckedtime => time, fetcherrormsg => $fetchError }) if !$dryRun;
|
$jobset->update({ lastcheckedtime => time, fetcherrormsg => $fetchError }) if !$dryRun;
|
||||||
$db->storage->dbh->do("notify eval_failed, ?", undef, join('\t', $tmpId));
|
$db->storage->dbh->do("notify eval_failed, ?", undef, join('\t', $tmpId));
|
||||||
});
|
});
|
||||||
|
@ -619,7 +619,7 @@ sub checkJobsetWrapped {
|
||||||
if (defined $prevEval && $prevEval->hash eq $argsHash && !$dryRun && !$jobset->forceeval && $prevEval->flake eq $flakeRef) {
|
if (defined $prevEval && $prevEval->hash eq $argsHash && !$dryRun && !$jobset->forceeval && $prevEval->flake eq $flakeRef) {
|
||||||
print STDERR " jobset is unchanged, skipping\n";
|
print STDERR " jobset is unchanged, skipping\n";
|
||||||
Net::Statsd::increment("hydra.evaluator.unchanged_checkouts");
|
Net::Statsd::increment("hydra.evaluator.unchanged_checkouts");
|
||||||
txn_do($db, sub {
|
$db->txn_do(sub {
|
||||||
$jobset->update({ lastcheckedtime => time, fetcherrormsg => undef });
|
$jobset->update({ lastcheckedtime => time, fetcherrormsg => undef });
|
||||||
$db->storage->dbh->do("notify eval_cached, ?", undef, join('\t', $tmpId));
|
$db->storage->dbh->do("notify eval_cached, ?", undef, join('\t', $tmpId));
|
||||||
});
|
});
|
||||||
|
@ -660,7 +660,7 @@ sub checkJobsetWrapped {
|
||||||
my $dbStart = clock_gettime(CLOCK_MONOTONIC);
|
my $dbStart = clock_gettime(CLOCK_MONOTONIC);
|
||||||
|
|
||||||
my %buildMap;
|
my %buildMap;
|
||||||
txn_do($db, sub {
|
$db->txn_do(sub {
|
||||||
|
|
||||||
my $prevEval = getPrevJobsetEval($db, $jobset, 1);
|
my $prevEval = getPrevJobsetEval($db, $jobset, 1);
|
||||||
|
|
||||||
|
@ -806,7 +806,7 @@ sub checkJobset {
|
||||||
my $failed = 0;
|
my $failed = 0;
|
||||||
if ($checkError) {
|
if ($checkError) {
|
||||||
print STDERR $checkError;
|
print STDERR $checkError;
|
||||||
txn_do($db, sub {
|
$db->txn_do(sub {
|
||||||
$jobset->update({lastcheckedtime => time});
|
$jobset->update({lastcheckedtime => time});
|
||||||
setJobsetError($jobset, $checkError);
|
setJobsetError($jobset, $checkError);
|
||||||
$db->storage->dbh->do("notify eval_failed, ?", undef, join('\t', $tmpId));
|
$db->storage->dbh->do("notify eval_failed, ?", undef, join('\t', $tmpId));
|
||||||
|
|
Loading…
Reference in a new issue