1use std::{
4 io,
5 time::{Duration, Instant},
6};
7
8use thiserror::Error;
9
10use crate::snapshot::SnapshotReadLimits;
11
12#[derive(Clone, Debug, Default, Eq, PartialEq)]
14pub struct StorageLimits {
15 pub recovery: RecoveryLimits,
17 pub maintenance: MaintenanceLimits,
19}
20
21impl StorageLimits {
22 pub(crate) fn compatibility() -> Self {
23 Self {
24 recovery: RecoveryLimits::compatibility(),
25 maintenance: MaintenanceLimits::compatibility(),
26 }
27 }
28
29 pub(crate) fn validate(&self) -> Result<(), StorageLimitError> {
30 self.recovery.validate()?;
31 self.maintenance.validate()
32 }
33}
34
35#[derive(Clone, Debug, Eq, PartialEq)]
37pub struct RecoveryLimits {
38 pub timeout: Duration,
40 pub max_directory_entries: u64,
42 pub max_log_file_bytes: u64,
44 pub max_log_frames: u64,
46 pub max_transactions: u64,
48 pub max_operations: u64,
50 pub max_decoded_operation_bytes: u64,
52 pub snapshot: SnapshotReadLimits,
54 pub max_lexical_documents: u64,
56 pub max_lexical_tokens: u64,
58}
59
60impl Default for RecoveryLimits {
61 fn default() -> Self {
62 Self {
63 timeout: Duration::from_secs(60),
64 max_directory_entries: 1_000_000,
65 max_log_file_bytes: 2 * 1024 * 1024 * 1024,
66 max_log_frames: 1_000_000,
67 max_transactions: 1_000_000,
68 max_operations: 1_000_000,
69 max_decoded_operation_bytes: 1024 * 1024 * 1024,
70 snapshot: SnapshotReadLimits::default(),
71 max_lexical_documents: 1_000_000,
72 max_lexical_tokens: 10_000_000,
73 }
74 }
75}
76
77impl RecoveryLimits {
78 pub(crate) fn compatibility() -> Self {
79 Self {
80 timeout: Duration::MAX,
81 max_directory_entries: u64::MAX,
82 max_log_file_bytes: u64::MAX,
83 max_log_frames: u64::MAX,
84 max_transactions: u64::MAX,
85 max_operations: u64::MAX,
86 max_decoded_operation_bytes: u64::MAX,
87 snapshot: SnapshotReadLimits {
88 file_bytes: u64::MAX,
89 entries: u64::MAX,
90 decoded_bytes: u64::MAX,
91 },
92 max_lexical_documents: u64::MAX,
93 max_lexical_tokens: u64::MAX,
94 }
95 }
96
97 pub(crate) fn validate(&self) -> Result<(), StorageLimitError> {
98 require_nonzero_duration(self.timeout, "recovery.timeout")?;
99 for (name, value) in [
100 ("recovery.max_directory_entries", self.max_directory_entries),
101 ("recovery.max_log_file_bytes", self.max_log_file_bytes),
102 ("recovery.max_log_frames", self.max_log_frames),
103 ("recovery.max_transactions", self.max_transactions),
104 ("recovery.max_operations", self.max_operations),
105 (
106 "recovery.max_decoded_operation_bytes",
107 self.max_decoded_operation_bytes,
108 ),
109 ("recovery.snapshot.file_bytes", self.snapshot.file_bytes),
110 ("recovery.snapshot.entries", self.snapshot.entries),
111 (
112 "recovery.snapshot.decoded_bytes",
113 self.snapshot.decoded_bytes,
114 ),
115 ("recovery.max_lexical_documents", self.max_lexical_documents),
116 ("recovery.max_lexical_tokens", self.max_lexical_tokens),
117 ] {
118 require_nonzero(value, name)?;
119 }
120 Ok(())
121 }
122}
123
124#[derive(Clone, Debug, Eq, PartialEq)]
126pub struct MaintenanceLimits {
127 pub timeout: Duration,
129 pub snapshot: SnapshotReadLimits,
131}
132
133impl Default for MaintenanceLimits {
134 fn default() -> Self {
135 Self {
136 timeout: Duration::from_secs(60),
137 snapshot: SnapshotReadLimits::default(),
138 }
139 }
140}
141
142impl MaintenanceLimits {
143 fn compatibility() -> Self {
144 Self {
145 timeout: Duration::MAX,
146 snapshot: SnapshotReadLimits {
147 file_bytes: u64::MAX,
148 entries: u64::MAX,
149 decoded_bytes: u64::MAX,
150 },
151 }
152 }
153
154 pub(crate) fn validate(&self) -> Result<(), StorageLimitError> {
155 require_nonzero_duration(self.timeout, "maintenance.timeout")?;
156 for (name, value) in [
157 ("maintenance.snapshot.file_bytes", self.snapshot.file_bytes),
158 ("maintenance.snapshot.entries", self.snapshot.entries),
159 (
160 "maintenance.snapshot.decoded_bytes",
161 self.snapshot.decoded_bytes,
162 ),
163 ] {
164 require_nonzero(value, name)?;
165 }
166 Ok(())
167 }
168}
169
170#[derive(Clone, Debug, Error, Eq, PartialEq)]
172pub enum StorageLimitError {
173 #[error("storage limit must be positive: {name}")]
175 ZeroLimit {
176 name: &'static str,
178 },
179 #[error("storage operation timed out")]
181 TimedOut,
182 #[error("storage directory entry limit exceeded: {maximum}")]
184 DirectoryEntriesExceeded {
185 maximum: u64,
187 },
188 #[error("log file byte limit exceeded: {actual} > {maximum}")]
190 LogFileBytesExceeded {
191 actual: u64,
193 maximum: u64,
195 },
196 #[error("log frame limit exceeded: {maximum}")]
198 LogFramesExceeded {
199 maximum: u64,
201 },
202 #[error("recovery transaction limit exceeded: {maximum}")]
204 TransactionsExceeded {
205 maximum: u64,
207 },
208 #[error("recovery operation limit exceeded: {maximum}")]
210 OperationsExceeded {
211 maximum: u64,
213 },
214 #[error("recovery decoded operation byte limit exceeded: {maximum}")]
216 DecodedOperationBytesExceeded {
217 maximum: u64,
219 },
220 #[error("lexical rebuild document limit exceeded: {maximum}")]
222 LexicalDocumentsExceeded {
223 maximum: u64,
225 },
226 #[error("lexical rebuild token limit exceeded: {maximum}")]
228 LexicalTokensExceeded {
229 maximum: u64,
231 },
232}
233
234#[derive(Clone, Debug)]
235pub(crate) struct OperationDeadline {
236 started: Instant,
237 timeout: Duration,
238}
239
240impl OperationDeadline {
241 pub(crate) fn new(timeout: Duration) -> Self {
242 Self {
243 started: Instant::now(),
244 timeout,
245 }
246 }
247
248 pub(crate) fn check(&self) -> Result<(), StorageLimitError> {
249 if self.started.elapsed() >= self.timeout {
250 Err(StorageLimitError::TimedOut)
251 } else {
252 Ok(())
253 }
254 }
255}
256
257pub(crate) fn limit_io_error(source: StorageLimitError) -> io::Error {
258 let kind = if matches!(source, StorageLimitError::TimedOut) {
259 io::ErrorKind::TimedOut
260 } else {
261 io::ErrorKind::Other
262 };
263 io::Error::new(kind, source)
264}
265
266pub fn storage_limit_from_io(source: &io::Error) -> Option<&StorageLimitError> {
274 source
275 .get_ref()
276 .and_then(|source| source.downcast_ref::<StorageLimitError>())
277}
278
279fn require_nonzero(value: u64, name: &'static str) -> Result<(), StorageLimitError> {
280 if value == 0 {
281 Err(StorageLimitError::ZeroLimit { name })
282 } else {
283 Ok(())
284 }
285}
286
287fn require_nonzero_duration(value: Duration, name: &'static str) -> Result<(), StorageLimitError> {
288 if value.is_zero() {
289 Err(StorageLimitError::ZeroLimit { name })
290 } else {
291 Ok(())
292 }
293}