Skip to content

[Bug]: Directory watcher leaks descriptors when initial handlers fail #2034

Description

@stephenlclarke

I have done the following

  • I have searched the existing issues
  • I have reproduced the issue using the main branch of this project

Steps to reproduce

During testing of my Container Compose plugin, I found a deterministic descriptor leak on stock apple/container main at 48145ac7fb177d9fb14e015a0bdea4c642b36729.

  1. Create a temporary directory and a DirectoryWatcher for that path.
  2. Invoke the watcher's initial-start path with a handler that throws.
  3. Repeat the failing start 32 times, matching the public watcher's retry behaviour without waiting 32 seconds.
  4. Enumerate /dev/fd, resolve each descriptor with fcntl(F_GETPATH), and count only descriptors whose path is the watched directory.
  5. Observe exactly 32 leaked descriptors on stock main.

The public startWatching loop retries once per second while no dispatch source exists, so a persistently failing initial handler leaks one descriptor per retry.

The same audit found that _startWatching treats descriptor 0 as an open failure even though open succeeds for every non-negative descriptor. If standard input is closed and open returns 0, the watcher rejects and leaks that valid descriptor.

Problem description

DirectoryWatcher._startWatching opens the watched directory before its initial enumeration and handler call. The dispatch source that normally owns and closes the descriptor is created only after those operations succeed. If either operation throws, the function returns without transferring ownership or closing the descriptor.

The failure path should close the descriptor before throwing. Descriptor 0 should be accepted as a successful open result. A regression test should count descriptors for the exact watched path rather than relying on a process-wide threshold, which can pass despite a leak or fail because unrelated tests opened files.

This is the focused DirectoryWatcher leak described by #1773. It is independently reproducible, but it does not by itself establish the VM virtiofs behaviour reported in #1097, so the proposed fix should not close #1097.

Environment

  • OS: macOS 26.5.1 (25F80)
  • Xcode: 26.6 (17F113)
  • Swift: 6.3.3
  • Container: source main at 48145ac7fb177d9fb14e015a0bdea4c642b36729

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions