pi_async_fs 0.1.2

Runtime-agnostic asynchronous filesystem contracts for local and remote storage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
// 增长型读取的资源上限模型。
//
// 增长型读取与固定目标读取具有不同权限:它从承载体当前逻辑末尾追加,
// 可以使用尚未初始化的备用容量,并在空间不足时申请更多容量。本模块只
// 表达一次调用最多获准新增多少有效字节;它不拥有缓冲区、不执行分配、
// 不读取文件,也不授予实现提前暴露未初始化内存的权限。
//
// 当前增长型核心读取只采用尾部追加语义。需要替换旧内容的调用方
// 应在调用前显式清空承载体,或传入一个新的空承载体;本模块不提供会在
// 异步操作开始时破坏旧内容的 `Replace` 模式。

use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};

use pi_result::{ClassifyErrorKind, ErrorKind, RawResult};

// 把增长上限绑定到承载体当前长度时发现的算术错误。
//
// 该错误属于无副作用的调用前输入错误。实现必须先完成这项检查,随后才可
// 取得备用容量、申请内存、触发后端 I/O 或推进顺序文件游标。因此,返回本
// 错误时,输入承载体及外部文件状态都必须保持不变。
#[derive(Debug, pi_result::thiserror::Error)]
#[non_exhaustive]
/// 把增长上限应用到缓冲区当前长度时发现的算术错误。
pub enum ReadGrowthLimitError {
    // 当前有效长度与本次最大新增量之和无法用 `usize` 表达。
    #[error(
        "read growth final length overflows usize: current length {current_len}, \
         maximum additional bytes {max_additional_bytes}"
    )]
    /// 当前长度与最大新增量之和无法用 `usize` 表达。
    FinalLengthOverflow {
        // 绑定上限时承载体权威已初始化视图的字节长度。
        /// 缓冲区当前已初始化内容的字节长度。
        current_len: usize,
        // 本次调用获准追加的最大有效字节数。
        /// 本次读取允许新增的最大字节数。
        max_additional_bytes: usize,
    },
}

impl ClassifyErrorKind for ReadGrowthLimitError {
    fn classify_error_kind(&self) -> ErrorKind {
        ErrorKind::InvalidInput
    }
}

// 一次增长型读取最多可以追加的有效字节数。
//
// # 作用与坐标
//
// 假设操作开始时承载体的权威已初始化长度为 `L`,本值为 `N`。本次调用只
// 获准把至多 `N` 个已经初始化的字节追加到逻辑尾部,并把公开有效视图从
// `[0, L)` 推进到不超过 `[0, L + N)`。已有的 `[0, L)` 不计入上限,也不得
// 被增长型读取覆盖或清除。
//
// 本类型不表示文件偏移、文件范围、MMAP 范围、目标物理容量、单次系统调用
// 大小或整个文件的最大尺寸。文件偏移与读取长度的 `u64` 检查由具体读取
// API 的文件范围合同另外承担。
//
// # 资源与分配语义
//
// 增长型实现应先使用已有备用容量,空间不足时才自动申请更多容量。上限是
// “允许提交多少新增有效字节”的硬边界,而不是要求实现预先分配全部空间。
// 分配器可以按自身增长策略取得超过 `L + N` 的物理容量,但不得把多出的
// 容量计入本次有效结果或读取超过 `N` 个字节。
//
// 本类型故意不提供无限增长变体。调用方必须显式选择每次调用可承担的最大
// 新增量,从而限制超大本地文件、远端对象或恶意输入造成的内存消耗。一个
// 很大的显式数值仍然可能导致昂贵分配;本类型是硬边界,不是资源充足保证。
//
// # 零上限
//
// `N == 0` 合法。增长型读取必须把它作为零 I/O、零分配、零修改操作处理。
// 因为没有探测数据源,结果只能说明“已达到调用方限制”,不能据此宣称已经
// 观察到文件结束。
//
// # 初始化与取消安全
//
// 本值只授予上限,不证明备用内存已经初始化。实现只能通过
// `MaybeUninit<u8>` 或等价的安全抽象把备用容量交给后端,并且只能提交后端
// 确认写完的连续前缀。错误、取消或 panic 都不得使未初始化字节进入公开的
// `[0, len)`。completion 后端尚未停止访问时,allocation 还必须保持存活且
// 地址稳定;拥有本值不豁免该生命周期义务。
/// 一次增长型读取最多可以新增的有效字节数。
///
/// 上限只约束本次调用新增并提交的字节,不包括缓冲区已有内容,也不表示应当
/// 预先分配相同容量。零是合法上限,并要求读取不观察数据源。
pub struct ReadGrowthLimit {
    max_additional_bytes: usize,
}

impl ReadGrowthLimit {
    // 创建一次调用的显式最大新增量。
    //
    // 任意 `usize`(包括零)在尚未绑定真实承载体长度时都是结构合法值,
    // 因此构造本身不会失败,也不会分配或执行 I/O。调用前必须再使用
    // [`Self::checked_final_len`] 检查它与当时真实长度的组合。
    //
    // 该方法是纯函数;无锁、无外部副作用、可重复调用,并计划为 O(1)。
    #[must_use]
    /// 从最大新增字节数构造上限。
    pub fn new(max_additional_bytes: usize) -> Self {
        Self {
            max_additional_bytes,
        }
    }

    // 返回本次调用最多获准追加的有效字节数。
    //
    // 返回值不包含操作开始前已有的有效字节,也不表示当前容量或必然读取的
    // 字节数。该访问不分配、不加锁、不执行 I/O,并计划为 O(1)。
    #[must_use]
    /// 返回本次允许新增的最大字节数。
    pub fn max_additional_bytes(&self) -> usize {
        self.max_additional_bytes
    }

    // 把上限绑定到操作开始时的真实有效长度,并返回最大最终有效长度。
    //
    // 成功值严格等于 `current_len + self.max_additional_bytes()`。加法必须使用
    // 受检算术;若无法用 `usize` 表达,则返回
    // [`ReadGrowthLimitError::FinalLengthOverflow`]。实现必须在任何分配、目标
    // 指针访问、后端 I/O、游标推进和承载体修改之前调用本方法。
    //
    // 本方法只验证长度算术,不检查物理容量、可用内存、文件偏移、文件长度
    // 或后端对象大小。它是只读、幂等、无锁、无 I/O 的 O(1) 操作。
    /// 受检计算 `current_len + max_additional_bytes`。
    ///
    /// 溢出时返回 [`ReadGrowthLimitError`]。
    pub fn checked_final_len(&self, current_len: usize) -> RawResult<usize, ReadGrowthLimitError> {
        current_len.checked_add(self.max_additional_bytes).ok_or(
            ReadGrowthLimitError::FinalLengthOverflow {
                current_len,
                max_additional_bytes: self.max_additional_bytes,
            },
        )
    }
}

impl Clone for ReadGrowthLimit {
    // 显式复制同一个数值上限。
    //
    // 克隆不共享可变状态、不分配且不扩大任何已经开始的读取权限;具体读取
    // 仍须分别绑定自身操作开始时的真实长度。
    fn clone(&self) -> Self {
        Self::new(self.max_additional_bytes)
    }
}

impl fmt::Debug for ReadGrowthLimit {
    // 以包含字段名称的开发者格式显示增长上限,不访问任何外部状态。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("ReadGrowthLimit")
            .field("max_additional_bytes", &self.max_additional_bytes)
            .finish()
    }
}

impl fmt::Display for ReadGrowthLimit {
    // 以面向日志和诊断的字节数量格式显示增长上限。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "maximum additional bytes: {}",
            self.max_additional_bytes
        )
    }
}

impl PartialEq for ReadGrowthLimit {
    // 按最大新增字节数判等,不读取承载体或后端状态。
    fn eq(&self, other: &Self) -> bool {
        self.max_additional_bytes == other.max_additional_bytes
    }
}

impl Eq for ReadGrowthLimit {}

impl PartialOrd for ReadGrowthLimit {
    // 按最大新增字节数提供与 [`Ord`] 一致的全序比较。
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for ReadGrowthLimit {
    // 按最大新增字节数升序比较;数值更大只表示权限上限更高。
    fn cmp(&self, other: &Self) -> Ordering {
        self.max_additional_bytes
            .cmp(&other.max_additional_bytes)
    }
}

impl Hash for ReadGrowthLimit {
    // 将最大新增字节数写入哈希器,不包含任何动态承载体状态。
    fn hash<H>(&self, state: &mut H)
    where
        H: Hasher,
    {
        self.max_additional_bytes.hash(state);
    }
}

// 一次增长型读取成功停止时的原因与已提交进度。
//
// # 作用
//
// 本类型只描述一次成功调用为什么停止,以及该调用向承载体逻辑尾部提交了
// 多少个已经初始化的字节。它不拥有或借用承载体,不包含操作开始前已有的
// 内容,也不表示物理容量、文件总长度、下一次读取位置或资源版本。
//
// `appended_bytes` 始终是本次调用实际提交的连续新增前缀。它必须小于或等于
// 调用时的 [`ReadGrowthLimit::max_additional_bytes`]。分配器额外取得但没有
// 初始化和提交的备用容量不能计入这个数值。
//
// # 成功与失败边界
//
// 两个变体都表示调用按照公共合同成功结束。后端错误以及已经提交部分字节
// 后发生的失败都不能伪装成本类型,而是只返回 [`pi_result::Error`];取消则
// 因 Future 被丢弃而没有返回值。读取失败不返回承载体、类型化进度或恢复
// 令牌。调用方在借用结束后仍持有原承载体,并可自行记录调用前长度、再与
// 当前 [`AsRef::<[u8]>::as_ref`] 长度比较,但公共错误值不重复携带该信息。
// 已经安全提交的连续后缀可以保留,旧前缀必须不变,未初始化字节绝不能因
// 错误或取消进入公开有效视图。
//
// # 文件结束与上限的区别
//
// 如果实现尚未耗尽调用方上限便明确观察到文件结束,则返回
// [`Self::EndOfFile`]。如果新增量恰好达到上限,则返回
// [`Self::LimitReached`],并且不得仅为判断后面是否还有数据而额外读取一个
// 字节。因此 `LimitReached` 既不证明后面还有数据,也不证明当前位置就是
// EOF;调用方可以用新的显式上限继续读取。
//
// 正数短读本身不是文件结束。实现必须在剩余上限内继续读取,直到明确观察
// 到 EOF、达到上限或发生错误。后端若在非空请求上返回无法解释为 EOF 的
// 零进度,则 adapter 必须按其协议错误处理,不能形成无界忙循环。
//
// # 零字节边界
//
// 零增长上限必须零 I/O 地返回
// `LimitReached { appended_bytes: 0 }`,因为实现没有观察数据源。上限大于零
// 且第一次有效读取便明确观察到 EOF 时,返回
// `EndOfFile { appended_bytes: 0 }`。
#[non_exhaustive]
/// 一次增长型读取成功结束的原因和已新增字节数。
///
/// `LimitReached` 不表示文件一定还有更多字节;为避免越过调用方上限,达到
/// 上限后不会额外探测文件尾。零上限返回 `LimitReached { appended_bytes: 0 }`。
pub enum ReadGrowthOutcome {
    // 在尚未耗尽本次增长上限时明确观察到文件结束。
    /// 在耗尽增长上限前明确观察到文件尾。
    EndOfFile {
        // 本次调用在观察到 EOF 前成功追加并提交的有效字节数。
        /// 本次成功新增并提交的字节数。
        appended_bytes: usize,
    },

    // 本次调用的最大新增量已经用尽,EOF 是否同时成立仍然未知。
    /// 已精确用尽本次最大新增量,文件尾状态未知。
    LimitReached {
        // 本次调用成功追加并提交的有效字节数;必须等于调用上限。
        /// 本次成功新增并提交的字节数,必须等于调用上限。
        appended_bytes: usize,
    },
}

impl ReadGrowthOutcome {
    // 返回本次调用成功追加并提交的有效字节数。
    //
    // 该数值不包含承载体旧内容、未提交的备用容量或失败操作的进度。方法只
    // 读取枚举字段,不分配、不加锁、不执行 I/O,并计划为 O(1)。
    #[must_use]
    /// 返回本次成功新增并提交的字节数。
    pub fn appended_bytes(&self) -> usize {
        match self {
            Self::EndOfFile { appended_bytes }
            | Self::LimitReached { appended_bytes } => *appended_bytes,
        }
    }

    // 判断调用是否在耗尽上限前明确观察到文件结束。
    //
    // `true` 只对应 [`Self::EndOfFile`];它不承诺文件在之后仍保持相同长度,
    // 因为其它协作者可以在快照之后追加或替换资源。方法无外部副作用且计划
    // 为 O(1)。
    #[must_use]
    /// 仅当调用明确在上限内观察到文件尾时返回 `true`。
    pub fn is_end_of_file(&self) -> bool {
        matches!(self, Self::EndOfFile { .. })
    }

    // 判断调用是否因为最大新增量已经用尽而停止。
    //
    // `true` 只对应 [`Self::LimitReached`],但不能据此断言文件后面存在或不
    // 存在数据。该判断不触发额外 EOF 探测,无 I/O 且计划为 O(1)。
    #[must_use]
    /// 仅当调用精确用尽增长上限时返回 `true`。
    pub fn is_limit_reached(&self) -> bool {
        matches!(self, Self::LimitReached { .. })
    }
}

impl Clone for ReadGrowthOutcome {
    // 显式复制成功终止分类和数值进度,不复制任何承载体内容。
    fn clone(&self) -> Self {
        match self {
            Self::EndOfFile { appended_bytes } => Self::EndOfFile {
                appended_bytes: *appended_bytes,
            },
            Self::LimitReached { appended_bytes } => Self::LimitReached {
                appended_bytes: *appended_bytes,
            },
        }
    }
}

impl fmt::Debug for ReadGrowthOutcome {
    // 以包含变体名称和字段名称的开发者格式显示成功结果。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::EndOfFile { appended_bytes } => formatter
                .debug_struct("EndOfFile")
                .field("appended_bytes", appended_bytes)
                .finish(),
            Self::LimitReached { appended_bytes } => formatter
                .debug_struct("LimitReached")
                .field("appended_bytes", appended_bytes)
                .finish(),
        }
    }
}

impl fmt::Display for ReadGrowthOutcome {
    // 以面向日志的终止原因与已追加字节数显示成功结果。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::EndOfFile { appended_bytes } => write!(
                formatter,
                "end of file after appending {appended_bytes} bytes"
            ),
            Self::LimitReached { appended_bytes } => write!(
                formatter,
                "growth limit reached after appending {appended_bytes} bytes"
            ),
        }
    }
}

impl PartialEq for ReadGrowthOutcome {
    // 按终止原因和已追加字节数判等。
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (
                Self::EndOfFile {
                    appended_bytes: left,
                },
                Self::EndOfFile {
                    appended_bytes: right,
                },
            )
            | (
                Self::LimitReached {
                    appended_bytes: left,
                },
                Self::LimitReached {
                    appended_bytes: right,
                },
            ) => left == right,
            _ => false,
        }
    }
}

impl Eq for ReadGrowthOutcome {}

impl PartialOrd for ReadGrowthOutcome {
    // 返回与 [`Ord`] 一致的全序比较结果。
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for ReadGrowthOutcome {
    // 先按终止原因、再按已追加字节数升序比较。
    //
    // 冻结顺序为 `EndOfFile < LimitReached`。该顺序只服务确定性集合、排序和
    // 诊断,不表示成功等级、时间先后或是否应该继续读取。
    fn cmp(&self, other: &Self) -> Ordering {
        match (self, other) {
            (
                Self::EndOfFile {
                    appended_bytes: left,
                },
                Self::EndOfFile {
                    appended_bytes: right,
                },
            )
            | (
                Self::LimitReached {
                    appended_bytes: left,
                },
                Self::LimitReached {
                    appended_bytes: right,
                },
            ) => left.cmp(right),
            (Self::EndOfFile { .. }, Self::LimitReached { .. }) => Ordering::Less,
            (Self::LimitReached { .. }, Self::EndOfFile { .. }) => Ordering::Greater,
        }
    }
}

impl Hash for ReadGrowthOutcome {
    // 将终止原因与已追加字节数共同写入哈希器。
    fn hash<H>(&self, state: &mut H)
    where
        H: Hasher,
    {
        match self {
            Self::EndOfFile { appended_bytes } => {
                0_u8.hash(state);
                appended_bytes.hash(state);
            }
            Self::LimitReached { appended_bytes } => {
                1_u8.hash(state);
                appended_bytes.hash(state);
            }
        }
    }
}