Skip to content

Commit b09f93b

Browse files
committed
Make complete_chunk and fail_chunk not error when doing so for previously completed, failed, or paused chunks
1 parent bcd4161 commit b09f93b

4 files changed

Lines changed: 44 additions & 69 deletions

File tree

‎libs/opsqueue_python/python/opsqueue/exceptions.py‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,6 @@ class TryFromIntError(IncorrectUsageError):
9292
pass
9393

9494

95-
class ChunkNotFoundError(IncorrectUsageError):
96-
"""
97-
Raised when a method is used to look up information about a chunk
98-
but the chunk doesn't exist within the Opsqueue.
99-
"""
100-
101-
pass
102-
103-
10495
class SubmissionNotFoundError(IncorrectUsageError):
10596
"""
10697
Raised when a method is used to look up information about a submission

‎libs/opsqueue_python/src/errors.rs‎

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,22 @@
22
/// so we have nice IDE support for docs-on-hover and for 'go to definition'.
33
use std::error::Error;
44

5-
use opsqueue::common::chunk::ChunkId;
5+
66
use opsqueue::common::errors::{
7-
ChunkNotFound, IncorrectUsage, SubmissionNotCancellable, SubmissionNotFound,
7+
IncorrectUsage, SubmissionNotCancellable, SubmissionNotFound,
88
UnexpectedOpsqueueConsumerServerResponse, E,
99
};
1010
use pyo3::exceptions::PyBaseException;
1111
use pyo3::{import_exception, Bound, PyErr, Python};
1212

1313
use crate::common;
14-
use crate::common::{ChunkIndex, SubmissionId};
1514

1615
// Expected errors:
1716
import_exception!(opsqueue.exceptions, SubmissionFailedError);
1817

1918
// Incorrect usage errors:
2019
import_exception!(opsqueue.exceptions, IncorrectUsageError);
2120
import_exception!(opsqueue.exceptions, TryFromIntError);
22-
import_exception!(opsqueue.exceptions, ChunkNotFoundError);
2321
import_exception!(opsqueue.exceptions, SubmissionNotFoundError);
2422
import_exception!(opsqueue.exceptions, SubmissionNotCancellableError);
2523
import_exception!(opsqueue.exceptions, NewObjectStoreClientError);
@@ -166,22 +164,6 @@ impl From<CError<crate::producer::SubmissionNotCompletedYetError>> for PyErr {
166164
}
167165
}
168166

169-
impl From<CError<ChunkNotFound>> for PyErr {
170-
fn from(value: CError<ChunkNotFound>) -> Self {
171-
let ChunkId {
172-
submission_id,
173-
chunk_index,
174-
} = value.0 .0;
175-
ChunkNotFoundError::new_err((
176-
value.0.to_string(),
177-
(
178-
SubmissionId::from(submission_id),
179-
ChunkIndex::from(chunk_index),
180-
),
181-
))
182-
}
183-
}
184-
185167
impl From<CError<opsqueue::object_store::NewObjectStoreClientError>> for PyErr {
186168
fn from(value: CError<opsqueue::object_store::NewObjectStoreClientError>) -> Self {
187169
NewObjectStoreClientError::new_err(value.0.to_string())

‎opsqueue/src/common/chunk.rs‎

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ impl Chunk {
218218
#[cfg(feature = "server-logic")]
219219
pub mod db {
220220
use super::*;
221-
use crate::common::errors::{ChunkNotFound, DatabaseError, SubmissionNotFound, E};
221+
use crate::common::errors::{DatabaseError, SubmissionNotFound, E};
222222
use crate::db::{Connection, True, WriterConnection};
223223
use axum_prometheus::metrics::{counter, gauge};
224224
use sqlx::{query, query_as};
@@ -281,25 +281,18 @@ pub mod db {
281281
chunk_id: ChunkId,
282282
output_content: Option<Vec<u8>>,
283283
mut conn: impl WriterConnection,
284-
) -> Result<(), E<DatabaseError, E<SubmissionNotFound, ChunkNotFound>>> {
285-
let _chunk_size: Result<ChunkSize, E<DatabaseError, E<SubmissionNotFound, ChunkNotFound>>> =
286-
conn.transaction(move |mut tx| {
287-
Box::pin(async move {
288-
let completed_work =
289-
complete_chunk_raw(chunk_id, output_content, &mut tx).await?;
290-
crate::common::submission::db::maybe_complete_submission(
291-
chunk_id.submission_id,
292-
&mut tx,
293-
)
294-
.await
295-
.map_err(|e| match e {
296-
E::L(e) => E::L(e),
297-
E::R(e) => E::R(E::L(e)),
298-
})?;
299-
Ok(completed_work.unwrap_or_default())
300-
})
284+
) -> Result<(), E<DatabaseError, SubmissionNotFound>> {
285+
conn.transaction(move |mut tx| {
286+
Box::pin(async move {
287+
complete_chunk_raw(chunk_id, output_content, &mut tx).await?;
288+
crate::common::submission::db::maybe_complete_submission(
289+
chunk_id.submission_id,
290+
&mut tx,
291+
)
292+
.await
301293
})
302-
.await;
294+
})
295+
.await?;
303296

304297
counter!(crate::prometheus::CHUNKS_COMPLETED_COUNTER).increment(1);
305298
Ok(())
@@ -311,17 +304,16 @@ pub mod db {
311304
chunk_id: ChunkId,
312305
output_content: Option<Vec<u8>>,
313306
mut tx: impl WriterConnection<Transaction = True>,
314-
) -> sqlx::Result<Option<ChunkSize>> {
307+
) -> sqlx::Result<()> {
315308
let now = chrono::prelude::Utc::now();
316-
query!(
309+
let rows_affected = query!(
317310
"
318311
INSERT INTO chunks_completed
319312
(submission_id, chunk_index, output_content, completed_at)
320313
SELECT submission_id, chunk_index, $1, julianday($2) FROM chunks
321314
WHERE chunks.submission_id = $3 AND chunks.chunk_index = $4;
322315
323-
DELETE FROM chunks WHERE chunks.submission_id = $5 AND chunks.chunk_index = $6
324-
RETURNING submission_id, chunk_index;
316+
DELETE FROM chunks WHERE chunks.submission_id = $5 AND chunks.chunk_index = $6;
325317
",
326318
output_content,
327319
now,
@@ -330,26 +322,40 @@ pub mod db {
330322
chunk_id.submission_id,
331323
chunk_id.chunk_index,
332324
)
333-
.fetch_one(tx.get_inner())
334-
.await?;
335-
// Defense in depth: Above query should never be called twice on the same chunk.
336-
// If it _does_ happen, it means that either a consumer is attempting a chunk they didn't reserve,
337-
// or we gave out the same reservation twice.
325+
.execute(tx.get_inner())
326+
.await?
327+
.rows_affected() as i64;
328+
// Defense in depth: Above query could be called twice on the same chunk. For instance,
329+
// when the server was restarted and the reservations are forgotten, and the same chunk
330+
// was reserved again.
331+
//
332+
// By using the rows affected to update `chunks_done` we ensure that we never mess up the
333+
// submission's `chunks_done` counter.
338334
//
339-
// By returning early if the chunk was not found,
340-
// we ensure that even in these situations
341-
// we never mess up the submission's `chunks_done` counter.
335+
// In addition, cancelling or pausing a submission while a chunk is reserved also results
336+
// in `rows_affected` being 0. Which is fine, because cancelled submissions count as failed,
337+
// and for paused submissions we will retry the chunk when it becomes unpaused.
342338
//
343339
// (Not doing that resulted in a hard-to-track-down bug in the past.
344340
// https://github.com/channable/opsqueue/issues/76
345341
// )
342+
343+
if rows_affected == 0 {
344+
tracing::warn!(
345+
"Could not complete chunk because it was either: completed before,\
346+
or cancelled, or paused. Ignoring."
347+
);
348+
}
349+
346350
sqlx::query_scalar!(
347-
"UPDATE submissions SET chunks_done = chunks_done + 1 WHERE submissions.id = $1 RETURNING submissions.chunk_size;",
351+
"UPDATE submissions SET chunks_done = chunks_done + $1 WHERE submissions.id = $2;",
352+
rows_affected,
348353
chunk_id.submission_id,
349354
)
350-
.fetch_one(tx.get_inner())
351-
.await
352-
.map(|opt| opt.map(ChunkSize))
355+
.execute(tx.get_inner())
356+
.await?;
357+
358+
Ok(())
353359
}
354360

355361
#[tracing::instrument(skip(conn))]

‎opsqueue/src/common/errors.rs‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use thiserror::Error;
1212
use crate::consumer::common::SyncServerToClientResponse;
1313

1414
use super::{
15-
chunk::{ChunkFailed, ChunkId},
15+
chunk::ChunkFailed,
1616
submission::{SubmissionCancelled, SubmissionCompleted, SubmissionFailed, SubmissionId},
1717
};
1818

@@ -31,10 +31,6 @@ impl<T> From<DatabaseError> for E<DatabaseError, T> {
3131
}
3232
}
3333

34-
#[derive(Error, Debug)]
35-
#[error("Chunk not found for ID {0:?}")]
36-
pub struct ChunkNotFound(pub ChunkId);
37-
3834
#[derive(Error, Debug, Deserialize, Serialize)]
3935
#[error("Submission not found for ID {0:?}")]
4036
pub struct SubmissionNotFound(pub SubmissionId);

0 commit comments

Comments
 (0)