Skip to content

Commit 544f793

Browse files
committed
refactor(db)!: Make DelayId generation conflict free
Use same structure as with `ExecutionIdDerived` to avoid generating (seeded) random ULID that might conflict in the database.
1 parent 76298ec commit 544f793

10 files changed

Lines changed: 180 additions & 94 deletions

File tree

crates/concepts/src/lib.rs

Lines changed: 122 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ impl Params {
883883
}
884884

885885
pub mod prefixed_ulid {
886-
use crate::{JoinSetId, JoinSetIdParseError};
886+
use crate::{JoinSetId, JoinSetIdParseError, JoinSetKind};
887887
use serde_with::{DeserializeFromStr, SerializeDisplay};
888888
use std::{
889889
fmt::{Debug, Display},
@@ -1028,7 +1028,7 @@ pub mod prefixed_ulid {
10281028
pub type ExecutorId = PrefixedUlid<prefix::Exr>;
10291029
pub type ExecutionIdTopLevel = PrefixedUlid<prefix::E>;
10301030
pub type RunId = PrefixedUlid<prefix::Run>;
1031-
pub type DelayId = PrefixedUlid<prefix::Delay>;
1031+
pub type DelayIdTopLevel = PrefixedUlid<prefix::Delay>;
10321032

10331033
#[cfg(any(test, feature = "test"))]
10341034
impl<'a, T> arbitrary::Arbitrary<'a> for PrefixedUlid<T> {
@@ -1147,26 +1147,32 @@ pub mod prefixed_ulid {
11471147
}
11481148
}
11491149
impl FromStr for ExecutionIdDerived {
1150-
type Err = ExecutionIdDerivedParseError;
1150+
type Err = DerivedIdParseError;
11511151

11521152
fn from_str(input: &str) -> Result<Self, Self::Err> {
1153-
if let Some((prefix, suffix)) = input.split_once(EXECUTION_ID_INFIX) {
1154-
let top_level = PrefixedUlid::from_str(prefix)
1155-
.map_err(ExecutionIdDerivedParseError::PrefixedUlidParseError)?;
1156-
let Some((infix, idx)) = suffix.rsplit_once(EXECUTION_ID_JOIN_SET_INFIX) else {
1157-
return Err(ExecutionIdDerivedParseError::SecondDelimiterNotFound);
1158-
};
1159-
let infix = Arc::from(infix);
1160-
let idx =
1161-
u64::from_str(idx).map_err(ExecutionIdDerivedParseError::ParseIndexError)?;
1162-
Ok(ExecutionIdDerived {
1163-
top_level,
1164-
infix,
1165-
idx,
1166-
})
1167-
} else {
1168-
Err(ExecutionIdDerivedParseError::FirstDelimiterNotFound)
1169-
}
1153+
let (top_level, infix, idx) = derived_from_str(input)?;
1154+
Ok(ExecutionIdDerived {
1155+
top_level,
1156+
infix,
1157+
idx,
1158+
})
1159+
}
1160+
}
1161+
1162+
fn derived_from_str<T: 'static>(
1163+
input: &str,
1164+
) -> Result<(PrefixedUlid<T>, Arc<str>, u64), DerivedIdParseError> {
1165+
if let Some((prefix, suffix)) = input.split_once(EXECUTION_ID_INFIX) {
1166+
let top_level = PrefixedUlid::from_str(prefix)
1167+
.map_err(DerivedIdParseError::PrefixedUlidParseError)?;
1168+
let Some((infix, idx)) = suffix.rsplit_once(EXECUTION_ID_JOIN_SET_INFIX) else {
1169+
return Err(DerivedIdParseError::SecondDelimiterNotFound);
1170+
};
1171+
let infix = Arc::from(infix);
1172+
let idx = u64::from_str(idx).map_err(DerivedIdParseError::ParseIndexError)?;
1173+
Ok((top_level, infix, idx))
1174+
} else {
1175+
Err(DerivedIdParseError::FirstDelimiterNotFound)
11701176
}
11711177
}
11721178

@@ -1180,17 +1186,15 @@ pub mod prefixed_ulid {
11801186
}
11811187

11821188
#[derive(Debug, thiserror::Error)]
1183-
pub enum ExecutionIdDerivedParseError {
1189+
pub enum DerivedIdParseError {
11841190
#[error(transparent)]
11851191
PrefixedUlidParseError(PrefixedUlidParseError),
1186-
#[error("cannot parse derived execution id - delimiter `{EXECUTION_ID_INFIX}` not found")]
1192+
#[error("cannot parse derived id - delimiter `{EXECUTION_ID_INFIX}` not found")]
11871193
FirstDelimiterNotFound,
1188-
#[error(
1189-
"cannot parse derived execution id - delimiter `{EXECUTION_ID_JOIN_SET_INFIX}` not found"
1190-
)]
1194+
#[error("cannot parse derived id - delimiter `{EXECUTION_ID_JOIN_SET_INFIX}` not found")]
11911195
SecondDelimiterNotFound,
11921196
#[error(
1193-
"cannot parse derived execution id - suffix after `{EXECUTION_ID_JOIN_SET_INFIX}` must be a number"
1197+
"cannot parse derived id - suffix after `{EXECUTION_ID_JOIN_SET_INFIX}` must be a number"
11941198
)]
11951199
ParseIndexError(ParseIntError),
11961200
}
@@ -1291,16 +1295,16 @@ pub mod prefixed_ulid {
12911295
ExecutionIdDerived::from_str(input)
12921296
.map(ExecutionId::Derived)
12931297
.map_err(|err| match err {
1294-
ExecutionIdDerivedParseError::FirstDelimiterNotFound => {
1298+
DerivedIdParseError::FirstDelimiterNotFound => {
12951299
unreachable!("first delimiter checked")
12961300
}
1297-
ExecutionIdDerivedParseError::SecondDelimiterNotFound => {
1301+
DerivedIdParseError::SecondDelimiterNotFound => {
12981302
ExecutionIdParseError::SecondDelimiterNotFound
12991303
}
1300-
ExecutionIdDerivedParseError::PrefixedUlidParseError(err) => {
1304+
DerivedIdParseError::PrefixedUlidParseError(err) => {
13011305
ExecutionIdParseError::PrefixedUlidParseError(err)
13021306
}
1303-
ExecutionIdDerivedParseError::ParseIndexError(err) => {
1307+
DerivedIdParseError::ParseIndexError(err) => {
13041308
ExecutionIdParseError::ParseIndexError(err)
13051309
}
13061310
})
@@ -1354,6 +1358,94 @@ pub mod prefixed_ulid {
13541358
Ok(ExecutionId::TopLevel(PrefixedUlid::arbitrary(u)?))
13551359
}
13561360
}
1361+
1362+
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, SerializeDisplay, DeserializeFromStr)]
1363+
pub struct DelayId {
1364+
top_level: DelayIdTopLevel,
1365+
infix: Arc<str>,
1366+
idx: u64,
1367+
}
1368+
impl DelayId {
1369+
#[must_use]
1370+
pub fn new_oneoff(execution_id: &ExecutionId, join_set_id: &JoinSetId) -> DelayId {
1371+
assert!(join_set_id.kind == JoinSetKind::OneOff);
1372+
let ExecutionIdDerived {
1373+
top_level,
1374+
infix,
1375+
idx,
1376+
} = execution_id.next_level(join_set_id);
1377+
let top_level = DelayIdTopLevel::new(top_level.ulid);
1378+
DelayId {
1379+
top_level,
1380+
infix,
1381+
idx,
1382+
}
1383+
}
1384+
1385+
#[must_use]
1386+
pub fn get_incremented(&self) -> Self {
1387+
Self {
1388+
top_level: self.top_level,
1389+
infix: self.infix.clone(),
1390+
idx: self.idx + 1,
1391+
}
1392+
}
1393+
1394+
fn display_or_debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1395+
let DelayId {
1396+
top_level,
1397+
infix,
1398+
idx,
1399+
} = self;
1400+
write!(
1401+
f,
1402+
"{top_level}{EXECUTION_ID_INFIX}{infix}{EXECUTION_ID_JOIN_SET_INFIX}{idx}"
1403+
)
1404+
}
1405+
}
1406+
1407+
pub mod delay_impl {
1408+
use super::{DelayId, DerivedIdParseError, derived_from_str};
1409+
use std::{
1410+
fmt::{Debug, Display},
1411+
str::FromStr,
1412+
};
1413+
1414+
impl Debug for DelayId {
1415+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1416+
self.display_or_debug(f)
1417+
}
1418+
}
1419+
1420+
impl Display for DelayId {
1421+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1422+
self.display_or_debug(f)
1423+
}
1424+
}
1425+
1426+
impl FromStr for DelayId {
1427+
type Err = DerivedIdParseError;
1428+
1429+
fn from_str(input: &str) -> Result<Self, Self::Err> {
1430+
let (top_level, infix, idx) = derived_from_str(input)?;
1431+
Ok(DelayId {
1432+
top_level,
1433+
infix,
1434+
idx,
1435+
})
1436+
}
1437+
}
1438+
1439+
#[cfg(any(test, feature = "test"))]
1440+
impl<'a> arbitrary::Arbitrary<'a> for DelayId {
1441+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1442+
use super::{ExecutionId, JoinSetId};
1443+
let execution_id = ExecutionId::arbitrary(u)?;
1444+
let join_set_id = JoinSetId::arbitrary(u)?;
1445+
Ok(DelayId::new_oneoff(&execution_id, &join_set_id))
1446+
}
1447+
}
1448+
}
13571449
}
13581450

13591451
#[derive(

crates/concepts/src/rusqlite_ext.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{ExecutionId, JoinSetId, prefixed_ulid::ExecutionIdDerived};
1+
use crate::{
2+
ExecutionId, JoinSetId,
3+
prefixed_ulid::{DelayId, ExecutionIdDerived},
4+
};
25
use rusqlite::{
36
ToSql,
47
types::{FromSql, FromSqlError, ToSqlOutput},
@@ -58,3 +61,21 @@ impl FromSql for JoinSetId {
5861
})
5962
}
6063
}
64+
65+
impl ToSql for DelayId {
66+
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
67+
Ok(ToSqlOutput::from(self.to_string()))
68+
}
69+
}
70+
impl FromSql for DelayId {
71+
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
72+
let str = value.as_str()?;
73+
str.parse::<DelayId>().map_err(|err| {
74+
error!(
75+
backtrace = %std::backtrace::Backtrace::capture(),
76+
"Cannot convert to DelayId value:`{str}` - {err:?}"
77+
);
78+
FromSqlError::InvalidType
79+
})
80+
}
81+
}

crates/concepts/src/storage.rs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -203,29 +203,6 @@ pub enum JoinSetResponse {
203203
},
204204
}
205205

206-
impl JoinSetResponse {
207-
#[must_use]
208-
pub fn delay_id(&self) -> Option<DelayId> {
209-
if let JoinSetResponse::DelayFinished { delay_id } = self {
210-
Some(*delay_id)
211-
} else {
212-
None
213-
}
214-
}
215-
216-
#[must_use]
217-
pub fn child_execution_id(&self) -> Option<ExecutionIdDerived> {
218-
if let JoinSetResponse::ChildExecutionFinished {
219-
child_execution_id, ..
220-
} = self
221-
{
222-
Some(child_execution_id.clone())
223-
} else {
224-
None
225-
}
226-
}
227-
}
228-
229206
pub const DUMMY_CREATED: ExecutionEventInner = ExecutionEventInner::Created {
230207
ffqn: FunctionFqn::new_static("", ""),
231208
params: Params::empty(),

crates/db-mem/src/inmemory_dao.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,8 @@ mod index {
445445
.collect::<HashMap<_, _>>();
446446
// Keep only open
447447
for responded in journal.responses.iter().filter_map(|e| {
448-
if let JoinSetResponse::DelayFinished { delay_id } = e.event.event {
449-
Some((e.event.join_set_id.clone(), delay_id))
448+
if let JoinSetResponse::DelayFinished { delay_id } = &e.event.event {
449+
Some((e.event.join_set_id.clone(), delay_id.clone()))
450450
} else {
451451
None
452452
}

crates/db-sqlite/src/sqlite_dao.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,6 @@ type ResponseSubscribers = Arc<
236236
struct PrefixedUlidWrapper<T: 'static>(PrefixedUlid<T>);
237237
type ExecutorIdW = PrefixedUlidWrapper<concepts::prefixed_ulid::prefix::Exr>;
238238
type RunIdW = PrefixedUlidWrapper<concepts::prefixed_ulid::prefix::Run>;
239-
type DelayIdW = PrefixedUlidWrapper<concepts::prefixed_ulid::prefix::Delay>;
240239

241240
impl<T: 'static> FromSql for PrefixedUlidWrapper<T> {
242241
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
@@ -1688,7 +1687,7 @@ impl<S: Sleep> SqlitePool<S> {
16881687
},
16891688
} => IndexAction::NoPendingStateChange(Some(DelayReq {
16901689
join_set_id: join_set_id.clone(),
1691-
delay_id: *delay_id,
1690+
delay_id: delay_id.clone(),
16921691
expires_at: *expires_at,
16931692
})),
16941693

@@ -2094,13 +2093,13 @@ impl<S: Sleep> SqlitePool<S> {
20942093
let created_at: DateTime<Utc> = row.get("created_at")?;
20952094
let join_set_id = row.get::<_, JoinSetId>("join_set_id")?;
20962095
let inner_res = match (
2097-
row.get::<_, Option<DelayIdW>>("delay_id")?,
2096+
row.get::<_, Option<DelayId>>("delay_id")?,
20982097
row.get::<_, Option<ExecutionIdDerived>>("child_execution_id")?,
20992098
row.get::<_, Option<VersionType>>("finished_version")?,
21002099
row.get::<_, Option<JsonWrapper<ExecutionEventInner>>>("json_value")?,
21012100
) {
21022101
(Some(delay_id), None, None, None) => Ok(JoinSetResponse::DelayFinished {
2103-
delay_id: delay_id.0,
2102+
delay_id,
21042103
}),
21052104
(None, Some(child_execution_id), Some(finished_version), Some(result)) => {
21062105
match result.0 {
@@ -2117,7 +2116,7 @@ impl<S: Sleep> SqlitePool<S> {
21172116
}
21182117
}
21192118
(delay, child, finished, result) => {
2120-
error!("Invalid row in t_join_set_response {id} - {:?} {child:?} {finished:?} {:?}", delay.map(|it|it.0), result.map(|it| it.0));
2119+
error!("Invalid row in t_join_set_response {id} - {:?} {child:?} {finished:?} {:?}", delay, result.map(|it| it.0));
21212120
Err(DbError::Specific(SpecificError::ConsistencyError(
21222121
StrVariant::Static("invalid row in t_join_set_response"),
21232122
)))},
@@ -2779,7 +2778,7 @@ impl<S: Sleep> DbConnection for SqlitePool<S> {
27792778
|row| {
27802779
let execution_id = row.get("execution_id")?;
27812780
let join_set_id = row.get::<_, JoinSetId>("join_set_id")?;
2782-
let delay_id = row.get::<_, DelayIdW>("delay_id")?.0;
2781+
let delay_id = row.get::<_, DelayId>("delay_id")?;
27832782
Ok(ExpiredTimer::Delay { execution_id, join_set_id, delay_id })
27842783
},
27852784
).map_err(convert_err)?

crates/executor/src/expired_timers_watcher.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ pub(crate) async fn tick(
173173
join_set_id,
174174
delay_id,
175175
} => {
176-
let event = JoinSetResponse::DelayFinished { delay_id };
177176
debug!(%execution_id, %join_set_id, %delay_id, "Appending DelayFinishedAsyncResponse");
177+
let event = JoinSetResponse::DelayFinished { delay_id };
178178
let res = db_connection
179179
.append_response(
180180
executed_at,
@@ -183,7 +183,7 @@ pub(crate) async fn tick(
183183
)
184184
.await;
185185
if let Err(err) = res {
186-
debug!(%execution_id, %delay_id, "Failed to update expired async timer - {err:?}");
186+
debug!(%execution_id, "Failed to update expired async timer - {err:?}");
187187
} else {
188188
expired_async_timers += 1;
189189
}

0 commit comments

Comments
 (0)