Line data Source code
1 : use std::{collections::hash_map::Entry, fs, sync::Arc};
2 :
3 : use anyhow::Context;
4 : use camino::Utf8PathBuf;
5 : use tracing::{error, info, info_span, warn};
6 : use utils::{crashsafe, fs_ext, id::TimelineId, lsn::Lsn};
7 :
8 : use crate::{context::RequestContext, import_datadir, tenant::Tenant};
9 :
10 : use super::Timeline;
11 :
12 : /// A timeline with some of its files on disk, being initialized.
13 : /// This struct ensures the atomicity of the timeline init: it's either properly created and inserted into pageserver's memory, or
14 : /// its local files are removed. In the worst case of a crash, an uninit mark file is left behind, which causes the directory
15 : /// to be removed on next restart.
16 : ///
17 : /// The caller is responsible for proper timeline data filling before the final init.
18 : #[must_use]
19 : pub struct UninitializedTimeline<'t> {
20 : pub(crate) owning_tenant: &'t Tenant,
21 : timeline_id: TimelineId,
22 : raw_timeline: Option<(Arc<Timeline>, TimelineUninitMark<'t>)>,
23 : }
24 :
25 : impl<'t> UninitializedTimeline<'t> {
26 290 : pub(crate) fn new(
27 290 : owning_tenant: &'t Tenant,
28 290 : timeline_id: TimelineId,
29 290 : raw_timeline: Option<(Arc<Timeline>, TimelineUninitMark<'t>)>,
30 290 : ) -> Self {
31 290 : Self {
32 290 : owning_tenant,
33 290 : timeline_id,
34 290 : raw_timeline,
35 290 : }
36 290 : }
37 :
38 : /// Finish timeline creation: insert it into the Tenant's timelines map and remove the
39 : /// uninit mark file.
40 : ///
41 : /// This function launches the flush loop if not already done.
42 : ///
43 : /// The caller is responsible for activating the timeline (function `.activate()`).
44 284 : pub(crate) fn finish_creation(mut self) -> anyhow::Result<Arc<Timeline>> {
45 284 : let timeline_id = self.timeline_id;
46 284 : let tenant_shard_id = self.owning_tenant.tenant_shard_id;
47 284 :
48 284 : if self.raw_timeline.is_none() {
49 0 : return Err(anyhow::anyhow!(
50 0 : "No timeline for initialization found for {tenant_shard_id}/{timeline_id}"
51 0 : ));
52 284 : }
53 284 :
54 284 : // Check that the caller initialized disk_consistent_lsn
55 284 : let new_disk_consistent_lsn = self
56 284 : .raw_timeline
57 284 : .as_ref()
58 284 : .expect("checked above")
59 284 : .0
60 284 : .get_disk_consistent_lsn();
61 284 :
62 284 : anyhow::ensure!(
63 284 : new_disk_consistent_lsn.is_valid(),
64 0 : "new timeline {tenant_shard_id}/{timeline_id} has invalid disk_consistent_lsn"
65 : );
66 :
67 284 : let mut timelines = self.owning_tenant.timelines.lock().unwrap();
68 284 : match timelines.entry(timeline_id) {
69 0 : Entry::Occupied(_) => anyhow::bail!(
70 0 : "Found freshly initialized timeline {tenant_shard_id}/{timeline_id} in the tenant map"
71 0 : ),
72 284 : Entry::Vacant(v) => {
73 284 : // after taking here should be no fallible operations, because the drop guard will not
74 284 : // cleanup after and would block for example the tenant deletion
75 284 : let (new_timeline, uninit_mark) =
76 284 : self.raw_timeline.take().expect("already checked");
77 284 :
78 284 : // this is the mutual exclusion between different retries to create the timeline;
79 284 : // this should be an assertion.
80 284 : uninit_mark.remove_uninit_mark().with_context(|| {
81 0 : format!(
82 0 : "Failed to remove uninit mark file for timeline {tenant_shard_id}/{timeline_id}"
83 0 : )
84 284 : })?;
85 284 : v.insert(Arc::clone(&new_timeline));
86 284 :
87 284 : new_timeline.maybe_spawn_flush_loop();
88 284 :
89 284 : Ok(new_timeline)
90 : }
91 : }
92 284 : }
93 :
94 : /// Prepares timeline data by loading it from the basebackup archive.
95 0 : pub(crate) async fn import_basebackup_from_tar(
96 0 : self,
97 0 : copyin_read: &mut (impl tokio::io::AsyncRead + Send + Sync + Unpin),
98 0 : base_lsn: Lsn,
99 0 : broker_client: storage_broker::BrokerClientChannel,
100 0 : ctx: &RequestContext,
101 0 : ) -> anyhow::Result<Arc<Timeline>> {
102 0 : let raw_timeline = self.raw_timeline()?;
103 :
104 0 : import_datadir::import_basebackup_from_tar(raw_timeline, copyin_read, base_lsn, ctx)
105 0 : .await
106 0 : .context("Failed to import basebackup")?;
107 :
108 : // Flush the new layer files to disk, before we make the timeline as available to
109 : // the outside world.
110 : //
111 : // Flush loop needs to be spawned in order to be able to flush.
112 0 : raw_timeline.maybe_spawn_flush_loop();
113 0 :
114 0 : fail::fail_point!("before-checkpoint-new-timeline", |_| {
115 0 : anyhow::bail!("failpoint before-checkpoint-new-timeline");
116 0 : });
117 :
118 0 : raw_timeline
119 0 : .freeze_and_flush()
120 0 : .await
121 0 : .context("Failed to flush after basebackup import")?;
122 :
123 : // All the data has been imported. Insert the Timeline into the tenant's timelines
124 : // map and remove the uninit mark file.
125 0 : let tl = self.finish_creation()?;
126 0 : tl.activate(broker_client, None, ctx);
127 0 : Ok(tl)
128 0 : }
129 :
130 80 : pub(crate) fn raw_timeline(&self) -> anyhow::Result<&Arc<Timeline>> {
131 80 : Ok(&self
132 80 : .raw_timeline
133 80 : .as_ref()
134 80 : .with_context(|| {
135 0 : format!(
136 0 : "No raw timeline {}/{} found",
137 0 : self.owning_tenant.tenant_shard_id, self.timeline_id
138 0 : )
139 80 : })?
140 : .0)
141 80 : }
142 : }
143 :
144 : impl Drop for UninitializedTimeline<'_> {
145 288 : fn drop(&mut self) {
146 288 : if let Some((_, uninit_mark)) = self.raw_timeline.take() {
147 4 : let _entered = info_span!("drop_uninitialized_timeline", tenant_id = %self.owning_tenant.tenant_shard_id.tenant_id, shard_id = %self.owning_tenant.tenant_shard_id.shard_slug(), timeline_id = %self.timeline_id).entered();
148 4 : error!("Timeline got dropped without initializing, cleaning its files");
149 4 : cleanup_timeline_directory(uninit_mark);
150 284 : }
151 288 : }
152 : }
153 :
154 4 : pub(crate) fn cleanup_timeline_directory(uninit_mark: TimelineUninitMark) {
155 4 : let timeline_path = &uninit_mark.timeline_path;
156 4 : match fs_ext::ignore_absent_files(|| fs::remove_dir_all(timeline_path)) {
157 : Ok(()) => {
158 4 : info!("Timeline dir {timeline_path:?} removed successfully, removing the uninit mark")
159 : }
160 0 : Err(e) => {
161 0 : error!("Failed to clean up uninitialized timeline directory {timeline_path:?}: {e:?}")
162 : }
163 : }
164 4 : drop(uninit_mark); // mark handles its deletion on drop, gets retained if timeline dir exists
165 4 : }
166 :
167 : /// An uninit mark file, created along the timeline dir to ensure the timeline either gets fully initialized and loaded into pageserver's memory,
168 : /// or gets removed eventually.
169 : ///
170 : /// XXX: it's important to create it near the timeline dir, not inside it to ensure timeline dir gets removed first.
171 : #[must_use]
172 : pub(crate) struct TimelineUninitMark<'t> {
173 : owning_tenant: &'t Tenant,
174 : timeline_id: TimelineId,
175 : uninit_mark_deleted: bool,
176 : uninit_mark_path: Utf8PathBuf,
177 : pub(crate) timeline_path: Utf8PathBuf,
178 : }
179 :
180 : /// Errors when acquiring exclusive access to a timeline ID for creation
181 2 : #[derive(thiserror::Error, Debug)]
182 : pub(crate) enum TimelineExclusionError {
183 : #[error("Already exists")]
184 : AlreadyExists(Arc<Timeline>),
185 : #[error("Already creating")]
186 : AlreadyCreating,
187 :
188 : // e.g. I/O errors, or some failure deep in postgres initdb
189 : #[error(transparent)]
190 : Other(#[from] anyhow::Error),
191 : }
192 :
193 : impl<'t> TimelineUninitMark<'t> {
194 296 : pub(crate) fn new(
195 296 : owning_tenant: &'t Tenant,
196 296 : timeline_id: TimelineId,
197 296 : uninit_mark_path: Utf8PathBuf,
198 296 : timeline_path: Utf8PathBuf,
199 296 : ) -> Result<Self, TimelineExclusionError> {
200 296 : // Lock order: this is the only place we take both locks. During drop() we only
201 296 : // lock creating_timelines
202 296 : let timelines = owning_tenant.timelines.lock().unwrap();
203 296 : let mut creating_timelines: std::sync::MutexGuard<
204 296 : '_,
205 296 : std::collections::HashSet<TimelineId>,
206 296 : > = owning_tenant.timelines_creating.lock().unwrap();
207 :
208 296 : if let Some(existing) = timelines.get(&timeline_id) {
209 2 : Err(TimelineExclusionError::AlreadyExists(existing.clone()))
210 294 : } else if creating_timelines.contains(&timeline_id) {
211 0 : Err(TimelineExclusionError::AlreadyCreating)
212 : } else {
213 294 : creating_timelines.insert(timeline_id);
214 294 : Ok(Self {
215 294 : owning_tenant,
216 294 : timeline_id,
217 294 : uninit_mark_deleted: false,
218 294 : uninit_mark_path,
219 294 : timeline_path,
220 294 : })
221 : }
222 296 : }
223 :
224 284 : fn remove_uninit_mark(mut self) -> anyhow::Result<()> {
225 284 : if !self.uninit_mark_deleted {
226 284 : self.delete_mark_file_if_present()?;
227 0 : }
228 :
229 284 : Ok(())
230 284 : }
231 :
232 292 : fn delete_mark_file_if_present(&mut self) -> anyhow::Result<()> {
233 292 : let uninit_mark_file = &self.uninit_mark_path;
234 292 : let uninit_mark_parent = uninit_mark_file
235 292 : .parent()
236 292 : .with_context(|| format!("Uninit mark file {uninit_mark_file:?} has no parent"))?;
237 292 : fs_ext::ignore_absent_files(|| fs::remove_file(uninit_mark_file)).with_context(|| {
238 0 : format!("Failed to remove uninit mark file at path {uninit_mark_file:?}")
239 292 : })?;
240 292 : crashsafe::fsync(uninit_mark_parent).context("Failed to fsync uninit mark parent")?;
241 292 : self.uninit_mark_deleted = true;
242 292 :
243 292 : Ok(())
244 292 : }
245 : }
246 :
247 : impl Drop for TimelineUninitMark<'_> {
248 292 : fn drop(&mut self) {
249 292 : if !self.uninit_mark_deleted {
250 8 : if self.timeline_path.exists() {
251 0 : error!(
252 0 : "Uninit mark {} is not removed, timeline {} stays uninitialized",
253 0 : self.uninit_mark_path, self.timeline_path
254 0 : )
255 : } else {
256 : // unblock later timeline creation attempts
257 8 : warn!(
258 8 : "Removing intermediate uninit mark file {}",
259 8 : self.uninit_mark_path
260 8 : );
261 8 : if let Err(e) = self.delete_mark_file_if_present() {
262 0 : error!("Failed to remove the uninit mark file: {e}")
263 8 : }
264 : }
265 284 : }
266 :
267 292 : self.owning_tenant
268 292 : .timelines_creating
269 292 : .lock()
270 292 : .unwrap()
271 292 : .remove(&self.timeline_id);
272 292 : }
273 : }
|