Disable SMB3 directory leases: they silently truncate recursive directory walks - #297
Merged
Merged
Conversation
A directory lease lets a client keep a directory handle open after the readdir completes, so that later metadata requests can be served from its cache. A recursive walk therefore accumulates handles faster than they are released, and once the session reaches real_max_open_files the server refuses further opens with NT_STATUS_INSUFFICIENT_RESOURCES. Both sides are silent about it. smbXsrv_open_create() logs nothing on that path at any debug level, and the client maps the status to EAGAIN rather than EMFILE, so a walk simply stops early and reports partial results as if they were complete. Measured over a 216K-object share: a full repair-xattrs scan covered 11799 paths before breaking, and 216233 with leases disabled. The count of open directories on the server, sampled every five minutes, stayed between 3 and 7 for the entire three-hour run instead of climbing to the ceiling. The option is new in Samba 4.22 and enabled by default on non-clustered installs, so deployments inherited it without the behaviour being chosen.
artem-from-ua
marked this pull request as ready for review
September 1, 2026 02:52
Owner
|
Fix the merge conflict and this can be checked in |
Resolve the conflict in tests/test_storage_runtime.py: keep upstream's deadtime = 720 from "Set deadtime to 12 hours" alongside this branch's assertion for "smb3 directory leases = no".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On a share holding a large, heavily branched directory tree — thousands of directories spread across a deep hierarchy — recursive walks fail with
EAGAIN/ errno 35 ("Resource temporarily unavailable"). A small share will not show this at all; it takes a tree big enough for a single traversal to accumulate thousands of open directory handles before finishing.The failure is quiet, which is what makes it dangerous.
tcapsule repair-xattrsreports a fraction of the tree as if it were the whole thing and exits successfully;xattr -lfails on arbitrary healthy files, making them look like they have corrupt metadata; any recursive traversal simply stops early.The cause is SMB3 directory leases, which Samba enables by default from 4.22 onward. Adding one line to the generated
smb.conffixes it:Environment
a109b23("Clean up mdns handling for WAN and LAN")Measured effect
Full-share
repair-xattrs --dry-run --include-hiddenover a tree of 216K objects (183K files, 33K directories, counted on the device itself, excluding the Time Machine sparsebundle):real_max_open_filessmb3 directory leases = noThroughput does not degrade. The longer wall-clock time is entirely a consequence of covering twenty times more of the tree — per path the scan is if anything marginally faster with the fix in place. The same holds on a smaller subtree measured separately: 15.2 paths/s stock against 15.6 and 16.0 on two consecutive runs with the fix. Whatever the directory cache was buying, a recursive walk was not benefiting from it, since each directory is visited once.
The working-set figure is the striking one, and it was sampled every five minutes for the duration of each run, by counting directory entries in the session
smbd's file table on the device:(on the capsule itself that is
sed -n '/drwx/p'andsed -n '$=', since it has neithergrepnorwc). Over the ~3-hour run with leases disabled, every one of those samples returned between 3 and 7, with no upward trend at any point. On the stock configuration the same counter climbs steadily until it reachesreal_max_open_filesand the walk breaks.The state also persists across walks. Running the same scan twice in a row against a directory tree with several thousand directories, without remounting in between, the second run covers dramatically less than the first — in the worst case it returns almost immediately having enumerated essentially nothing, and still exits successfully. Handles accumulated by the first traversal are still held when the second one starts, so it begins already saturated. With directory leases disabled both runs are identical.
What exactly makes a tree susceptible was not established. It reproduces reliably on a tree of a few thousand directories and not at all on one of a few dozen, but whether the deciding factor is the directory count, the nesting depth, the fan-out, or the ratio of directories to files was not tested.
Why this happens
A directory lease is the client's protocol-level permission to keep a directory handle open after the readdir completes, so that subsequent metadata requests can be served from its cache (MS-SMB2 §3.3.5.9.11). A recursive walk therefore accumulates open directory handles faster than they are released.
Samba caps concurrent handles per session at
real_max_open_files, computed insource3/smbd/files.c,file_init_global():Past that,
smbXsrv_open_create()insource3/smbd/smbXsrv_open.crefuses:On this device
max open files = 512, soreal_maxis 512 and the walk breaks once the session is holding that many open directories. The relationship was verified directly on a run configured with a higher limit: withreal_max = 980,fstat -p <pid> | grep drwxshowed 981 open directories at the moment of failure — the arithmetic matches exactly.The failure is silent on both sides. On the server, this path logs nothing at any debug level — the familiar
"Too many open files, unable to open more!"message lives on theEMFILEpath inopen.cand is emitted atDEBUG(0), so its absence is positive evidence that the kernel never returnedEMFILE. On the client,NT_STATUS_INSUFFICIENT_RESOURCESmaps toEAGAINrather thanEMFILE(nt2errno[]in Apple'skernel/netsmb/smb_subr.c), which is what makes errno 35 the fingerprint of this specific counter.Raising the limit does not fix it. It moves the failure point further out — a run with
max open filesraised to 980 got roughly five times as far before breaking — but the client keeps accumulating regardless of how many handles are available, so the walk still truncates.Why the fix belongs on the server
smb3 directory leasesis a global option introduced in Samba 4.22 and enabled by default on non-clustered installs (MR 3842, 4.22.0 release notes). This build is 4.24.3, so it is active.Setting it to
notakes effect at NEGOTIATE —source3/smbd/smb2_negprot.c:The capability simply never appears in the NEGOTIATE response, so no client can request a directory lease in the first place. Confirmed on the live mount:
smbutil statshares -areportsFILE_LEASING_SUPPORTED TRUEand noDIR_LEASING_SUPPORTEDline at all once the option is set.There is a second line of defence on CREATE (
source3/smbd/open.c), which quietly ignores a lease request rather than failing it:Being a server-side setting, it covers every client at once — including the Time Machine and Spotlight mounts, which mount the same share separately under
/Volumes/.timemachine/and would otherwise need to be handled individually.Known side effects
Two things change beyond leases themselves, and both should be recorded rather than discovered later.
strict renamestops being forced on. Enabling directory leases implicitly enablesstrict rename; disabling them returns it to its own default ofno. Nothing stops working as a result — the parameter only controls whether renaming a directory that has open files below it is refused (Windows behaviour,yes) or allowed (POSIX behaviour,no, and Samba's default for many years). The change makes that operation more permissive, not less. It does not apply to a macOS client at all, since the documentation states renames are always allowed once the client requests UNIX extensions.nois also what Samba recommends, becauseyesforces a scan of the entire open-handle database on every directory rename — a real cost on a 128 MB device.Directory metadata caching goes away — but the measured cost is nil.
The concern was that a directory lease is what lets a client answer repeated metadata requests for the same directory locally, so Finder-style browsing (returning to a directory already visited) might get slower. Measured on a 116-entry directory, three consecutive passes in each configuration:
ls -l, leases onls -l, leases offstatall entries, leases onstatall entries, leases offThe warm passes are indistinguishable — 887/866 ms without leases against 969/855 ms with them, and the
statfigures are flat in both. Repeated access is still served from cache after the change, because the client's own directory cache is a separate mechanism that this option does not touch. (The cold first pass differs, but a single sample either way is not evidence of anything.)The test also confirms the option takes effect as described:
smbutil statshares -areportedDIR_LEASING_SUPPORTED TRUEin the stock configuration and omitted the line entirely once leases were disabled.Happy to test further
This reproduces on my share every single time — with leases enabled a traversal has never once completed, and with them disabled it has never once failed. Nothing about it is intermittent or load-dependent, so it is cheap to test against.
If it would help, I can run the same measurements against other builds, other Samba settings, or other client configurations, and report coverage against a known object count along with the server-side working set. Say which combinations are of interest.