Skip to content

Commit f978428

Browse files
authored
Merge pull request #363 from Kobzol/check-suite-refactor
Detect completed builds based only on workflow completion events
2 parents 2ab8f3a + 4d2b9af commit f978428

22 files changed

+423
-1543
lines changed

docs/design.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,13 @@ repository:
4949
(based on the `timeout` configured for the repository), it will cancel them.
5050
- Reload user permissions from the Team API.
5151
- Reload `rust-bors.toml` config for the repository from its main branch.
52+
- Reload the mergeability status of open PRs from GitHub.
53+
- Sync the status of PRs between the DB and GitHub.
54+
- Run the merge queue.
5255

5356
## Concurrency
5457
The bot is currently listening for GitHub webhooks concurrently, however it handles all commands serially, to avoid
55-
race conditions. This limitation is expected to be lifted in the future.
58+
race conditions. This limitation might be lifted in the future.
5659

5760
## Try builds
5861
A try build means that you execute a specific CI job on a PR (without merging the PR), to test if the job passes C
@@ -202,6 +205,13 @@ With [homu](https://github.com/rust-lang/homu) (the old bors implementation), Gi
202205
to use a "fake" job that marked the whole CI workflow as succeeded or failed, to signal to bors if it should consider
203206
the workflow to be OK or not.
204207

205-
The new bors uses a different approach. It asks GitHub which [check suites](https://docs.github.com/en/rest/checks/suites)
206-
are attached to a given commit, and then it waits until all of these check suites complete (or until a timeout is reached).
207-
Thanks to this approach, there is no need to introduce fake CI jobs.
208+
The new bors uses a different approach. In an earlier implementation, it used to ask GitHub which [check suites](https://docs.github.com/en/rest/checks/suites) were attached to a given commit, and then it waited until all of these check suites were completed (or until a timeout was reached). However, this was too "powerful" for rust-lang/rust, where we currently have only a single CI workflow per commit, and it was producing certain race conditions, where GitHub would send us a webhook that a check suite was completed, but when we then asked the GitHub API about the status of the check suite, it was still marked as pending.
209+
210+
To make the implementation more robust, it now behaves as follow:
211+
- We listen for the workflow completed webhook.
212+
- When it is received, we ask GitHub what are all the workflows attached to the workflow's check suite.
213+
- If all of them are completed, we mark the build as finished.
214+
215+
Note that we explicitly do not read the "Check suite was completed" webhook, because it can actually be received *before* a webhook that tells us that the last workflow of that check suite was completed. If that happens, we could mark a build as completed without knowing the final conclusion of its workflows. That is not a big problem, but it would mean that we sometimes cannot post the real status of a workflow in the "build completed" bors comment on GitHub. So instead we just listen for the completed workflows.
216+
217+
In any case, with new bors there is no need to introduce fake conclusion CI jobs.

docs/development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ One-time setup:
5555
- Configure its private key.
5656
- Give it permissions for `Actions` (r/w), `Checks` (r/w), `Commit statuses` (r), `Contents` (r/w), `Issues` (r/w) and
5757
`Pull requests` (r/w).
58-
- Subscribe it to webhook events `Check suite`, `Check run`, `Issue comment`, `Issues`, `Pull request`,
58+
- Subscribe it to webhook events `Issue comment`, `Issues`, `Pull request`,
5959
`Pull request review`, `Pull request review comment` and `Workflow run`.
6060
- Install your GitHub app on some test repository where you want to test bors.
6161
- Don't forget to configure `rust-bors.toml` in the root of the repository, and also add some example CI workflows.

src/bors/comment.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,10 @@ fn list_workflows_status(workflows: &[WorkflowModel]) -> String {
196196
"- [{}]({}) {}",
197197
w.name,
198198
w.url,
199-
if w.status == WorkflowStatus::Success {
200-
":white_check_mark:"
201-
} else {
202-
":x:"
199+
match w.status {
200+
WorkflowStatus::Success => ":white_check_mark:",
201+
WorkflowStatus::Failure => ":x:",
202+
WorkflowStatus::Pending => ":question:",
203203
}
204204
)
205205
})

src/bors/event.rs

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::database::{WorkflowStatus, WorkflowType};
22
use crate::github::{CommitSha, GithubRepoName, GithubUser, PullRequest, PullRequestNumber};
33
use chrono::Duration;
4-
use octocrab::models::RunId;
4+
use octocrab::models::{CheckSuiteId, RunId};
55

66
#[derive(Debug)]
77
pub enum BorsRepositoryEvent {
@@ -31,12 +31,9 @@ pub enum BorsRepositoryEvent {
3131
/// when a branch is deleted or when a tag is deleted.
3232
PushToBranch(PushToBranch),
3333
/// A workflow run on Github Actions or a check run from external CI system has been started.
34-
WorkflowStarted(WorkflowStarted),
34+
WorkflowStarted(WorkflowRunStarted),
3535
/// A workflow run on Github Actions or a check run from external CI system has been completed.
36-
WorkflowCompleted(WorkflowCompleted),
37-
/// A check suite has been completed, either as a workflow run on Github Actions, or as a
38-
/// workflow from some external CI system.
39-
CheckSuiteCompleted(CheckSuiteCompleted),
36+
WorkflowCompleted(WorkflowRunCompleted),
4037
}
4138

4239
impl BorsRepositoryEvent {
@@ -56,7 +53,6 @@ impl BorsRepositoryEvent {
5653
BorsRepositoryEvent::PushToBranch(payload) => &payload.repository,
5754
BorsRepositoryEvent::WorkflowStarted(workflow) => &workflow.repository,
5855
BorsRepositoryEvent::WorkflowCompleted(workflow) => &workflow.repository,
59-
BorsRepositoryEvent::CheckSuiteCompleted(payload) => &payload.repository,
6056
}
6157
}
6258
}
@@ -166,7 +162,7 @@ pub struct PushToBranch {
166162
}
167163

168164
#[derive(Debug)]
169-
pub struct WorkflowStarted {
165+
pub struct WorkflowRunStarted {
170166
pub repository: GithubRepoName,
171167
pub name: String,
172168
pub branch: String,
@@ -177,18 +173,13 @@ pub struct WorkflowStarted {
177173
}
178174

179175
#[derive(Debug)]
180-
pub struct WorkflowCompleted {
176+
pub struct WorkflowRunCompleted {
181177
pub repository: GithubRepoName,
182178
pub branch: String,
183179
pub commit_sha: CommitSha,
184180
pub run_id: RunId,
185181
pub status: WorkflowStatus,
186182
pub running_time: Option<Duration>,
187-
}
188-
189-
#[derive(Debug)]
190-
pub struct CheckSuiteCompleted {
191-
pub repository: GithubRepoName,
192-
pub branch: String,
193-
pub commit_sha: CommitSha,
183+
/// Check suite to which this workflow is attached.
184+
pub check_suite_id: CheckSuiteId,
194185
}

src/bors/handlers/info.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub(super) async fn command_info(
6060

6161
#[cfg(test)]
6262
mod tests {
63-
use crate::tests::mocks::{Workflow, WorkflowEvent, run_test};
63+
use crate::tests::mocks::{WorkflowEvent, WorkflowRunData, run_test};
6464

6565
#[sqlx::test]
6666
async fn info_for_unapproved_pr(pool: sqlx::PgPool) {
@@ -156,7 +156,9 @@ mod tests {
156156
tester.expect_comments(1).await;
157157

158158
tester
159-
.workflow_event(WorkflowEvent::started(Workflow::from(tester.try_branch())))
159+
.workflow_event(WorkflowEvent::started(WorkflowRunData::from(
160+
tester.try_branch(),
161+
)))
160162
.await?;
161163

162164
tester.post_comment("@bors info").await?;

src/bors/handlers/mod.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ use crate::bors::handlers::review::{
1616
command_approve, command_close_tree, command_open_tree, command_unapprove,
1717
};
1818
use crate::bors::handlers::trybuild::{TRY_BRANCH_NAME, command_try_build, command_try_cancel};
19-
use crate::bors::handlers::workflow::{
20-
handle_check_suite_completed, handle_workflow_completed, handle_workflow_started,
21-
};
19+
use crate::bors::handlers::workflow::{handle_workflow_completed, handle_workflow_started};
2220
use crate::bors::merge_queue::{AUTO_BRANCH_NAME, MergeQueueSender};
2321
use crate::bors::{BorsContext, Comment, RepositoryState};
2422
use crate::database::{DelegatedPermission, PullRequestModel};
@@ -123,15 +121,6 @@ pub async fn handle_bors_repository_event(
123121
.instrument(span.clone())
124122
.await?;
125123
}
126-
BorsRepositoryEvent::CheckSuiteCompleted(payload) => {
127-
let span = tracing::info_span!(
128-
"Check suite completed",
129-
repo = payload.repository.to_string(),
130-
);
131-
handle_check_suite_completed(repo, db, payload, &merge_queue_tx)
132-
.instrument(span.clone())
133-
.await?;
134-
}
135124
BorsRepositoryEvent::PullRequestEdited(payload) => {
136125
let span =
137126
tracing::info_span!("Pull request edited", repo = payload.repository.to_string());

src/bors/handlers/refresh.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ timeout = 3600
304304
.run_test(async |tester| {
305305
tester.post_comment("@bors try").await?;
306306
tester.expect_comments(1).await;
307-
tester.start_workflow(tester.try_branch()).await?;
307+
tester.workflow_start(tester.try_branch()).await?;
308308

309309
with_mocked_time(Duration::from_secs(4000), async {
310310
tester.cancel_timed_out_builds().await;

0 commit comments

Comments
 (0)