Skip to content

Commit 5919d5d

Browse files
committed
Switch to use locking for issue data.
1 parent b11c42f commit 5919d5d

File tree

2 files changed

+71
-40
lines changed

2 files changed

+71
-40
lines changed

src/db/issue_data.rs

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,76 @@
44
//! Each issue has a unique "key" where you can store data under. Typically
55
//! that key should be the name of the handler. The data can be anything that
66
//! can be serialized to JSON.
7+
//!
8+
//! Note that this uses crude locking, so try to keep the duration between
9+
//! loading and saving to a minimum.
710
811
use crate::github::Issue;
912
use anyhow::{Context, Result};
1013
use serde::{Deserialize, Serialize};
1114
use tokio_postgres::types::Json;
12-
use tokio_postgres::Client as DbClient;
15+
use tokio_postgres::{Client as DbClient, Transaction};
1316

14-
pub async fn load<T: for<'a> Deserialize<'a>>(
15-
db: &DbClient,
16-
issue: &Issue,
17-
key: &str,
18-
) -> Result<Option<T>> {
19-
let repo = issue.repository().to_string();
20-
let data = db
21-
.query_opt(
22-
"SELECT data FROM issue_data WHERE \
23-
repo = $1 AND issue_number = $2 AND key = $3",
24-
&[&repo, &(issue.number as i32), &key],
25-
)
26-
.await
27-
.context("selecting issue data")?
28-
.map(|row| row.get::<usize, Json<T>>(0).0);
29-
Ok(data)
17+
pub struct IssueData<'db, T>
18+
where
19+
T: for<'a> Deserialize<'a> + Serialize + Default + std::fmt::Debug + Sync,
20+
{
21+
transaction: Transaction<'db>,
22+
repo: String,
23+
issue_number: i32,
24+
key: String,
25+
pub data: T,
3026
}
3127

32-
pub async fn save<T: Serialize + std::fmt::Debug + Sync>(
33-
db: &DbClient,
34-
issue: &Issue,
35-
key: &str,
36-
data: &T,
37-
) -> Result<()> {
38-
let repo = issue.repository().to_string();
39-
db.execute(
40-
"INSERT INTO issue_data (repo, issue_number, key, data) VALUES ($1, $2, $3, $4) \
41-
ON CONFLICT (repo, issue_number, key) DO UPDATE SET data=EXCLUDED.data",
42-
&[&repo, &(issue.number as i32), &key, &Json(data)],
43-
)
44-
.await
45-
.context("inserting issue data")?;
46-
Ok(())
28+
impl<'db, T> IssueData<'db, T>
29+
where
30+
T: for<'a> Deserialize<'a> + Serialize + Default + std::fmt::Debug + Sync,
31+
{
32+
pub async fn load(
33+
db: &'db mut DbClient,
34+
issue: &Issue,
35+
key: &str,
36+
) -> Result<IssueData<'db, T>> {
37+
let repo = issue.repository().to_string();
38+
let issue_number = issue.number as i32;
39+
let transaction = db.transaction().await?;
40+
transaction
41+
.execute("LOCK TABLE issue_data", &[])
42+
.await
43+
.context("locking issue data")?;
44+
let data = transaction
45+
.query_opt(
46+
"SELECT data FROM issue_data WHERE \
47+
repo = $1 AND issue_number = $2 AND key = $3",
48+
&[&repo, &issue_number, &key],
49+
)
50+
.await
51+
.context("selecting issue data")?
52+
.map(|row| row.get::<usize, Json<T>>(0).0)
53+
.unwrap_or_default();
54+
Ok(IssueData {
55+
transaction,
56+
repo,
57+
issue_number,
58+
key: key.to_string(),
59+
data,
60+
})
61+
}
62+
63+
pub async fn save(self) -> Result<()> {
64+
self.transaction
65+
.execute(
66+
"INSERT INTO issue_data (repo, issue_number, key, data) \
67+
VALUES ($1, $2, $3, $4) \
68+
ON CONFLICT (repo, issue_number, key) DO UPDATE SET data=EXCLUDED.data",
69+
&[&self.repo, &self.issue_number, &self.key, &Json(&self.data)],
70+
)
71+
.await
72+
.context("inserting issue data")?;
73+
self.transaction
74+
.commit()
75+
.await
76+
.context("committing issue data")?;
77+
Ok(())
78+
}
4779
}

src/handlers/mentions.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
use crate::{
66
config::{MentionsConfig, MentionsPathConfig},
7-
db::issue_data,
7+
db::issue_data::IssueData,
88
github::{files_changed, IssuesAction, IssuesEvent},
99
handlers::Context,
1010
};
@@ -78,14 +78,13 @@ pub(super) async fn handle_input(
7878
event: &IssuesEvent,
7979
input: MentionsInput,
8080
) -> anyhow::Result<()> {
81-
let client = ctx.db.get().await;
82-
let mut state: MentionState = issue_data::load(&client, &event.issue, MENTIONS_KEY)
83-
.await?
84-
.unwrap_or_default();
81+
let mut client = ctx.db.get().await;
82+
let mut state: IssueData<'_, MentionState> =
83+
IssueData::load(&mut client, &event.issue, MENTIONS_KEY).await?;
8584
// Build the message to post to the issue.
8685
let mut result = String::new();
8786
for to_mention in &input.paths {
88-
if state.paths.iter().any(|p| p == to_mention) {
87+
if state.data.paths.iter().any(|p| p == to_mention) {
8988
// Avoid duplicate mentions.
9089
continue;
9190
}
@@ -100,15 +99,15 @@ pub(super) async fn handle_input(
10099
if !reviewers.is_empty() {
101100
write!(result, "\n\ncc {}", reviewers.join(", ")).unwrap();
102101
}
103-
state.paths.push(to_mention.to_string());
102+
state.data.paths.push(to_mention.to_string());
104103
}
105104
if !result.is_empty() {
106105
event
107106
.issue
108107
.post_comment(&ctx.github, &result)
109108
.await
110109
.context("failed to post mentions comment")?;
111-
issue_data::save(&client, &event.issue, MENTIONS_KEY, &state).await?;
110+
state.save().await?;
112111
}
113112
Ok(())
114113
}

0 commit comments

Comments
 (0)