Line data Source code
1 : use std::{collections::hash_map::Entry, fs, path::PathBuf, sync::Arc};
2 :
3 : use anyhow::Context;
4 : use tracing::{error, info, info_span, warn};
5 : use utils::{crashsafe, fs_ext, id::TimelineId, lsn::Lsn};
6 :
7 : use crate::{context::RequestContext, import_datadir, tenant::Tenant};
8 :
9 : use super::Timeline;
10 :
11 : /// A timeline with some of its files on disk, being initialized.
12 : /// This struct ensures the atomicity of the timeline init: it's either properly created and inserted into pageserver's memory, or
13 : /// its local files are removed. In the worst case of a crash, an uninit mark file is left behind, which causes the directory
14 : /// to be removed on next restart.
15 : ///
16 : /// The caller is responsible for proper timeline data filling before the final init.
17 : #[must_use]
18 : pub struct UninitializedTimeline<'t> {
19 : pub(crate) owning_tenant: &'t Tenant,
20 : timeline_id: TimelineId,
21 : raw_timeline: Option<(Arc<Timeline>, TimelineUninitMark)>,
22 : }
23 :
24 : impl<'t> UninitializedTimeline<'t> {
25 1041 : pub(crate) fn new(
26 1041 : owning_tenant: &'t Tenant,
27 1041 : timeline_id: TimelineId,
28 1041 : raw_timeline: Option<(Arc<Timeline>, TimelineUninitMark)>,
29 1041 : ) -> Self {
30 1041 : Self {
31 1041 : owning_tenant,
32 1041 : timeline_id,
33 1041 : raw_timeline,
34 1041 : }
35 1041 : }
36 :
37 : /// Finish timeline creation: insert it into the Tenant's timelines map and remove the
38 : /// uninit mark file.
39 : ///
40 : /// This function launches the flush loop if not already done.
41 : ///
42 : /// The caller is responsible for activating the timeline (function `.activate()`).
43 1036 : pub(crate) fn finish_creation(mut self) -> anyhow::Result<Arc<Timeline>> {
44 1036 : let timeline_id = self.timeline_id;
45 1036 : let tenant_id = self.owning_tenant.tenant_id;
46 :
47 1036 : let (new_timeline, uninit_mark) = self.raw_timeline.take().with_context(|| {
48 0 : format!("No timeline for initalization found for {tenant_id}/{timeline_id}")
49 1036 : })?;
50 :
51 : // Check that the caller initialized disk_consistent_lsn
52 1036 : let new_disk_consistent_lsn = new_timeline.get_disk_consistent_lsn();
53 1036 : anyhow::ensure!(
54 1036 : new_disk_consistent_lsn.is_valid(),
55 0 : "new timeline {tenant_id}/{timeline_id} has invalid disk_consistent_lsn"
56 : );
57 :
58 1036 : let mut timelines = self.owning_tenant.timelines.lock().unwrap();
59 1036 : match timelines.entry(timeline_id) {
60 0 : Entry::Occupied(_) => anyhow::bail!(
61 0 : "Found freshly initialized timeline {tenant_id}/{timeline_id} in the tenant map"
62 0 : ),
63 1036 : Entry::Vacant(v) => {
64 1036 : uninit_mark.remove_uninit_mark().with_context(|| {
65 0 : format!(
66 0 : "Failed to remove uninit mark file for timeline {tenant_id}/{timeline_id}"
67 0 : )
68 1036 : })?;
69 1036 : v.insert(Arc::clone(&new_timeline));
70 1036 :
71 1036 : new_timeline.maybe_spawn_flush_loop();
72 1036 : }
73 1036 : }
74 1036 :
75 1036 : Ok(new_timeline)
76 1036 : }
77 :
78 : /// Prepares timeline data by loading it from the basebackup archive.
79 5 : pub(crate) async fn import_basebackup_from_tar(
80 5 : self,
81 5 : copyin_read: &mut (impl tokio::io::AsyncRead + Send + Sync + Unpin),
82 5 : base_lsn: Lsn,
83 5 : broker_client: storage_broker::BrokerClientChannel,
84 5 : ctx: &RequestContext,
85 5 : ) -> anyhow::Result<Arc<Timeline>> {
86 5 : let raw_timeline = self.raw_timeline()?;
87 :
88 5 : import_datadir::import_basebackup_from_tar(raw_timeline, copyin_read, base_lsn, ctx)
89 543 : .await
90 5 : .context("Failed to import basebackup")?;
91 :
92 : // Flush the new layer files to disk, before we make the timeline as available to
93 : // the outside world.
94 : //
95 : // Flush loop needs to be spawned in order to be able to flush.
96 3 : raw_timeline.maybe_spawn_flush_loop();
97 3 :
98 3 : fail::fail_point!("before-checkpoint-new-timeline", |_| {
99 0 : anyhow::bail!("failpoint before-checkpoint-new-timeline");
100 3 : });
101 :
102 3 : raw_timeline
103 3 : .freeze_and_flush()
104 3 : .await
105 3 : .context("Failed to flush after basebackup import")?;
106 :
107 : // All the data has been imported. Insert the Timeline into the tenant's timelines
108 : // map and remove the uninit mark file.
109 3 : let tl = self.finish_creation()?;
110 3 : tl.activate(broker_client, None, ctx);
111 3 : Ok(tl)
112 5 : }
113 :
114 683 : pub(crate) fn raw_timeline(&self) -> anyhow::Result<&Arc<Timeline>> {
115 683 : Ok(&self
116 683 : .raw_timeline
117 683 : .as_ref()
118 683 : .with_context(|| {
119 0 : format!(
120 0 : "No raw timeline {}/{} found",
121 0 : self.owning_tenant.tenant_id, self.timeline_id
122 0 : )
123 683 : })?
124 : .0)
125 683 : }
126 : }
127 :
128 : impl Drop for UninitializedTimeline<'_> {
129 : fn drop(&mut self) {
130 1040 : if let Some((_, uninit_mark)) = self.raw_timeline.take() {
131 4 : let _entered = info_span!("drop_uninitialized_timeline", tenant_id = %self.owning_tenant.tenant_id, timeline_id = %self.timeline_id).entered();
132 4 : error!("Timeline got dropped without initializing, cleaning its files");
133 4 : cleanup_timeline_directory(uninit_mark);
134 1036 : }
135 1040 : }
136 : }
137 :
138 5 : pub(crate) fn cleanup_timeline_directory(uninit_mark: TimelineUninitMark) {
139 5 : let timeline_path = &uninit_mark.timeline_path;
140 5 : match fs_ext::ignore_absent_files(|| fs::remove_dir_all(timeline_path)) {
141 : Ok(()) => {
142 5 : info!("Timeline dir {timeline_path:?} removed successfully, removing the uninit mark")
143 : }
144 0 : Err(e) => {
145 0 : error!("Failed to clean up uninitialized timeline directory {timeline_path:?}: {e:?}")
146 : }
147 : }
148 5 : drop(uninit_mark); // mark handles its deletion on drop, gets retained if timeline dir exists
149 5 : }
150 :
151 : /// An uninit mark file, created along the timeline dir to ensure the timeline either gets fully initialized and loaded into pageserver's memory,
152 : /// or gets removed eventually.
153 : ///
154 : /// XXX: it's important to create it near the timeline dir, not inside it to ensure timeline dir gets removed first.
155 : #[must_use]
156 : pub(crate) struct TimelineUninitMark {
157 : uninit_mark_deleted: bool,
158 : uninit_mark_path: PathBuf,
159 : pub(crate) timeline_path: PathBuf,
160 : }
161 :
162 : impl TimelineUninitMark {
163 1050 : pub(crate) fn new(uninit_mark_path: PathBuf, timeline_path: PathBuf) -> Self {
164 1050 : Self {
165 1050 : uninit_mark_deleted: false,
166 1050 : uninit_mark_path,
167 1050 : timeline_path,
168 1050 : }
169 1050 : }
170 :
171 1036 : fn remove_uninit_mark(mut self) -> anyhow::Result<()> {
172 1036 : if !self.uninit_mark_deleted {
173 1036 : self.delete_mark_file_if_present()?;
174 0 : }
175 :
176 1036 : Ok(())
177 1036 : }
178 :
179 1049 : fn delete_mark_file_if_present(&mut self) -> anyhow::Result<()> {
180 1049 : let uninit_mark_file = &self.uninit_mark_path;
181 1049 : let uninit_mark_parent = uninit_mark_file
182 1049 : .parent()
183 1049 : .with_context(|| format!("Uninit mark file {uninit_mark_file:?} has no parent"))?;
184 1049 : fs_ext::ignore_absent_files(|| fs::remove_file(uninit_mark_file)).with_context(|| {
185 0 : format!("Failed to remove uninit mark file at path {uninit_mark_file:?}")
186 1049 : })?;
187 1049 : crashsafe::fsync(uninit_mark_parent).context("Failed to fsync uninit mark parent")?;
188 1049 : self.uninit_mark_deleted = true;
189 1049 :
190 1049 : Ok(())
191 1049 : }
192 : }
193 :
194 : impl Drop for TimelineUninitMark {
195 1049 : fn drop(&mut self) {
196 1049 : if !self.uninit_mark_deleted {
197 13 : if self.timeline_path.exists() {
198 0 : error!(
199 0 : "Uninit mark {} is not removed, timeline {} stays uninitialized",
200 0 : self.uninit_mark_path.display(),
201 0 : self.timeline_path.display()
202 0 : )
203 : } else {
204 : // unblock later timeline creation attempts
205 13 : warn!(
206 13 : "Removing intermediate uninit mark file {}",
207 13 : self.uninit_mark_path.display()
208 13 : );
209 13 : if let Err(e) = self.delete_mark_file_if_present() {
210 0 : error!("Failed to remove the uninit mark file: {e}")
211 13 : }
212 : }
213 1036 : }
214 1049 : }
215 : }
|