Skip to content

erofs: unbounded dentry cache grows to 2.3GB on 1.5M files #13195

@ildar-safarov

Description

@ildar-safarov

Description

The erofs filesystem never evicts cached dentries, leading to unbounded memory growth in the sentry.

The dentry comment in pkg/sentry/fsimpl/erofs/erofs.go documents this:

TODO: This can lead to unbounded memory growth in sentry due to the ever-growing dentry tree. We should have a dentry LRU cache, similar to what fsimpl/gofer does.

Have a fix ready - LRU dentry cache.

Steps to reproduce

Create an EROFS image with 1.5M files and walk it inside a gVisor sandbox. Capture a heap profile before and after the walk.

#!/bin/bash
set -eu

WORKDIR=$(mktemp -d)
trap 'rm -rf "$WORKDIR"' EXIT

echo "Creating 1.5M files..."
python3 -c "
import os
for i in range(100):
    for j in range(100):
        d = os.path.join('$WORKDIR/root', f't_{i:03d}', f's_{j:03d}')
        os.makedirs(d, exist_ok=True)
        for k in range(150):
            open(os.path.join(d, f'f_{k:03d}.txt'), 'wb').write(b'data')
    if (i + 1) % 10 == 0:
        print(f'  {(i+1)*100*150} files created')
"

mkfs.erofs "$WORKDIR/img.erofs" "$WORKDIR/root"
rm -rf "$WORKDIR/root"

BUNDLE="$WORKDIR/bundle"
mkdir -p "$BUNDLE"
CID="erofs-test-$$"

cat > "$BUNDLE/config.json" <<EOF
{
  "ociVersion": "1.2.0",
  "process": {
    "terminal": false,
    "user": { "uid": 0, "gid": 0 },
    "cwd": "/",
    "args": ["/usr/bin/sleep", "infinity"],
    "env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]
  },
  "root": { "path": "/" }
}
EOF

RUNSC="${RUNSC_PATH:-runsc}"
sudo "$RUNSC" --profile --network=none --overlay2=root:memory \
    create --bundle "$BUNDLE" "$CID"
sudo "$RUNSC" start "$CID"
sleep 2

sudo "$RUNSC" debug -profile-heap "$WORKDIR/before.pprof" -delay 2s "$CID"

sudo "$RUNSC" exec "$CID" mkdir -p /mnt
sudo "$RUNSC" debug --mount "erofs:$WORKDIR/img.erofs:/mnt" "$CID"
sudo "$RUNSC" exec "$CID" python3 -c "
import os
count = 0
for dp, _, fns in os.walk('/mnt'):
    for f in fns:
        os.stat(os.path.join(dp, f))
        count += 1
print(f'walked {count} files')
"

sudo "$RUNSC" debug -profile-heap "$WORKDIR/after.pprof" -delay 2s "$CID"

sudo "$RUNSC" kill "$CID" KILL; sleep 1
sudo "$RUNSC" delete -force "$CID"

echo "=== Before walk ==="
go tool pprof -text -inuse_space "$WORKDIR/before.pprof" 2>&1 | head -3
echo "=== After walk ==="
go tool pprof -text -inuse_space "$WORKDIR/after.pprof" 2>&1 | head -3

Expected: Heap stays bounded after walking 1.5M files.

Actual:

=== Before walk ===
Type: inuse_space
Time: 2026-05-15 20:09:14 UTC
Showing nodes accounting for 4128.45kB, 100% of 4128.45kB total
=== After walk ===
Type: inuse_space
Time: 2026-05-15 20:09:34 UTC
Showing nodes accounting for 2353.31MB, 99.79% of 2358.36MB total

runsc version

runsc version release-20260420.0-134-g7e7d35b8a0ae
spec: 1.2.1

docker version (if using docker)

uname

Linux XXX 6.8.0-1048-gcp #51~22.04.1-Ubuntu SMP Wed Feb 11 02:38:51 UTC 2026 aarch64 aarch64 aarch64 GNU/Linux

kubectl (if using Kubernetes)

repo state (if built from source)

release-20260511.0-26-g7e7d35b8a

runsc debug logs (if available)

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions