Skip to content

Commit 95b12e5

Browse files
Merge pull request #83 from balena-io-modules/create-rollback-bug
Fix task error rollback
2 parents 640f058 + 01366ac commit 95b12e5

3 files changed

Lines changed: 408 additions & 116 deletions

File tree

mahler-core/src/runtime/channel.rs

Lines changed: 26 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,30 @@
1-
use std::ops::Deref;
2-
use std::sync::Arc;
3-
4-
use tokio::sync::Mutex;
5-
61
use crate::error::Error;
72
use crate::json::{Patch, Value};
83
use crate::result::Result;
94
use crate::sync::Sender;
105

11-
#[derive(Clone)]
12-
struct Checkpoint<T>(Arc<Mutex<Option<T>>>);
13-
14-
impl<T> Default for Checkpoint<T> {
15-
fn default() -> Self {
16-
Self(Arc::new(Mutex::new(None)))
17-
}
18-
}
19-
20-
impl<T> Deref for Checkpoint<T> {
21-
type Target = Mutex<Option<T>>;
22-
23-
fn deref(&self) -> &Self::Target {
24-
self.0.as_ref()
25-
}
26-
}
27-
286
/// A channel to communicate state changes at runtime
297
///
308
/// The `Channel` allows tasks to send state changes and rollback checkpoints back to the worker during execution,
319
/// enabling real-time progress updates. This can be used by extractors to propagate changes
3210
/// during a long operation.
3311
#[derive(Clone)]
3412
pub struct Channel {
35-
sender: Option<Sender<Patch>>,
36-
checkpoint: Checkpoint<Value>,
13+
sender: Option<Sender<(Patch, Option<Value>)>>,
3714
}
3815

3916
impl std::fmt::Debug for Channel {
4017
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41-
let mut dbg = f.debug_struct("Channel");
42-
dbg.field(
43-
"sender",
44-
if self.sender.is_some() {
45-
&"attached"
46-
} else {
47-
&"detached"
48-
},
49-
);
50-
match self.checkpoint.try_lock() {
51-
Ok(guard) => dbg.field("checkpoint", &*guard),
52-
Err(_) => dbg.field("checkpoint", &"locked"),
53-
};
54-
dbg.finish()
18+
f.debug_struct("Channel")
19+
.field(
20+
"sender",
21+
if self.sender.is_some() {
22+
&"attached"
23+
} else {
24+
&"detached"
25+
},
26+
)
27+
.finish()
5528
}
5629
}
5730

@@ -63,51 +36,40 @@ impl Channel {
6336

6437
/// Create a detached channel. A detached channel is not connected to a worker
6538
pub fn detached() -> Self {
66-
Self {
67-
sender: None,
68-
checkpoint: Checkpoint::default(),
69-
}
39+
Self { sender: None }
7040
}
7141

7242
/// Communicate the changes to the global state
7343
pub async fn send(&self, changes: Patch) -> Result<()> {
7444
if let Some(sender) = self.sender.as_ref() {
75-
sender.send(changes).await.map_err(Error::internal)?;
45+
sender
46+
.send((changes, None))
47+
.await
48+
.map_err(Error::internal)?;
7649
}
7750
Ok(())
7851
}
7952

8053
/// Communicate changes and update the checkpoint
8154
///
82-
/// Like [`send`](Channel::send), but also records the given value as the
83-
/// current checkpoint. The checkpoint can be read by the caller via
84-
/// [`checkpoint`](Channel::checkpoint) after the task completes.
85-
pub async fn send_and_commit(&self, changes: Patch, new_checkpoint: Value) -> Result<()> {
55+
/// Like [`send`](Channel::send), but also sends the given value as the
56+
/// current checkpoint alongside the patch.
57+
pub async fn send_and_commit(&self, changes: Patch, checkpoint: Value) -> Result<()> {
8658
if let Some(sender) = self.sender.as_ref() {
87-
sender.send(changes).await.map_err(Error::internal)?;
88-
*self.checkpoint.lock().await = Some(new_checkpoint);
59+
sender
60+
.send((changes, Some(checkpoint)))
61+
.await
62+
.map_err(Error::internal)?;
8963
}
9064
Ok(())
9165
}
92-
93-
/// Return the last checkpointed value, if any.
94-
///
95-
/// Returns `Some(value)` if [`send_and_commit`](Channel::send_and_commit) was called
96-
/// at least once, `None` otherwise.
97-
pub async fn checkpoint(&self) -> Option<Value> {
98-
self.checkpoint.lock().await.clone()
99-
}
10066
}
10167

102-
impl From<Sender<Patch>> for Channel {
103-
/// Create a channel with a checkpoint for tracking committed state.
104-
///
105-
/// The checkpoint allows [`send_and_commit`](Channel::send_and_commit) to record
106-
/// state snapshots, retrievable later via [`checkpoint`](Channel::checkpoint).
107-
fn from(sender: Sender<Patch>) -> Self {
68+
impl From<Sender<(Patch, Option<Value>)>> for Channel {
69+
/// Create an attached channel from a sender.
70+
fn from(sender: Sender<(Patch, Option<Value>)>) -> Self {
10871
Self {
10972
sender: Some(sender),
110-
checkpoint: Checkpoint::default(),
11173
}
11274
}
11375
}

mahler/src/extract/view.rs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -816,14 +816,14 @@ mod tests {
816816
})
817817
.unwrap();
818818

819-
let (tx, mut rx) = sync::channel::<Patch>(1);
819+
let (tx, mut rx) = sync::channel::<(Patch, Option<Value>)>(1);
820820
let channel = Channel::from(tx);
821821

822822
let received = Arc::new(tokio::sync::Mutex::new(Vec::new()));
823823
let received_clone = received.clone();
824824
let ack_task = tokio::spawn(async move {
825825
while let Some(msg) = rx.recv().await {
826-
received_clone.lock().await.push(msg.data.clone());
826+
received_clone.lock().await.push(msg.data.0.clone());
827827
msg.ack();
828828
}
829829
});
@@ -875,14 +875,14 @@ mod tests {
875875
})
876876
.unwrap();
877877

878-
let (tx, mut rx) = sync::channel::<Patch>(10);
878+
let (tx, mut rx) = sync::channel::<(Patch, Option<Value>)>(10);
879879
let channel = Channel::from(tx);
880880

881881
let received = Arc::new(tokio::sync::Mutex::new(Vec::new()));
882882
let received_clone = received.clone();
883883
let ack_task = tokio::spawn(async move {
884884
while let Some(msg) = rx.recv().await {
885-
received_clone.lock().await.push(msg.data.clone());
885+
received_clone.lock().await.push(msg.data.0.clone());
886886
msg.ack();
887887
}
888888
});
@@ -930,15 +930,15 @@ mod tests {
930930
};
931931
let system = System::try_from(state).unwrap();
932932

933-
let (tx, mut rx) = sync::channel::<Patch>(1);
933+
let (tx, mut rx) = sync::channel::<(Patch, Option<Value>)>(1);
934934
let channel = Channel::from(tx);
935935

936936
// Spawn a task to receive and acknowledge messages
937937
let received_patches = Arc::new(tokio::sync::Mutex::new(Vec::new()));
938938
let received_patches_clone = received_patches.clone();
939939
let ack_task = tokio::spawn(async move {
940940
while let Some(msg) = rx.recv().await {
941-
received_patches_clone.lock().await.push(msg.data.clone());
941+
received_patches_clone.lock().await.push(msg.data.0.clone());
942942
msg.ack();
943943
}
944944
});
@@ -987,15 +987,15 @@ mod tests {
987987
let state = MyState { numbers };
988988
let system = System::try_from(state).unwrap();
989989

990-
let (tx, mut rx) = sync::channel::<Patch>(1);
990+
let (tx, mut rx) = sync::channel::<(Patch, Option<Value>)>(1);
991991
let channel = Channel::from(tx);
992992

993993
// Spawn a task to receive and acknowledge messages
994994
let received_patches = Arc::new(tokio::sync::Mutex::new(Vec::new()));
995995
let received_patches_clone = received_patches.clone();
996996
let ack_task = tokio::spawn(async move {
997997
while let Some(msg) = rx.recv().await {
998-
received_patches_clone.lock().await.push(msg.data.clone());
998+
received_patches_clone.lock().await.push(msg.data.0.clone());
999999
msg.ack();
10001000
}
10011001
});
@@ -1080,7 +1080,7 @@ mod tests {
10801080
})
10811081
.unwrap();
10821082

1083-
let (tx, mut rx) = sync::channel::<Patch>(1);
1083+
let (tx, mut rx) = sync::channel::<(Patch, Option<Value>)>(1);
10841084
let channel = Channel::from(tx);
10851085

10861086
let received = Arc::new(tokio::sync::Mutex::new(Vec::new()));
@@ -1101,18 +1101,19 @@ mod tests {
11011101

11021102
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
11031103

1104-
let patches = received.lock().await;
1105-
assert_eq!(patches.len(), 1);
1104+
let messages = received.lock().await;
1105+
assert_eq!(messages.len(), 1);
1106+
let (patch, checkpoint) = &messages[0];
11061107
assert_eq!(
1107-
patches[0],
1108+
*patch,
11081109
serde_json::from_value::<Patch>(json!([
11091110
{ "op": "add", "path": "/numbers/0", "value": 10 },
11101111
]))
11111112
.unwrap()
11121113
);
1113-
drop(patches);
1114-
1115-
assert_eq!(channel.checkpoint().await, Some(json!([10])));
1114+
// commit() sends the new state value as the checkpoint
1115+
assert_eq!(*checkpoint, Some(json!([10])));
1116+
drop(messages);
11161117

11171118
drop(channel);
11181119
ack_task.abort();
@@ -1138,7 +1139,7 @@ mod tests {
11381139
};
11391140
let system = System::try_from(state).unwrap();
11401141

1141-
let (tx, mut rx) = sync::channel::<Patch>(1);
1142+
let (tx, mut rx) = sync::channel::<(Patch, Option<Value>)>(1);
11421143
let channel = Channel::from(tx);
11431144

11441145
let received = Arc::new(tokio::sync::Mutex::new(Vec::new()));
@@ -1159,18 +1160,19 @@ mod tests {
11591160

11601161
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
11611162

1162-
let patches = received.lock().await;
1163-
assert_eq!(patches.len(), 1);
1163+
let messages = received.lock().await;
1164+
assert_eq!(messages.len(), 1);
1165+
let (patch, checkpoint) = &messages[0];
11641166
assert_eq!(
1165-
patches[0],
1167+
*patch,
11661168
serde_json::from_value::<Patch>(json!([
11671169
{ "op": "add", "path": "/numbers/new", "value": 42 },
11681170
]))
11691171
.unwrap()
11701172
);
1171-
drop(patches);
1172-
1173-
assert_eq!(channel.checkpoint().await, Some(json!(42)));
1173+
// commit() sends the committed value as the checkpoint
1174+
assert_eq!(*checkpoint, Some(json!(42)));
1175+
drop(messages);
11741176

11751177
drop(channel);
11761178
ack_task.abort();

0 commit comments

Comments
 (0)