use crate::errors::PagedbError;
use crate::pager::anchor::HeaderCursor;
use crate::pager::header::commit_header;
use crate::pager::structural_header::MainDbHeaderFields;
use crate::vfs::Vfs;
use crate::{CommitId, Result};
use super::core::{Db, WriterState, encode_free_list_root, encode_root_ref};
use super::segment::SegmentReconciliation;
use super::util::page_size_log2;
impl<V: Vfs + Clone> Db<V> {
pub(crate) async fn retry_pending_apply_journal(&self) -> Result<()> {
let mut state = self.writer.lock().await;
let visibility = self.visibility_gate.write().await;
self.retry_pending_apply_journal_visible(&mut state, &visibility)
.await
}
pub(crate) async fn retry_pending_apply_journal_visible(
&self,
state: &mut WriterState,
_visibility: &tokio::sync::RwLockWriteGuard<'_, ()>,
) -> Result<()> {
let journal_id = state.pending_apply_journal_id;
if journal_id == [0; 16] {
return Ok(());
}
let Ok(Some(record)) =
crate::recovery::journal::replay_apply_journal(&self.pager, self.realm_id, journal_id)
.await
else {
let commit = CommitId(state.latest_commit_id);
self.poison(commit);
return Err(PagedbError::durably_committed_but_unpublished(commit));
};
let Ok(reconciliation) = self.reconcile_journal_actions(&record.actions).await else {
let commit = CommitId(state.latest_commit_id);
self.poison(commit);
return Err(PagedbError::durably_committed_but_unpublished(commit));
};
match reconciliation {
SegmentReconciliation::Deferred => {
self.publish_snapshot(state);
return Err(PagedbError::ReadersPinningTruncatedRange);
}
SegmentReconciliation::Complete => {}
}
#[cfg(test)]
self.pause_in_apply_publication_window().await;
let header_cursor = self.pager.header_cursor()?;
let next_seq = header_cursor.next_seq()?;
let counter_anchor = self.pager.pending_anchor();
let fields = cleared_header_fields(self, state, next_seq, counter_anchor)?;
let hk = self.hk.read().clone();
let Ok(next_slot) = commit_header(
&*self.vfs,
&self.main_db_path,
&hk,
&fields,
header_cursor.slot,
self.page_size,
)
.await
else {
let commit = CommitId(state.latest_commit_id);
self.poison(commit);
return Err(PagedbError::durably_committed_but_unpublished(commit));
};
self.pager.note_header_written(HeaderCursor {
slot: next_slot,
seq: next_seq,
});
if self.pager.commit_anchor(counter_anchor).is_err() {
let commit = CommitId(state.latest_commit_id);
self.poison(commit);
return Err(PagedbError::durably_committed_but_unpublished(commit));
}
state.pending_apply_journal_id = [0; 16];
self.publish_snapshot(state);
if let Err(error) = self.pager.remove_journal(journal_id).await {
tracing::debug!(name = "apply_journal.orphan", error = %error, "retaining recoverable apply-journal orphan");
}
Ok(())
}
}
fn cleared_header_fields<V: Vfs + Clone>(
db: &Db<V>,
state: &WriterState,
seq: u64,
counter_anchor: u64,
) -> Result<MainDbHeaderFields> {
Ok(MainDbHeaderFields {
format_version: crate::pager::structural_header::MAIN_FORMAT_VERSION,
cipher_id: db.cipher_id.as_byte(),
page_size_log2: page_size_log2(db.page_size)?,
flags: 0,
file_id: db.file_id,
kek_salt: db.kek_salt,
mk_epoch: db.mk_epoch.load(std::sync::atomic::Ordering::SeqCst),
seq,
active_root_page_id: state.root_page_id,
active_root_txn_id: state.latest_commit_id,
counter_anchor,
commit_id: CommitId(state.latest_commit_id),
free_list_root: encode_free_list_root(state.free_list_root_page_id),
catalog_root: encode_root_ref(state.catalog_root_page_id, state.catalog_root_txn_id),
apply_journal_root_page_id: 0,
apply_journal_root_version: 0,
commit_history_root_page_id: state.commit_history_root_page_id,
commit_history_root_version: state.commit_history_root_version,
restore_mode: state.restore_mode,
next_page_id: state.next_page_id,
commit_retain_policy_tag: state.commit_retain_policy_tag,
commit_retain_policy_value: state.commit_retain_policy_value,
realm_id: db.realm_id,
})
}