Skip to content

Commit 4b59ac1

Browse files
authored
Merge pull request #436 from pythonspeed/433-unwrap-is-bad
unwrap() is bad
2 parents 26d88c5 + 0effe60 commit 4b59ac1

File tree

4 files changed

+39
-27
lines changed

4 files changed

+39
-27
lines changed

.changelog/433.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
If /proc is in unexpected format, try to keep running anyway. This can happen, for example, on very old versions of Linux.

filpreload/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ lazy_static! {
3232
if std::env::var("__FIL_DISABLE_OOM_DETECTION") == Ok("1".to_string()) {
3333
Box::new(InfiniteMemory {})
3434
} else {
35-
Box::new(RealMemoryInfo::new())
35+
Box::new(RealMemoryInfo::default())
3636
}
3737
),
3838
});

memapi/src/oom.rs

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -132,18 +132,18 @@ impl OutOfMemoryEstimator {
132132
}
133133

134134
#[cfg(target_os = "linux")]
135-
fn get_cgroup_paths<'a>(proc_cgroups: &'a str) -> Vec<&'a str> {
135+
fn get_cgroup_paths(proc_cgroups: &str) -> Option<Vec<&str>> {
136136
let mut result = vec![];
137137
for line in proc_cgroups.lines() {
138138
// TODO better error handling?
139-
let mut parts = line.splitn(3, ":");
140-
let subsystems = parts.nth(1).unwrap();
141-
if (subsystems == "") || subsystems.split(",").any(|s| s == "memory") {
142-
let cgroup_path = parts.nth(0).unwrap().strip_prefix("/").unwrap();
139+
let mut parts = line.splitn(3, ':');
140+
let subsystems = parts.nth(1)?;
141+
if subsystems.is_empty() || subsystems.split(',').any(|s| s == "memory") {
142+
let cgroup_path = parts.next()?.strip_prefix('/')?;
143143
result.push(cgroup_path);
144144
}
145145
}
146-
result
146+
Some(result)
147147
}
148148

149149
/// Real system information.
@@ -156,9 +156,9 @@ pub struct RealMemoryInfo {
156156
cgroup: Option<cgroups_rs::Cgroup>,
157157
}
158158

159-
impl RealMemoryInfo {
159+
impl Default for RealMemoryInfo {
160160
#[cfg(target_os = "linux")]
161-
pub fn new() -> Self {
161+
fn default() -> Self {
162162
let get_cgroup = || {
163163
let contents = match read_to_string("/proc/self/cgroup") {
164164
Ok(contents) => contents,
@@ -167,14 +167,14 @@ impl RealMemoryInfo {
167167
return None;
168168
}
169169
};
170-
let cgroup_paths = get_cgroup_paths(&contents);
171-
for path in cgroup_paths {
170+
let cgroup_paths = get_cgroup_paths(&contents)?;
171+
if let Some(path) = cgroup_paths.into_iter().next() {
172172
let h = cgroups_rs::hierarchies::auto();
173173
let cgroup = cgroups_rs::Cgroup::load(h, path);
174174
// Make sure memory_stat() works. Sometimes it doesn't
175175
// (https://github.com/pythonspeed/filprofiler/issues/147). If
176176
// it doesn't, this'll panic.
177-
let mem: &cgroups_rs::memory::MemController = cgroup.controller_of().unwrap();
177+
let mem: &cgroups_rs::memory::MemController = cgroup.controller_of()?;
178178
let _mem = mem.memory_stat();
179179
return Some(cgroup);
180180
}
@@ -190,18 +190,20 @@ impl RealMemoryInfo {
190190
}
191191
};
192192
Self {
193-
cgroup: cgroup,
193+
cgroup,
194194
process: psutil::process::Process::current().ok(),
195195
}
196196
}
197197

198198
#[cfg(target_os = "macos")]
199-
pub fn new() -> Self {
199+
fn default() -> Self {
200200
Self {
201201
process: psutil::process::Process::current().ok(),
202202
}
203203
}
204+
}
204205

206+
impl RealMemoryInfo {
205207
#[cfg(target_os = "linux")]
206208
pub fn get_cgroup_available_memory(&self) -> usize {
207209
let mut result = std::usize::MAX;
@@ -231,14 +233,18 @@ impl RealMemoryInfo {
231233

232234
impl MemoryInfo for RealMemoryInfo {
233235
fn total_memory(&self) -> usize {
234-
psutil::memory::virtual_memory().unwrap().total() as usize
236+
psutil::memory::virtual_memory()
237+
.map(|vm| vm.total() as usize)
238+
.unwrap_or(0)
235239
}
236240

237241
/// Return how much free memory we have, as bytes.
238242
fn get_available_memory(&self) -> usize {
239243
// This will include memory that can become available by syncing
240244
// filesystem buffers to disk, which is probably what we want.
241-
let available = psutil::memory::virtual_memory().unwrap().available() as usize;
245+
let available = psutil::memory::virtual_memory()
246+
.map(|vm| vm.available() as usize)
247+
.unwrap_or(std::usize::MAX);
242248
let cgroup_available = self.get_cgroup_available_memory();
243249
std::cmp::min(available, cgroup_available)
244250
}
@@ -261,8 +267,10 @@ impl MemoryInfo for RealMemoryInfo {
261267
eprintln!(
262268
"=fil-profile= cgroup (e.g. container) memory info: {:?}",
263269
if let Some(cgroup) = &self.cgroup {
264-
let mem: &cgroups_rs::memory::MemController = cgroup.controller_of().unwrap();
265-
Some(mem.memory_stat())
270+
cgroup
271+
.controller_of::<cgroups_rs::memory::MemController>()
272+
.as_ref()
273+
.map(|mem| mem.memory_stat())
266274
} else {
267275
None
268276
}
@@ -369,7 +377,7 @@ mod tests {
369377
proptest! {
370378
// Random allocations don't break invariants
371379
#[test]
372-
fn not_oom(allocated_sizes in prop::collection::vec(1..1000 as usize, 10..2000)) {
380+
fn not_oom(allocated_sizes in prop::collection::vec(1..1000usize, 10..2000)) {
373381
let (mut estimator, memory_info) = setup_estimator();
374382
let mut allocated = 0;
375383
for size in allocated_sizes {

tests/test_endtoend.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ def test_out_of_memory():
254254
written out.
255255
"""
256256
script = TEST_SCRIPTS / "oom.py"
257+
# Exit code 53 means out-of-memory detection was triggered
257258
output_dir = profile(script, expect_exit_code=53)
258259
time.sleep(10) # wait for child process to finish
259260
allocations = get_allocations(
@@ -285,6 +286,7 @@ def test_out_of_memory_slow_leak():
285286
written out.
286287
"""
287288
script = TEST_SCRIPTS / "oom-slow.py"
289+
# Exit code 53 means out-of-memory detection was triggered
288290
output_dir = profile(script, expect_exit_code=53)
289291
time.sleep(10) # wait for child process to finish
290292
allocations = get_allocations(
@@ -326,7 +328,7 @@ def test_out_of_memory_detection_disabled():
326328
assert False, "process succeeded?!"
327329

328330

329-
def get_systemd_run_args(available_memory):
331+
def get_systemd_run_args(memory_limit):
330332
"""
331333
Figure out if we're on system with cgroups v2, or not, and return
332334
appropriate systemd-run args.
@@ -340,7 +342,7 @@ def get_systemd_run_args(available_memory):
340342
"--gid",
341343
str(os.getegid()),
342344
"-p",
343-
f"MemoryLimit={available_memory // 4}B",
345+
f"MemoryLimit={memory_limit}B",
344346
"--scope",
345347
"--same-dir",
346348
]
@@ -364,10 +366,12 @@ def test_out_of_memory_slow_leak_cgroups():
364366
"""
365367
available_memory = psutil.virtual_memory().available
366368
script = TEST_SCRIPTS / "oom-slow.py"
369+
memory_limit = available_memory // 4
367370
output_dir = profile(
368371
script,
372+
# Exit code 53 means out-of-memory detection was triggered
369373
expect_exit_code=53,
370-
argv_prefix=get_systemd_run_args(available_memory),
374+
argv_prefix=get_systemd_run_args(memory_limit),
371375
)
372376
time.sleep(10) # wait for child process to finish
373377
allocations = get_allocations(
@@ -382,10 +386,9 @@ def test_out_of_memory_slow_leak_cgroups():
382386

383387
expected_alloc = ((str(script), "<module>", 3),)
384388

385-
# Should've allocated at least a little before running out, unless testing
386-
# environment is _really_ restricted, in which case other tests would've
387-
# failed.
388-
assert match(allocations, {expected_alloc: big}, as_mb) > 100
389+
failed_alloc_size = match(allocations, {expected_alloc: big}, lambda kb: kb * 1024)
390+
# We shouldn't trigger OOM detection too soon.
391+
assert failed_alloc_size > 0.7 * memory_limit
389392

390393

391394
def test_external_behavior():
@@ -506,7 +509,7 @@ def test_jupyter(tmpdir):
506509
continue
507510
else:
508511
actual_path = key
509-
assert actual_path != None
512+
assert actual_path is not None
510513
assert actual_path[0][0] != actual_path[1][0] # code is in different cells
511514
path2 = (
512515
(re.compile(".*ipy.*"), "__magic_run_with_fil", 2),

0 commit comments

Comments
 (0)