Allow build to be bumped to the front of the queue via the web interface
Builds now have a "Bump up" action. This will cause the queue runner to prioritise the steps of the build above all other steps.
This commit is contained in:
		| @@ -144,7 +144,9 @@ system_time State::doDispatch() | |||||||
|             { |             { | ||||||
|                 auto a_(a->state.lock()); |                 auto a_(a->state.lock()); | ||||||
|                 auto b_(b->state.lock()); // FIXME: deadlock? |                 auto b_(b->state.lock()); // FIXME: deadlock? | ||||||
|                 return a_->lowestBuildID < b_->lowestBuildID; |                 return | ||||||
|  |                     a_->highestGlobalPriority != b_->highestGlobalPriority ? a_->highestGlobalPriority > b_->highestGlobalPriority : | ||||||
|  |                     a_->lowestBuildID < b_->lowestBuildID; | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|         /* Find a machine with a free slot and find a step to run |         /* Find a machine with a free slot and find a step to run | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ void State::queueMonitorLoop() | |||||||
|     receiver buildsRestarted(*conn, "builds_restarted"); |     receiver buildsRestarted(*conn, "builds_restarted"); | ||||||
|     receiver buildsCancelled(*conn, "builds_cancelled"); |     receiver buildsCancelled(*conn, "builds_cancelled"); | ||||||
|     receiver buildsDeleted(*conn, "builds_deleted"); |     receiver buildsDeleted(*conn, "builds_deleted"); | ||||||
|  |     receiver buildsBumped(*conn, "builds_bumped"); | ||||||
|  |  | ||||||
|     auto store = openStore(); // FIXME: pool |     auto store = openStore(); // FIXME: pool | ||||||
|  |  | ||||||
| @@ -44,9 +45,9 @@ void State::queueMonitorLoop() | |||||||
|             printMsg(lvlTalkative, "got notification: builds restarted"); |             printMsg(lvlTalkative, "got notification: builds restarted"); | ||||||
|             lastBuildId = 0; // check all builds |             lastBuildId = 0; // check all builds | ||||||
|         } |         } | ||||||
|         if (buildsCancelled.get() || buildsDeleted.get()) { |         if (buildsCancelled.get() || buildsDeleted.get() || buildsBumped.get()) { | ||||||
|             printMsg(lvlTalkative, "got notification: builds cancelled"); |             printMsg(lvlTalkative, "got notification: builds cancelled or bumped"); | ||||||
|             removeCancelledBuilds(*conn); |             processQueueChange(*conn); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
| @@ -64,7 +65,7 @@ void State::getQueuedBuilds(Connection & conn, std::shared_ptr<StoreAPI> store, | |||||||
|     { |     { | ||||||
|         pqxx::work txn(conn); |         pqxx::work txn(conn); | ||||||
|  |  | ||||||
|         auto res = txn.parameterized("select id, project, jobset, job, drvPath, maxsilent, timeout, timestamp from Builds where id > $1 and finished = 0 order by id")(lastBuildId).exec(); |         auto res = txn.parameterized("select id, project, jobset, job, drvPath, maxsilent, timeout, timestamp, globalPriority from Builds where id > $1 and finished = 0 order by id")(lastBuildId).exec(); | ||||||
|  |  | ||||||
|         for (auto const & row : res) { |         for (auto const & row : res) { | ||||||
|             auto builds_(builds.lock()); |             auto builds_(builds.lock()); | ||||||
| @@ -82,6 +83,7 @@ void State::getQueuedBuilds(Connection & conn, std::shared_ptr<StoreAPI> store, | |||||||
|             build->maxSilentTime = row["maxsilent"].as<int>(); |             build->maxSilentTime = row["maxsilent"].as<int>(); | ||||||
|             build->buildTimeout = row["timeout"].as<int>(); |             build->buildTimeout = row["timeout"].as<int>(); | ||||||
|             build->timestamp = row["timestamp"].as<time_t>(); |             build->timestamp = row["timestamp"].as<time_t>(); | ||||||
|  |             build->globalPriority = row["globalPriority"].as<int>(); | ||||||
|  |  | ||||||
|             newBuilds.emplace(std::make_pair(build->drvPath, build)); |             newBuilds.emplace(std::make_pair(build->drvPath, build)); | ||||||
|         } |         } | ||||||
| @@ -228,13 +230,7 @@ void State::getQueuedBuilds(Connection & conn, std::shared_ptr<StoreAPI> store, | |||||||
|             throw; |             throw; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /* Update the lowest build ID field of each dependency. This |         build->propagatePriorities(); | ||||||
|            is used by the dispatcher to start steps in order of build |  | ||||||
|            ID. */ |  | ||||||
|         visitDependencies([&](const Step::ptr & step) { |  | ||||||
|             auto step_(step->state.lock()); |  | ||||||
|             step_->lowestBuildID = std::min(step_->lowestBuildID, build->id); |  | ||||||
|         }, build->toplevel); |  | ||||||
|  |  | ||||||
|         /* Add the new runnable build steps to ‘runnable’ and wake up |         /* Add the new runnable build steps to ‘runnable’ and wake up | ||||||
|            the builder threads. */ |            the builder threads. */ | ||||||
| @@ -247,26 +243,47 @@ void State::getQueuedBuilds(Connection & conn, std::shared_ptr<StoreAPI> store, | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void State::removeCancelledBuilds(Connection & conn) | void Build::propagatePriorities() | ||||||
|  | { | ||||||
|  |     /* Update the highest global priority and lowest build ID fields | ||||||
|  |        of each dependency. This is used by the dispatcher to start | ||||||
|  |        steps in order of descending global priority and ascending | ||||||
|  |        build ID. */ | ||||||
|  |     visitDependencies([&](const Step::ptr & step) { | ||||||
|  |         auto step_(step->state.lock()); | ||||||
|  |         step_->highestGlobalPriority = std::max(step_->highestGlobalPriority, globalPriority); | ||||||
|  |         step_->lowestBuildID = std::min(step_->lowestBuildID, id); | ||||||
|  |     }, toplevel); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | void State::processQueueChange(Connection & conn) | ||||||
| { | { | ||||||
|     /* Get the current set of queued builds. */ |     /* Get the current set of queued builds. */ | ||||||
|     std::set<BuildID> currentIds; |     std::map<BuildID, int> currentIds; | ||||||
|     { |     { | ||||||
|         pqxx::work txn(conn); |         pqxx::work txn(conn); | ||||||
|         auto res = txn.exec("select id from Builds where finished = 0"); |         auto res = txn.exec("select id, globalPriority from Builds where finished = 0"); | ||||||
|         for (auto const & row : res) |         for (auto const & row : res) | ||||||
|             currentIds.insert(row["id"].as<BuildID>()); |             currentIds[row["id"].as<BuildID>()] = row["globalPriority"].as<BuildID>(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     auto builds_(builds.lock()); |     auto builds_(builds.lock()); | ||||||
|  |  | ||||||
|     for (auto i = builds_->begin(); i != builds_->end(); ) { |     for (auto i = builds_->begin(); i != builds_->end(); ) { | ||||||
|         if (currentIds.find(i->first) == currentIds.end()) { |         auto b = currentIds.find(i->first); | ||||||
|  |         if (b == currentIds.end()) { | ||||||
|             printMsg(lvlInfo, format("discarding cancelled build %1%") % i->first); |             printMsg(lvlInfo, format("discarding cancelled build %1%") % i->first); | ||||||
|             i = builds_->erase(i); |             i = builds_->erase(i); | ||||||
|             // FIXME: ideally we would interrupt active build steps here. |             // FIXME: ideally we would interrupt active build steps here. | ||||||
|         } else |             continue; | ||||||
|             ++i; |         } | ||||||
|  |         if (i->second->globalPriority < b->second) { | ||||||
|  |             printMsg(lvlInfo, format("priority of build %1% increased") % i->first); | ||||||
|  |             i->second->globalPriority = b->second; | ||||||
|  |             i->second->propagatePriorities(); | ||||||
|  |         } | ||||||
|  |         ++i; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -71,6 +71,7 @@ struct Build | |||||||
|     std::string projectName, jobsetName, jobName; |     std::string projectName, jobsetName, jobName; | ||||||
|     time_t timestamp; |     time_t timestamp; | ||||||
|     unsigned int maxSilentTime, buildTimeout; |     unsigned int maxSilentTime, buildTimeout; | ||||||
|  |     int globalPriority; | ||||||
|  |  | ||||||
|     std::shared_ptr<Step> toplevel; |     std::shared_ptr<Step> toplevel; | ||||||
|  |  | ||||||
| @@ -80,6 +81,8 @@ struct Build | |||||||
|     { |     { | ||||||
|         return projectName + ":" + jobsetName + ":" + jobName; |         return projectName + ":" + jobsetName + ":" + jobName; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     void propagatePriorities(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -113,7 +116,11 @@ struct Step | |||||||
|         /* Point in time after which the step can be retried. */ |         /* Point in time after which the step can be retried. */ | ||||||
|         system_time after; |         system_time after; | ||||||
|  |  | ||||||
|         /* The lowest build ID depending on this step. */ |         /* The highest global priority of any build depending on this | ||||||
|  |            step. */ | ||||||
|  |         int highestGlobalPriority{0}; | ||||||
|  |  | ||||||
|  |         /* The lowest ID of any build depending on this step. */ | ||||||
|         BuildID lowestBuildID{std::numeric_limits<BuildID>::max()}; |         BuildID lowestBuildID{std::numeric_limits<BuildID>::max()}; | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -282,9 +289,11 @@ private: | |||||||
|  |  | ||||||
|     void queueMonitorLoop(); |     void queueMonitorLoop(); | ||||||
|  |  | ||||||
|  |     /* Check the queue for new builds. */ | ||||||
|     void getQueuedBuilds(Connection & conn, std::shared_ptr<nix::StoreAPI> store, unsigned int & lastBuildId); |     void getQueuedBuilds(Connection & conn, std::shared_ptr<nix::StoreAPI> store, unsigned int & lastBuildId); | ||||||
|  |  | ||||||
|     void removeCancelledBuilds(Connection & conn); |     /* Handle cancellation, deletion and priority bumps. */ | ||||||
|  |     void processQueueChange(Connection & conn); | ||||||
|  |  | ||||||
|     Step::ptr createStep(std::shared_ptr<nix::StoreAPI> store, const nix::Path & drvPath, |     Step::ptr createStep(std::shared_ptr<nix::StoreAPI> store, const nix::Path & drvPath, | ||||||
|         Build::ptr referringBuild, Step::ptr referringStep, std::set<nix::Path> & finishedDrvs, |         Build::ptr referringBuild, Step::ptr referringStep, std::set<nix::Path> & finishedDrvs, | ||||||
|   | |||||||
| @@ -475,6 +475,23 @@ sub keep : Chained('buildChain') PathPart Args(1) { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | sub bump : Chained('buildChain') PathPart('bump') { | ||||||
|  |     my ($self, $c, $x) = @_; | ||||||
|  |  | ||||||
|  |     my $build = $c->stash->{build}; | ||||||
|  |  | ||||||
|  |     requireProjectOwner($c, $build->project); # FIXME: require admin? | ||||||
|  |  | ||||||
|  |     $c->model('DB')->schema->txn_do(sub { | ||||||
|  |         $build->update({globalpriority => time()}); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $c->flash->{successMsg} = "Build has been bumped to the front of the queue."; | ||||||
|  |  | ||||||
|  |     $c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| sub add_to_release : Chained('buildChain') PathPart('add-to-release') Args(0) { | sub add_to_release : Chained('buildChain') PathPart('add-to-release') Args(0) { | ||||||
|     my ($self, $c) = @_; |     my ($self, $c) = @_; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -88,7 +88,7 @@ sub queue_GET { | |||||||
|     $c->stash->{flashMsg} //= $c->flash->{buildMsg}; |     $c->stash->{flashMsg} //= $c->flash->{buildMsg}; | ||||||
|     $self->status_ok( |     $self->status_ok( | ||||||
|         $c, |         $c, | ||||||
|         entity => [$c->model('DB::Builds')->search({finished => 0}, { order_by => ["id"]})] |         entity => [$c->model('DB::Builds')->search({finished => 0}, { order_by => ["globalpriority desc", "id"]})] | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -138,6 +138,12 @@ __PACKAGE__->table("Builds"); | |||||||
|   default_value: 0 |   default_value: 0 | ||||||
|   is_nullable: 0 |   is_nullable: 0 | ||||||
|  |  | ||||||
|  | =head2 globalpriority | ||||||
|  |  | ||||||
|  |   data_type: 'integer' | ||||||
|  |   default_value: 0 | ||||||
|  |   is_nullable: 0 | ||||||
|  |  | ||||||
| =head2 busy | =head2 busy | ||||||
|  |  | ||||||
|   data_type: 'integer' |   data_type: 'integer' | ||||||
| @@ -241,6 +247,8 @@ __PACKAGE__->add_columns( | |||||||
|   { data_type => "text", is_nullable => 1 }, |   { data_type => "text", is_nullable => 1 }, | ||||||
|   "priority", |   "priority", | ||||||
|   { data_type => "integer", default_value => 0, is_nullable => 0 }, |   { data_type => "integer", default_value => 0, is_nullable => 0 }, | ||||||
|  |   "globalpriority", | ||||||
|  |   { data_type => "integer", default_value => 0, is_nullable => 0 }, | ||||||
|   "busy", |   "busy", | ||||||
|   { data_type => "integer", default_value => 0, is_nullable => 0 }, |   { data_type => "integer", default_value => 0, is_nullable => 0 }, | ||||||
|   "locker", |   "locker", | ||||||
| @@ -550,8 +558,8 @@ __PACKAGE__->many_to_many( | |||||||
| ); | ); | ||||||
|  |  | ||||||
|  |  | ||||||
| # Created by DBIx::Class::Schema::Loader v0.07043 @ 2015-07-30 16:52:20 | # Created by DBIx::Class::Schema::Loader v0.07043 @ 2015-08-10 15:10:41 | ||||||
| # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Y2lDtgY8EBLOuCHAI8fWRQ | # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:rjifgnPtjY96MaQ7eiGzaA | ||||||
|  |  | ||||||
| __PACKAGE__->has_many( | __PACKAGE__->has_many( | ||||||
|   "dependents", |   "dependents", | ||||||
|   | |||||||
| @@ -96,6 +96,7 @@ | |||||||
|           <li><a href="[% c.uri_for('/build' build.id 'restart') %]">Restart</a></li> |           <li><a href="[% c.uri_for('/build' build.id 'restart') %]">Restart</a></li> | ||||||
|         [% ELSE %] |         [% ELSE %] | ||||||
|           <li><a href="[% c.uri_for('/build' build.id 'cancel') %]">Cancel</a></li> |           <li><a href="[% c.uri_for('/build' build.id 'cancel') %]">Cancel</a></li> | ||||||
|  |           <li><a href="[% c.uri_for('/build' build.id 'bump') %]">Bump up</a></li> | ||||||
|         [% END %] |         [% END %] | ||||||
|         [% IF available && project.releases %] |         [% IF available && project.releases %] | ||||||
|           [% INCLUDE menuItem |           [% INCLUDE menuItem | ||||||
|   | |||||||
| @@ -156,9 +156,13 @@ create table Builds ( | |||||||
|     nixExprInput  text, |     nixExprInput  text, | ||||||
|     nixExprPath   text, |     nixExprPath   text, | ||||||
|  |  | ||||||
|     -- Information about scheduled builds. |     -- Priority within a jobset, set via meta.schedulingPriority. | ||||||
|     priority      integer not null default 0, |     priority      integer not null default 0, | ||||||
|  |  | ||||||
|  |     -- Priority among all builds, used by the admin to bump builds to | ||||||
|  |     -- the front of the queue via the web interface. | ||||||
|  |     globalPriority integer not null default 0, | ||||||
|  |  | ||||||
|     -- FIXME: remove (obsolete with the new queue runner) |     -- FIXME: remove (obsolete with the new queue runner) | ||||||
|     busy          integer not null default 0, -- true means someone is building this job now |     busy          integer not null default 0, -- true means someone is building this job now | ||||||
|     locker        text, -- !!! hostname/pid of the process building this job? |     locker        text, -- !!! hostname/pid of the process building this job? | ||||||
| @@ -218,6 +222,10 @@ create function notifyBuildCancelled() returns trigger as 'begin notify builds_c | |||||||
| create trigger BuildCancelled after update on Builds for each row | create trigger BuildCancelled after update on Builds for each row | ||||||
|   when (old.finished = 0 and new.finished = 1 and new.buildStatus = 4) execute procedure notifyBuildCancelled(); |   when (old.finished = 0 and new.finished = 1 and new.buildStatus = 4) execute procedure notifyBuildCancelled(); | ||||||
|  |  | ||||||
|  | create function notifyBuildBumped() returns trigger as 'begin notify builds_bumped; return null; end;' language plpgsql; | ||||||
|  | create trigger BuildBumped after update on Builds for each row | ||||||
|  |   when (old.globalPriority != new.globalPriority) execute procedure notifyBuildBumped(); | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								src/sql/upgrade-40.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/sql/upgrade-40.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | alter table Builds add column globalPriority integer not null default 0; | ||||||
|  |  | ||||||
|  | create function notifyBuildBumped() returns trigger as 'begin notify builds_bumped; return null; end;' language plpgsql; | ||||||
|  | create trigger BuildBumped after update on Builds for each row | ||||||
|  |   when (old.globalPriority != new.globalPriority) execute procedure notifyBuildBumped(); | ||||||
		Reference in New Issue
	
	Block a user