Skip to content
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
18 changes: 16 additions & 2 deletions crates/database/src/issues.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ impl NewEvent {
&& d.contains('\n')
{
return Err(AppError::BadRequest(
"description must be a single line (no newlines); use `message` for body text".into(),
"description must be a single line (no newlines); use `message` for body text"
.into(),
));
}

Expand Down Expand Up @@ -504,12 +505,25 @@ async fn group_has_open_incident(db: &mut AsyncPgConnection, root_server_id: Uui
/// The boolean is consumed by `re_evaluate_incident_membership` to decide
/// whether a Slack `incident_open` outbox row should be enqueued (re-joining
/// an existing incident shouldn't re-notify).
///
/// Two parallel event pushes against the same group must not each insert a
/// fresh incident row. We take a `FOR UPDATE` lock on the root server's
/// row up-front so the find-or-create pair serializes per-server. A
/// unique partial index on `incidents (server_id) WHERE closed_at IS NULL`
/// is the belt-and-braces backstop at the DB layer.
async fn find_or_open_incident(
db: &mut AsyncPgConnection,
root_server_id: Uuid,
opened_at: Timestamp,
) -> Result<(Uuid, bool)> {
use crate::schema::incidents;
use crate::schema::{incidents, servers};

let _server_lock: Uuid = servers::table
.select(servers::id)
.filter(servers::id.eq(root_server_id))
.for_update()
.first(db)
.await?;

let open: Option<Incident> = incidents::table
.select(Incident::as_select())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DROP INDEX incidents_open_by_server;
CREATE INDEX incidents_open_by_server ON incidents (server_id) WHERE closed_at IS NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Enforce at most one open incident per server. Promotes the existing
-- partial index to UNIQUE so two concurrent event pushes for the same
-- server can no longer both insert a fresh incident row.

DROP INDEX incidents_open_by_server;
CREATE UNIQUE INDEX incidents_open_by_server ON incidents (server_id) WHERE closed_at IS NULL;