Skip to content

Commit 0c2c57e

Browse files
committed
fix(pageserver): prevent stack overflow in count_deltas
1 parent 8f60b04 commit 0c2c57e

1 file changed

Lines changed: 122 additions & 50 deletions

File tree

pageserver/src/tenant/layer_map.rs

Lines changed: 122 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -839,70 +839,114 @@ impl LayerMap {
839839
/// we'll need to visit for any page reconstruction in this region.
840840
/// We use this heuristic to decide whether to create an image layer.
841841
pub fn count_deltas(&self, key: &Range<Key>, lsn: &Range<Lsn>, limit: Option<usize>) -> usize {
842-
// We get the delta coverage of the region, and for each part of the coverage
843-
// we recurse right underneath the delta. The recursion depth is limited by
844-
// the largest result this function could return, which is in practice between
845-
// 3 and 10 (since we usually try to create an image when the number gets larger).
846-
847842
if lsn.is_empty() || key.is_empty() || limit == Some(0) {
848843
return 0;
849844
}
850845

851-
let version = match self.historic.get().unwrap().get_version(lsn.end.0 - 1) {
852-
Some(v) => v,
853-
None => return 0,
854-
};
846+
struct Frame {
847+
max_stacked_deltas: usize,
848+
}
855849

856-
let start = key.start.to_i128();
857-
let end = key.end.to_i128();
850+
enum Task {
851+
Process {
852+
key: Range<Key>,
853+
lsn: Range<Lsn>,
854+
limit: Option<usize>,
855+
},
856+
Accumulate {
857+
frame_idx: usize,
858+
base_count: usize,
859+
},
860+
FinishFrame {
861+
frame_idx: usize,
862+
},
863+
}
858864

859-
// Initialize loop variables
860-
let mut max_stacked_deltas = 0;
861-
let mut current_key = start;
862-
let mut current_val = version.delta_coverage.query(start);
863-
864-
// Loop through the delta coverage and recurse on each part
865-
for (change_key, change_val) in version.delta_coverage.range(start..end) {
866-
// If there's a relevant delta in this part, add 1 and recurse down
867-
if let Some(val) = &current_val {
868-
if val.get_lsn_range().end > lsn.start {
869-
let kr = Key::from_i128(current_key)..Key::from_i128(change_key);
870-
let lr = lsn.start..val.get_lsn_range().start;
871-
if !kr.is_empty() {
872-
let base_count = Self::is_reimage_worthy(val, key) as usize;
873-
let new_limit = limit.map(|l| l - base_count);
874-
let max_stacked_deltas_underneath = self.count_deltas(&kr, &lr, new_limit);
875-
max_stacked_deltas = std::cmp::max(
876-
max_stacked_deltas,
877-
base_count + max_stacked_deltas_underneath,
878-
);
865+
let mut last_result = 0;
866+
let mut frames: Vec<Frame> = Vec::new();
867+
let mut tasks = vec![Task::Process {
868+
key: key.clone(),
869+
lsn: lsn.clone(),
870+
limit,
871+
}];
872+
873+
while let Some(task) = tasks.pop() {
874+
match task {
875+
Task::Process { key, lsn, limit } => {
876+
if lsn.is_empty() || key.is_empty() || limit == Some(0) {
877+
last_result = 0;
878+
continue;
879879
}
880-
}
881-
}
882880

883-
current_key = change_key;
884-
current_val.clone_from(&change_val);
885-
}
881+
let version = match self.historic.get().unwrap().get_version(lsn.end.0 - 1) {
882+
Some(v) => v,
883+
None => {
884+
last_result = 0;
885+
continue;
886+
}
887+
};
886888

887-
// Consider the last part
888-
if let Some(val) = &current_val {
889-
if val.get_lsn_range().end > lsn.start {
890-
let kr = Key::from_i128(current_key)..Key::from_i128(end);
891-
let lr = lsn.start..val.get_lsn_range().start;
892-
893-
if !kr.is_empty() {
894-
let base_count = Self::is_reimage_worthy(val, key) as usize;
895-
let new_limit = limit.map(|l| l - base_count);
896-
let max_stacked_deltas_underneath = self.count_deltas(&kr, &lr, new_limit);
897-
max_stacked_deltas = std::cmp::max(
898-
max_stacked_deltas,
899-
base_count + max_stacked_deltas_underneath,
889+
let start = key.start.to_i128();
890+
let end = key.end.to_i128();
891+
892+
let mut partitions = Vec::new();
893+
let mut current_key = start;
894+
let mut current_val = version.delta_coverage.query(start);
895+
896+
let changes = version.delta_coverage.range(start..end)
897+
.chain(std::iter::once((end, None)));
898+
899+
for (change_key, next_val) in changes {
900+
if let Some(val) = &current_val {
901+
if val.get_lsn_range().end > lsn.start {
902+
let kr = Key::from_i128(current_key)..Key::from_i128(change_key);
903+
let lr = lsn.start..val.get_lsn_range().start;
904+
if !kr.is_empty() {
905+
let base_count = Self::is_reimage_worthy(val, &key) as usize;
906+
let new_limit = limit.map(|l| l.saturating_sub(base_count));
907+
partitions.push((kr, lr, new_limit, base_count));
908+
}
909+
}
910+
}
911+
current_key = change_key;
912+
current_val = next_val;
913+
}
914+
915+
if partitions.is_empty() {
916+
last_result = 0;
917+
continue;
918+
}
919+
920+
let frame_idx = frames.len();
921+
frames.push(Frame { max_stacked_deltas: 0 });
922+
923+
tasks.push(Task::FinishFrame { frame_idx });
924+
925+
for (kr, lr, new_limit, base_count) in partitions.into_iter().rev() {
926+
tasks.push(Task::Accumulate {
927+
frame_idx,
928+
base_count,
929+
});
930+
tasks.push(Task::Process {
931+
key: kr,
932+
lsn: lr,
933+
limit: new_limit,
934+
});
935+
}
936+
}
937+
Task::Accumulate { frame_idx, base_count } => {
938+
frames[frame_idx].max_stacked_deltas = std::cmp::max(
939+
frames[frame_idx].max_stacked_deltas,
940+
base_count + last_result,
900941
);
901942
}
943+
Task::FinishFrame { frame_idx } => {
944+
last_result = frames[frame_idx].max_stacked_deltas;
945+
}
902946
}
903947
}
904948

905-
max_stacked_deltas
949+
last_result
906950
}
907951

908952
/* BEGIN_HADRON */
@@ -1808,6 +1852,34 @@ mod tests {
18081852
assert_eq!(Lsn(100), image_consistent_lsn);
18091853
}
18101854

1855+
#[test]
1856+
fn test_count_deltas_stack_overflow() {
1857+
let mut layer_map = LayerMap::default();
1858+
let mut updates = layer_map.batch_update();
1859+
1860+
let tenant_shard_id = TenantShardId::unsharded(TenantId::generate());
1861+
let timeline_id = TimelineId::generate();
1862+
1863+
// Insert 10,000 overlapping delta layers with increasing LSN ranges
1864+
// Use a non-L0 key range so that all layers are considered reimage-worthy at all partition ranges.
1865+
let key_range = Key::from_i128(0)..Key::from_i128(100);
1866+
for i in 1..10000 {
1867+
let desc = PersistentLayerDesc::new_delta(
1868+
tenant_shard_id,
1869+
timeline_id,
1870+
key_range.clone(),
1871+
Lsn(i * 10)..Lsn(i * 10 + 5),
1872+
1024,
1873+
);
1874+
updates.insert_historic(desc);
1875+
}
1876+
updates.flush();
1877+
1878+
// This call will recursively traverse all 10,000 layers in the original code, causing stack overflow
1879+
let count = layer_map.count_deltas(&key_range, &(Lsn(0)..Lsn(100000)), None);
1880+
assert_eq!(count, 9999);
1881+
}
1882+
18111883
/* END_HADRON */
18121884
}
18131885

0 commit comments

Comments
 (0)