1use std::{
2 cmp, fmt, io,
3 num::NonZeroU32,
4 num::ParseIntError,
5 ops::{Deref, RangeInclusive},
6 time::Duration,
7};
8
9use crate::api::{ColumnName, SqliteValue};
10use antithesis_sdk::assert_sometimes;
11use bytes::{Bytes, BytesMut};
12use foca::{Identity, Member, Notification, OwnedNotification, Runtime, Timer};
13use itertools::Itertools;
14use metrics::counter;
15use rusqlite::{
16 ToSql,
17 types::{FromSql, FromSqlError},
18};
19use serde::{Deserialize, Serialize};
20use speedy::{Context, Readable, Reader, Writable, Writer};
21use time::OffsetDateTime;
22use tokio::{
23 sync::{mpsc, oneshot},
24 task::block_in_place,
25};
26use tracing::{debug, error, trace};
27use uhlc::NTP64;
28
29use crate::{
30 actor::{Actor, ActorId, ClusterId},
31 agent::Agent,
32 base::{CrsqlDbVersion, CrsqlSeq},
33 change::{Change, ChunkedChanges, MAX_CHANGES_BYTE_SIZE, row_to_change},
34 channel::CorroSender,
35 sqlite::SqlitePoolError,
36 sync::SyncTraceContextV1,
37 updates::match_changes,
38};
39
40#[derive(Debug, Clone, Readable, Writable)]
41pub enum UniPayload {
42 V1 {
43 data: UniPayloadV1,
44 #[speedy(default_on_eof)]
45 cluster_id: ClusterId,
46 },
47}
48
49#[derive(Debug, Clone, Readable, Writable)]
50pub enum UniPayloadV1 {
51 Broadcast(BroadcastV1),
52}
53
54#[derive(Debug, Clone, Readable, Writable)]
55pub enum BiPayload {
56 V1 {
57 data: BiPayloadV1,
58 #[speedy(default_on_eof)]
59 cluster_id: ClusterId,
60 },
61}
62
63#[derive(Debug, Clone, Readable, Writable)]
64pub enum BiPayloadV1 {
65 SyncStart {
66 actor_id: ActorId,
67 #[speedy(default_on_eof)]
68 trace_ctx: SyncTraceContextV1,
69 },
70}
71
72#[derive(Debug)]
73pub enum FocaInput {
74 Announce(Actor),
75 Data(Bytes),
76 ClusterSize(NonZeroU32),
77 ApplyMany(Vec<Member<Actor>>),
78 Cmd(FocaCmd),
79}
80
81#[derive(Debug)]
82pub enum FocaCmd {
83 Rejoin(oneshot::Sender<Result<(), foca::Error>>),
84 MembershipStates(mpsc::Sender<foca::Member<Actor>>),
85 ChangeIdentity(Actor, oneshot::Sender<Result<(), foca::Error>>),
86}
87
88#[derive(Debug, Clone, Readable, Writable)]
89pub enum AuthzV1 {
90 Token(String),
91}
92
93#[derive(Clone, Debug, Readable, Writable)]
94pub enum BroadcastV1 {
95 Change(ChangeV1),
96}
97
98#[derive(Debug, Clone, PartialEq, Readable, Writable)]
99pub struct ColumnChange {
100 pub cid: ColumnName,
101 pub val: SqliteValue,
102 pub col_version: i64,
103 pub seq: CrsqlSeq,
104 pub cl: i64,
105}
106
107#[derive(Debug, Clone, Copy, strum::IntoStaticStr)]
108#[strum(serialize_all = "snake_case")]
109pub enum ChangeSource {
110 Broadcast,
111 Sync,
112}
113
114#[derive(Debug, Clone, PartialEq, Readable, Writable)]
115pub struct ChangeV1 {
116 pub actor_id: ActorId,
117 pub changeset: Changeset,
118}
119
120impl Deref for ChangeV1 {
121 type Target = Changeset;
122
123 fn deref(&self) -> &Self::Target {
124 &self.changeset
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Readable, Writable)]
129pub enum Changeset {
130 Empty {
131 versions: RangeInclusive<CrsqlDbVersion>,
132 #[speedy(default_on_eof)]
133 ts: Option<Timestamp>,
134 },
135 Full {
136 version: CrsqlDbVersion,
137 changes: Vec<Change>,
138 seqs: RangeInclusive<CrsqlSeq>,
140 last_seq: CrsqlSeq,
142 ts: Timestamp,
143 },
144 EmptySet {
145 versions: Vec<RangeInclusive<CrsqlDbVersion>>,
146 ts: Timestamp,
147 },
148}
149
150impl From<ChangesetParts> for Changeset {
151 fn from(value: ChangesetParts) -> Self {
152 Changeset::Full {
153 version: value.version,
154 changes: value.changes,
155 seqs: value.seqs,
156 last_seq: value.last_seq,
157 ts: value.ts,
158 }
159 }
160}
161
162pub struct ChangesetParts {
163 pub version: CrsqlDbVersion,
164 pub changes: Vec<Change>,
165 pub seqs: RangeInclusive<CrsqlSeq>,
166 pub last_seq: CrsqlSeq,
167 pub ts: Timestamp,
168}
169
170impl Changeset {
171 pub fn versions(&self) -> RangeInclusive<CrsqlDbVersion> {
172 match self {
173 Changeset::Empty { versions, .. } => versions.clone(),
174 Changeset::EmptySet { .. } => CrsqlDbVersion(0)..=CrsqlDbVersion(0),
177 Changeset::Full { version, .. } => *version..=*version,
178 }
179 }
180
181 pub fn processing_cost(&self) -> usize {
183 match self {
184 Changeset::Empty { versions, .. } => {
185 cmp::min((versions.end().0 - versions.start().0) as usize + 1, 20)
186 }
187 Changeset::EmptySet { versions, .. } => versions
188 .iter()
189 .map(|versions| cmp::min((versions.end().0 - versions.start().0) as usize + 1, 20))
190 .sum::<usize>(),
191 Changeset::Full { changes, .. } => changes.len(),
192 }
193 }
194
195 pub fn max_db_version(&self) -> Option<CrsqlDbVersion> {
196 self.changes().iter().map(|c| c.db_version).max()
197 }
198
199 pub fn seqs(&self) -> Option<&RangeInclusive<CrsqlSeq>> {
200 match self {
201 Changeset::Empty { .. } => None,
202 Changeset::EmptySet { .. } => None,
203 Changeset::Full { seqs, .. } => Some(seqs),
204 }
205 }
206
207 pub fn last_seq(&self) -> Option<CrsqlSeq> {
208 match self {
209 Changeset::Empty { .. } => None,
210 Changeset::EmptySet { .. } => None,
211 Changeset::Full { last_seq, .. } => Some(*last_seq),
212 }
213 }
214
215 pub fn is_complete(&self) -> bool {
216 match self {
217 Changeset::Empty { .. } => true,
218 Changeset::EmptySet { .. } => true,
219 Changeset::Full { seqs, last_seq, .. } => {
220 *seqs.start() == CrsqlSeq(0) && seqs.end() == last_seq
221 }
222 }
223 }
224
225 pub fn len(&self) -> usize {
226 match self {
227 Changeset::Empty { .. } => 0, Changeset::EmptySet { versions, .. } => versions.len(),
229 Changeset::Full { changes, .. } => changes.len(),
230 }
231 }
232
233 pub fn is_empty(&self) -> bool {
234 match self {
235 Changeset::Empty { .. } => true,
236 Changeset::EmptySet { .. } => true,
237 Changeset::Full { changes, .. } => changes.is_empty(),
238 }
239 }
240
241 pub fn is_empty_set(&self) -> bool {
242 match self {
243 Changeset::Empty { .. } => false,
244 Changeset::EmptySet { .. } => true,
245 Changeset::Full { .. } => false,
246 }
247 }
248
249 pub fn ts(&self) -> Option<Timestamp> {
250 match self {
251 Changeset::Empty { ts, .. } => *ts,
252 Changeset::EmptySet { ts, .. } => Some(*ts),
253 Changeset::Full { ts, .. } => Some(*ts),
254 }
255 }
256
257 pub fn changes(&self) -> &[Change] {
258 match self {
259 Changeset::Empty { .. } => &[],
260 Changeset::EmptySet { .. } => &[],
261 Changeset::Full { changes, .. } => changes,
262 }
263 }
264
265 pub fn into_parts(self) -> Option<ChangesetParts> {
266 match self {
267 Changeset::Empty { .. } => None,
268 Changeset::EmptySet { .. } => None,
269 Changeset::Full {
270 version,
271 changes,
272 seqs,
273 last_seq,
274 ts,
275 } => Some(ChangesetParts {
276 version,
277 changes,
278 seqs,
279 last_seq,
280 ts,
281 }),
282 }
283 }
284}
285
286#[derive(Debug, thiserror::Error)]
287pub enum TimestampParseError {
288 #[error("could not parse timestamp: {0:?}")]
289 Parse(ParseIntError),
290}
291
292#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialOrd, Ord)]
293#[serde(transparent)]
294pub struct Timestamp(pub NTP64);
295
296impl Timestamp {
297 pub fn to_time(&self) -> OffsetDateTime {
298 OffsetDateTime::from_unix_timestamp(self.0.as_secs() as i64).unwrap()
299 + time::Duration::nanoseconds(self.0.subsec_nanos() as i64)
300 }
301
302 pub fn to_ntp64(&self) -> NTP64 {
303 self.0
304 }
305
306 pub fn zero() -> Self {
307 Timestamp::from(0u64)
308 }
309
310 pub fn is_zero(&self) -> bool {
311 self.0.0 == 0
312 }
313}
314
315impl PartialEq for Timestamp {
317 fn eq(&self, other: &Self) -> bool {
318 self.0.as_secs() == other.0.as_secs() && self.0.subsec_nanos() == other.0.subsec_nanos()
319 }
320}
321
322impl std::hash::Hash for Timestamp {
323 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
324 self.0.as_secs().hash(state);
325 self.0.subsec_nanos().hash(state);
326 }
327}
328
329impl Deref for Timestamp {
330 type Target = NTP64;
331
332 fn deref(&self) -> &Self::Target {
333 &self.0
334 }
335}
336
337impl fmt::Display for Timestamp {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 self.0.fmt(f)
340 }
341}
342
343impl From<uhlc::Timestamp> for Timestamp {
344 fn from(ts: uhlc::Timestamp) -> Self {
345 Self(*ts.get_time())
346 }
347}
348
349impl From<NTP64> for Timestamp {
350 fn from(ntp64: NTP64) -> Self {
351 Self(ntp64)
352 }
353}
354
355impl From<u64> for Timestamp {
356 fn from(value: u64) -> Self {
357 Self(NTP64(value))
358 }
359}
360
361impl FromSql for Timestamp {
362 fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
363 match value {
364 rusqlite::types::ValueRef::Text(b) => match std::str::from_utf8(b) {
365 Ok(s) => match s.parse::<u64>() {
366 Ok(ntp) => Ok(Timestamp(NTP64(ntp))),
367 Err(e) => Err(FromSqlError::Other(Box::new(TimestampParseError::Parse(e)))),
368 },
369 Err(e) => Err(FromSqlError::Other(Box::new(e))),
370 },
371 _ => Err(FromSqlError::InvalidType),
372 }
373 }
374}
375
376impl ToSql for Timestamp {
377 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
378 Ok(rusqlite::types::ToSqlOutput::Owned(
379 rusqlite::types::Value::Text(self.0.as_u64().to_string()),
380 ))
381 }
382}
383
384impl<'a, C> Readable<'a, C> for Timestamp
385where
386 C: Context,
387{
388 #[inline]
389 fn read_from<R: Reader<'a, C>>(reader: &mut R) -> Result<Self, C::Error> {
390 Ok(Timestamp(NTP64(u64::read_from(reader)?)))
391 }
392
393 #[inline]
394 fn minimum_bytes_needed() -> usize {
395 std::mem::size_of::<u64>()
396 }
397}
398
399impl<C> Writable<C> for Timestamp
400where
401 C: Context,
402{
403 #[inline]
404 fn write_to<T: ?Sized + Writer<C>>(&self, writer: &mut T) -> Result<(), C::Error> {
405 self.0.0.write_to(writer)
406 }
407
408 #[inline]
409 fn bytes_needed(&self) -> Result<usize, C::Error> {
410 <u64 as speedy::Writable<C>>::bytes_needed(&self.0.0)
411 }
412}
413
414#[derive(Debug, thiserror::Error)]
415pub enum BroadcastEncodeError {
416 #[error(transparent)]
417 Encode(#[from] speedy::Error),
418 #[error(transparent)]
419 Io(#[from] io::Error),
420}
421
422#[derive(Debug, thiserror::Error)]
423pub enum BroadcastDecodeError {
424 #[error(transparent)]
425 Decode(#[from] speedy::Error),
426 #[error("corrupted message, crc mismatch (got: {0}, expected {1})")]
427 Corrupted(u32, u32),
428 #[error(transparent)]
429 Io(#[from] io::Error),
430 #[error("insufficient length received to decode message: {0}")]
431 InsufficientLength(usize),
432}
433
434#[derive(Debug)]
435pub enum BroadcastInput {
436 Rebroadcast(BroadcastV1),
437 AddBroadcast(BroadcastV1),
438}
439
440pub struct DispatchRuntime<T> {
441 pub to_send: CorroSender<(T, Bytes)>,
442 pub to_schedule: CorroSender<(Duration, Timer<T>)>,
443 pub notifications: CorroSender<OwnedNotification<T>>,
444 pub active: bool,
445 pub buf: BytesMut,
446}
447
448impl<T: Identity> Runtime<T> for DispatchRuntime<T> {
449 fn notify(&mut self, notification: Notification<'_, T>) {
450 match ¬ification {
451 Notification::Active => {
452 self.active = true;
453 }
454 Notification::Idle | Notification::Defunct => {
455 self.active = false;
456 }
457 _ => {}
458 };
459
460 let owned_notification = notification.to_owned();
462
463 if let Err(e) = self.notifications.try_send(owned_notification) {
464 counter!("corro.channel.error", "type" => "full", "name" => "dispatch.notifications")
465 .increment(1);
466 error!("error dispatching notification: {e}");
467 }
468 }
469
470 fn send_to(&mut self, to: T, data: &[u8]) {
471 trace!("cluster send_to {to:?}");
472 self.buf.extend_from_slice(data);
473
474 if let Err(e) = self.to_send.try_send((to, self.buf.split().freeze())) {
475 counter!("corro.channel.error", "type" => "full", "name" => "dispatch.to_send")
476 .increment(1);
477 error!("error dispatching broadcast packet: {e}");
478 }
479 }
480
481 fn submit_after(&mut self, event: Timer<T>, after: Duration) {
482 if let Err(e) = self.to_schedule.try_send((after, event)) {
483 counter!("corro.channel.error", "type" => "full", "name" => "dispatch.to_schedule")
484 .increment(1);
485 error!("error dispatching scheduled event: {e}");
486 }
487 }
488}
489
490impl<T> DispatchRuntime<T> {
491 pub fn new(
492 to_send: CorroSender<(T, Bytes)>,
493 to_schedule: CorroSender<(Duration, Timer<T>)>,
494 notifications: CorroSender<OwnedNotification<T>>,
495 ) -> Self {
496 Self {
497 to_send,
498 to_schedule,
499 notifications,
500 active: false,
501 buf: BytesMut::new(),
502 }
503 }
504}
505
506#[derive(Debug, thiserror::Error)]
507pub enum BroadcastError {
508 #[error(transparent)]
509 Pool(#[from] SqlitePoolError),
510 #[error(transparent)]
511 Sqlite(#[from] rusqlite::Error),
512}
513
514pub async fn broadcast_changes(
515 agent: Agent,
516 db_version: CrsqlDbVersion,
517 last_seq: CrsqlSeq,
518 ts: Timestamp,
519) -> Result<(), BroadcastError> {
520 let actor_id = agent.actor_id();
521 let conn = agent.pool().read().await?;
522 trace!("got conn for broadcast");
523
524 block_in_place(|| {
525 let mut prepped = conn.prepare_cached(
527 r#"
528 SELECT "table", pk, cid, val, col_version, db_version, seq, site_id, cl
529 FROM crsql_changes
530 WHERE db_version = ?
531 AND site_id = crsql_site_id()
532 ORDER BY seq ASC
533 "#,
534 )?;
535 let rows = prepped.query_map([db_version], row_to_change)?;
536 let chunked = ChunkedChanges::new(rows, CrsqlSeq(0), last_seq, MAX_CHANGES_BYTE_SIZE);
537 for changes_seqs in chunked {
538 match changes_seqs {
539 Ok((changes, seqs)) => {
540 for (table_name, count) in changes.iter().counts_by(|change| &change.table) {
541 counter!("corro.changes.committed", "table" => table_name.to_string(), "source" => "local").increment(count as u64);
542 }
543
544 trace!("broadcasting changes: {changes:?} for seq: {seqs:?}");
545
546 debug!("match_changes db_version: {db_version}");
547 match_changes(agent.subs_manager(), &changes, db_version);
548 match_changes(agent.updates_manager(), &changes, db_version);
549
550 let tx_bcast = agent.tx_bcast().clone();
551 assert_sometimes!(true, "Corrosion broadcasts changes");
552 tokio::spawn(async move {
553 if let Err(e) = tx_bcast
554 .send(BroadcastInput::AddBroadcast(BroadcastV1::Change(
555 ChangeV1 {
556 actor_id,
557 changeset: Changeset::Full {
558 version: db_version,
559 changes,
560 seqs,
561 last_seq,
562 ts,
563 },
564 },
565 )))
566 .await
567 {
568 error!("could not send change message for broadcast: {e}");
569 }
570 });
571 }
572 Err(e) => {
573 error!(
574 "could not process crsql change (db_version: {db_version}) for broadcast: {e}"
575 );
576 break;
577 }
578 }
579 }
580 Ok::<_, rusqlite::Error>(())
581 })?;
582
583 Ok(())
584}