Skip to content

Commit 01c20f6

Browse files
committed
feat(virtual-fs): Add TmpFs memory tracking and limiting
Hacky implementation for tracking and limiting the memory consumption of in-memory files of the TmpFs / mem_fs. Ideally this would use a Vec with a custom allocator, but the allocator APIs are currently restricted to nightly Rust. To keep both code impact and performance impact low, a TrackedVec is added that can hold a FsMemoryLimiter, which has callbacks for growing and for shrinking memory. Without the new "tracked" feature enabled, a stub impl is added, which does not do any tracking , and hence has minimal performance impact. This should be rewritten to a sane implementaiton soon as part of a larger virtual-fs rewrite. Part of #3865
1 parent 458330e commit 01c20f6

File tree

7 files changed

+248
-14
lines changed

7 files changed

+248
-14
lines changed

lib/virtual-fs/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ webc-fs = ["webc", "anyhow"]
4242
static-fs = ["webc", "anyhow"]
4343
enable-serde = ["typetag"]
4444
no-time = []
45+
# Enables memory tracking/limiting functionality for the in-memory filesystem.
46+
tracking = []

lib/virtual-fs/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ pub mod webc_fs;
4343
#[cfg(feature = "webc-fs")]
4444
mod webc_volume_fs;
4545

46+
pub mod limiter;
47+
4648
pub use arc_box_file::*;
4749
pub use arc_file::*;
4850
pub use arc_fs::*;

lib/virtual-fs/src/limiter.rs

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
use std::sync::Arc;
2+
3+
use crate::FsError;
4+
5+
pub use self::tracked_vec::TrackedVec;
6+
7+
/// Allows tracking and limiting the memory usage of a memfs [`FileSystem`].
8+
pub trait FsMemoryLimiter: Send + Sync + std::fmt::Debug {
9+
fn on_grow(&self, grown_bytes: usize) -> std::result::Result<(), FsError>;
10+
fn on_shrink(&self, shrunk_bytes: usize);
11+
}
12+
13+
pub type DynFsMemoryLimiter = Arc<dyn FsMemoryLimiter + Send + Sync>;
14+
15+
#[cfg(feature = "tracking")]
16+
mod tracked_vec {
17+
use crate::FsError;
18+
19+
use super::DynFsMemoryLimiter;
20+
21+
#[derive(Debug, Clone)]
22+
pub struct TrackedVec {
23+
data: Vec<u8>,
24+
pub(super) limiter: Option<DynFsMemoryLimiter>,
25+
}
26+
27+
impl TrackedVec {
28+
pub fn new(limiter: Option<DynFsMemoryLimiter>) -> Self {
29+
Self {
30+
data: Vec::new(),
31+
limiter,
32+
}
33+
}
34+
35+
pub fn limiter(&self) -> Option<&DynFsMemoryLimiter> {
36+
self.limiter.as_ref()
37+
}
38+
39+
pub fn with_capacity(
40+
capacity: usize,
41+
limiter: Option<DynFsMemoryLimiter>,
42+
) -> Result<Self, FsError> {
43+
if let Some(limiter) = &limiter {
44+
limiter.on_grow(capacity)?;
45+
}
46+
Ok(Self {
47+
data: Vec::with_capacity(capacity),
48+
limiter,
49+
})
50+
}
51+
52+
pub fn clear(&mut self) {
53+
self.data.clear();
54+
}
55+
56+
pub fn append(&mut self, other: &mut Self) -> Result<(), FsError> {
57+
let old_capacity = self.data.capacity();
58+
self.data.append(&mut other.data);
59+
60+
if let Some(limiter) = &self.limiter {
61+
let new = self.data.capacity() - old_capacity;
62+
limiter.on_grow(new)?;
63+
}
64+
65+
Ok(())
66+
}
67+
68+
pub fn split_off(&mut self, at: usize) -> Result<Self, FsError> {
69+
let other = self.data.split_off(at);
70+
71+
if let Some(limiter) = &self.limiter {
72+
// NOTE: split_off leaves the original vector capacity intact, so
73+
// we can just add the new length.
74+
let new_len = other.capacity();
75+
limiter.on_grow(new_len)?;
76+
}
77+
78+
Ok(Self {
79+
data: other,
80+
limiter: self.limiter.clone(),
81+
})
82+
}
83+
84+
pub fn resize(&mut self, new_len: usize, value: u8) -> Result<(), FsError> {
85+
let old_capacity = self.data.capacity();
86+
self.data.resize(new_len, value);
87+
if let Some(limiter) = &self.limiter {
88+
let new = self.data.capacity() - old_capacity;
89+
limiter.on_grow(new)?;
90+
}
91+
Ok(())
92+
}
93+
94+
pub fn extend_from_slice(&mut self, other: &[u8]) -> Result<(), FsError> {
95+
let old_capacity = self.data.capacity();
96+
self.data.extend_from_slice(other);
97+
if let Some(limiter) = &self.limiter {
98+
let new = self.data.capacity() - old_capacity;
99+
limiter.on_grow(new)?;
100+
}
101+
Ok(())
102+
}
103+
104+
pub fn reserve_exact(&mut self, additional: usize) -> Result<(), FsError> {
105+
let old_capacity = self.data.capacity();
106+
self.data.reserve_exact(additional);
107+
if let Some(limiter) = &self.limiter {
108+
let new = self.data.capacity() - old_capacity;
109+
limiter.on_grow(new)?;
110+
}
111+
Ok(())
112+
}
113+
}
114+
115+
impl Drop for TrackedVec {
116+
fn drop(&mut self) {
117+
if let Some(limiter) = &self.limiter {
118+
limiter.on_shrink(self.data.len());
119+
}
120+
}
121+
}
122+
123+
impl std::ops::Deref for TrackedVec {
124+
type Target = [u8];
125+
126+
fn deref(&self) -> &Self::Target {
127+
&self.data
128+
}
129+
}
130+
131+
impl std::ops::DerefMut for TrackedVec {
132+
fn deref_mut(&mut self) -> &mut Self::Target {
133+
&mut self.data
134+
}
135+
}
136+
}
137+
138+
#[cfg(not(feature = "tracking"))]
139+
mod tracked_vec {
140+
use crate::FsError;
141+
142+
use super::DynFsMemoryLimiter;
143+
144+
#[derive(Debug)]
145+
pub struct TrackedVec {
146+
data: Vec<u8>,
147+
}
148+
149+
impl TrackedVec {
150+
pub fn new(_limiter: Option<DynFsMemoryLimiter>) -> Self {
151+
Self { data: Vec::new() }
152+
}
153+
154+
pub fn limiter(&self) -> Option<&DynFsMemoryLimiter> {
155+
None
156+
}
157+
158+
pub fn with_capacity(
159+
capacity: usize,
160+
_limiter: Option<DynFsMemoryLimiter>,
161+
) -> Result<Self, FsError> {
162+
Ok(Self {
163+
data: Vec::with_capacity(capacity),
164+
})
165+
}
166+
167+
pub fn clear(&mut self) {
168+
self.data.clear();
169+
}
170+
171+
pub fn append(&mut self, other: &mut Self) -> Result<(), FsError> {
172+
self.data.append(&mut other.data);
173+
Ok(())
174+
}
175+
176+
pub fn split_off(&mut self, at: usize) -> Result<Self, FsError> {
177+
let other = self.data.split_off(at);
178+
Ok(Self { data: other })
179+
}
180+
181+
pub fn resize(&mut self, new_len: usize, value: u8) -> Result<(), FsError> {
182+
self.data.resize(new_len, value);
183+
Ok(())
184+
}
185+
186+
pub fn extend_from_slice(&mut self, other: &[u8]) -> Result<(), FsError> {
187+
self.data.extend_from_slice(other);
188+
Ok(())
189+
}
190+
191+
pub fn reserve_exact(&mut self, additional: usize) -> Result<(), FsError> {
192+
self.data.reserve_exact(additional);
193+
Ok(())
194+
}
195+
}
196+
197+
impl std::ops::Deref for TrackedVec {
198+
type Target = Vec<u8>;
199+
200+
fn deref(&self) -> &Self::Target {
201+
&self.data
202+
}
203+
}
204+
205+
impl std::ops::DerefMut for TrackedVec {
206+
fn deref_mut(&mut self) -> &mut Self::Target {
207+
&mut self.data
208+
}
209+
}
210+
}

lib/virtual-fs/src/mem_fs/file.rs

+20-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use tokio::io::AsyncRead;
66
use tokio::io::{AsyncSeek, AsyncWrite};
77

88
use super::*;
9+
use crate::limiter::TrackedVec;
910
use crate::{FsError, Result, VirtualFile};
1011
use std::borrow::Cow;
1112
use std::cmp;
@@ -177,7 +178,7 @@ impl VirtualFile for FileHandle {
177178
match inode {
178179
Some(Node::File(FileNode { file, metadata, .. })) => {
179180
file.buffer
180-
.resize(new_size.try_into().map_err(|_| FsError::UnknownError)?, 0);
181+
.resize(new_size.try_into().map_err(|_| FsError::UnknownError)?, 0)?;
181182
metadata.len = new_size;
182183
}
183184
Some(Node::CustomFile(node)) => {
@@ -1228,12 +1229,14 @@ impl fmt::Debug for FileHandle {
12281229
/// represents a read/write position in the buffer.
12291230
#[derive(Debug)]
12301231
pub(super) struct File {
1231-
buffer: Vec<u8>,
1232+
buffer: TrackedVec,
12321233
}
12331234

12341235
impl File {
1235-
pub(super) fn new() -> Self {
1236-
Self { buffer: Vec::new() }
1236+
pub(super) fn new(limiter: Option<crate::limiter::DynFsMemoryLimiter>) -> Self {
1237+
Self {
1238+
buffer: TrackedVec::new(limiter),
1239+
}
12371240
}
12381241

12391242
pub(super) fn truncate(&mut self) {
@@ -1304,27 +1307,32 @@ impl File {
13041307
match *cursor {
13051308
// The cursor is at the end of the buffer: happy path!
13061309
position if position == self.buffer.len() as u64 => {
1307-
self.buffer.extend_from_slice(buf);
1310+
self.buffer.extend_from_slice(buf)?;
13081311
}
13091312

13101313
// The cursor is at the beginning of the buffer (and the
13111314
// buffer is not empty, otherwise it would have been
13121315
// caught by the previous arm): almost a happy path!
13131316
0 => {
1314-
let mut new_buffer = Vec::with_capacity(self.buffer.len() + buf.len());
1315-
new_buffer.extend_from_slice(buf);
1316-
new_buffer.append(&mut self.buffer);
1317+
// FIXME(perf,theduke): make this faster, it's horrible!
1318+
let mut new_buffer = TrackedVec::with_capacity(
1319+
self.buffer.len() + buf.len(),
1320+
self.buffer.limiter().cloned(),
1321+
)?;
1322+
new_buffer.extend_from_slice(buf)?;
1323+
new_buffer.append(&mut self.buffer)?;
13171324

13181325
self.buffer = new_buffer;
13191326
}
13201327

13211328
// The cursor is somewhere in the buffer: not the happy path.
13221329
position => {
1323-
self.buffer.reserve_exact(buf.len());
1330+
self.buffer.reserve_exact(buf.len())?;
13241331

1325-
let mut remainder = self.buffer.split_off(position as usize);
1326-
self.buffer.extend_from_slice(buf);
1327-
self.buffer.append(&mut remainder);
1332+
// FIXME(perf,theduke): make this faster, it's horrible!
1333+
let mut remainder = self.buffer.split_off(position as usize)?;
1334+
self.buffer.extend_from_slice(buf)?;
1335+
self.buffer.append(&mut remainder)?;
13281336
}
13291337
}
13301338

lib/virtual-fs/src/mem_fs/file_opener.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ impl crate::FileOpener for FileSystem {
448448
// Write lock.
449449
let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
450450

451-
let file = File::new();
451+
let file = File::new(fs.limiter.clone());
452452

453453
// Creating the file in the storage.
454454
let inode_of_file = fs.storage.vacant_entry().key();

lib/virtual-fs/src/mem_fs/filesystem.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ pub struct FileSystem {
2020
}
2121

2222
impl FileSystem {
23+
pub fn set_memory_limiter(&self, limiter: crate::limiter::DynFsMemoryLimiter) {
24+
self.inner.write().unwrap().limiter = Some(limiter);
25+
}
26+
2327
pub fn new_open_options_ext(&self) -> &FileSystem {
2428
self
2529
}
@@ -539,6 +543,7 @@ impl fmt::Debug for FileSystem {
539543
/// indexed by their respective `Inode` in a slab.
540544
pub(super) struct FileSystemInner {
541545
pub(super) storage: Slab<Node>,
546+
pub(super) limiter: Option<crate::limiter::DynFsMemoryLimiter>,
542547
}
543548

544549
#[derive(Debug)]
@@ -932,7 +937,10 @@ impl Default for FileSystemInner {
932937
},
933938
}));
934939

935-
Self { storage: slab }
940+
Self {
941+
storage: slab,
942+
limiter: None,
943+
}
936944
}
937945
}
938946

lib/virtual-fs/src/tmp_fs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ impl TmpFileSystem {
3030
Self::default()
3131
}
3232

33+
pub fn set_memory_limiter(&self, limiter: crate::limiter::DynFsMemoryLimiter) {
34+
self.fs.set_memory_limiter(limiter);
35+
}
36+
3337
pub fn new_open_options_ext(&self) -> &mem_fs::FileSystem {
3438
self.fs.new_open_options_ext()
3539
}

0 commit comments

Comments
 (0)