Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean stale builds from state #171

Merged
merged 1 commit into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions lib/common.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@ open Devkit

module Slack_timestamp = Fresh (String) ()

module Timestamp = struct
type t = Ptime.t

let wrap s =
match Ptime.of_rfc3339 s with
| Ok (t, _, _) -> t
| Error _ -> failwith "Invalid timestamp"
let unwrap t = Ptime.to_rfc3339 t

let wrap_with_fallback ?(fallback = Ptime_clock.now ()) s =
match Ptime.of_rfc3339 s with
| Ok (t, _, _) -> t
| Error _ -> fallback
end

module Slack_channel : sig
type 'kind t

Expand Down
25 changes: 22 additions & 3 deletions lib/dune
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
(library
(name lib)
(libraries atdgen atdgen-runtime biniou cstruct curl curl.lwt base64
devkit devkit.core extlib hex lwt lwt.unix nocrypto omd re2 sexplib0 uri
yojson)
(libraries
atdgen
atdgen-runtime
base64
biniou
cstruct
curl
curl.lwt
devkit
devkit.core
extlib
hex
lwt
lwt.unix
nocrypto
omd
ptime
ptime.clock
re2
sexplib0
uri
yojson)
(preprocess
(pps lwt_ppx)))

Expand Down
9 changes: 5 additions & 4 deletions lib/state.atd
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ type 'v map_as_object <ocaml from="Common"> = abstract
type 'v int_map_as_object <ocaml from="Common"> = abstract
type 'v table_as_object <ocaml from="Common"> = abstract
type string_set <ocaml from="Common"> = abstract
type timestamp = string wrap <ocaml t="Common.Slack_timestamp.t" wrap="Common.Slack_timestamp.inject" unwrap="Common.Slack_timestamp.project">
type slack_timestamp = string wrap <ocaml t="Common.Slack_timestamp.t" wrap="Common.Slack_timestamp.inject" unwrap="Common.Slack_timestamp.project">
type timestamp = string wrap <ocaml module="Common.Timestamp">
type user_id = string wrap <ocaml t="Common.Slack_user_id.t" wrap="Common.Slack_user_id.inject" unwrap="Common.Slack_user_id.project">
type channel_id = string wrap <ocaml t="Common.Slack_channel.Ident.t" wrap="Common.Slack_channel.Ident.inject" unwrap="Common.Slack_channel.Ident.project">
type any_channel = string wrap <ocaml t="Common.Slack_channel.Any.t" wrap="Common.Slack_channel.Any.inject" unwrap="Common.Slack_channel.Any.project">
Expand All @@ -26,8 +27,8 @@ type build_status = {
commit: ci_commit;
is_finished: bool;
failed_steps: failed_step list;
created_at: string;
finished_at: string nullable;
created_at: timestamp;
finished_at: timestamp nullable;
}

(* A map from builds numbers to build statuses *)
Expand All @@ -50,7 +51,7 @@ type commit_sets = {
type pipeline_commits = commit_sets map_as_object

type slack_thread = {
ts: timestamp;
ts: slack_timestamp;
channel: any_channel;
cid: channel_id;
}
Expand Down
19 changes: 16 additions & 3 deletions lib/state.ml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ let set_repo_pipeline_status { state } (n : Github_t.status_notification) =
in
let finished_at =
match is_finished with
| true -> Some n.updated_at
| true -> Some (Timestamp.wrap_with_fallback n.updated_at)
| false -> None
in
(* Even if this is an initial build state, we can't just set an "empty" state because we don't know
Expand All @@ -84,7 +84,7 @@ let set_repo_pipeline_status { state } (n : Github_t.status_notification) =
commit;
is_finished;
failed_steps;
created_at = n.updated_at;
created_at = Timestamp.wrap_with_fallback n.updated_at;
finished_at;
}
in
Expand All @@ -107,11 +107,24 @@ let set_repo_pipeline_status { state } (n : Github_t.status_notification) =
in
let rm_successful_build =
update_builds_in_branches ~branches:n.branches ~f:(fun builds_map ->
let open Ptime in
let threshold =
(* 2h as the threshold for long running or stale builds *)
Ptime.Span.of_int_s (60 * 60 * 2)
in
let is_past_threshold (build_status : State_t.build_status) threshold =
let now = Ptime_clock.now () in
match add_span build_status.created_at threshold with
| Some t_plus_threshold -> is_earlier ~than:now t_plus_threshold
| None -> false
in
IntMap.remove build_number builds_map
|> IntMap.filter (fun build_number' build_status ->
(* Remove old builds without failed steps because they were fixed and cleaned from state *)
match build_status.State_t.failed_steps with
(* Remove old builds without failed steps because they were fixed and cleaned from state *)
| [] when build_number' < build_number && build_status.is_finished -> false
(* Remove builds that ran for more than the threshold or for which we didn't get an end notification *)
| _ when is_past_threshold build_status threshold -> false
| _ -> true))
in
let rm_successful_step =
Expand Down
8 changes: 6 additions & 2 deletions lib/util.ml
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,12 @@ module Build = struct
String.compare s1.name s2.name)
in
let current_build =
(* We will not get an exception here because we checked that the build is failed and finished *)
IntMap.find current_build_number builds_maps
try IntMap.find current_build_number builds_maps
with _ ->
(* edge case: we got a notification for a build that ran longer than the defined threshold
and was cleaned from state. This shouldn't happen, but adding an error message to make
clearer what is happening if it does. *)
failwith "Error: failed to find current build in state, maybe it was cleaned up?"
in
List.filter
(fun (step : State_t.failed_step) ->
Expand Down
Loading