Search before asking
What happened + What you expected to happen
In our searcher workload, we create multiple random_coro_file instances for the same physical file to perform concurrent random reads across different executors or I/O contexts.
Currently, every path-based random_coro_file opens the file independently and consumes one file descriptor. Therefore, FD usage grows approximately as:
number of files × random_coro_file instances per file
For example:
10 files × 32 random_coro_file instances per file = 320 file descriptors
A searcher pod may contain many files and concurrent readers. The resulting FD usage can exceed the pod's ulimit -n, causing open() to fail with EMFILE (Too many open files). This may also prevent the process from opening unrelated files or sockets.
I expect random_coro_file to support multiple instances sharing one existing file descriptor, especially for positioned operations such as async_read_at.
The desired behavior is:
- Open each physical file only once.
- Allow multiple
random_coro_file instances to use the same fd concurrently.
- Support non-owning fd semantics so destroying one wrapper does not close the shared fd.
- Keep the fd valid until all wrappers and in-flight operations have completed.
- Preserve the existing path-based API and behavior.
With this model, FD usage becomes proportional to the number of physical files instead of the number of random_coro_file instances.
Reproduction way
Create multiple random_coro_file instances for the same file:
std::vector<std::unique_ptr<coro_io::random_coro_file>> files;
for (std::size_t i = 0; i < concurrency; ++i) {
files.emplace_back(std::make_unique<coro_io::random_coro_file>(
"/path/to/data", std::ios::in, executor));
}
Keep the process running and inspect its descriptors:
When concurrency is 320, approximately 320 descriptors refer to the same file.
In a deployment with a sufficiently low FD limit, increasing the number of files or readers eventually causes:
open: Too many open files
Expected result after adding fd sharing:
10 physical files × 32 wrappers per file
Current: 320 file descriptors
Expected: 10 file descriptors
All wrappers should still be able to perform concurrent async_read_at operations correctly.
Anything else
A possible solution is to add constructors that accept an existing fd and explicitly define its ownership:
random_coro_file(
int fd,
asio::io_context::executor_type executor,
std::string_view file_path = "",
bool own_fd = true);
When own_fd is false:
- The wrapper should use the supplied fd without calling
open() or dup().
close() and the destructor should detach or release the native handle without closing it.
- The caller is responsible for keeping the fd alive until all wrappers and in-flight I/O operations have completed.
The implementation should cover both the thread-pool pread/pwrite backend and the native asynchronous backend where supported.
I have implemented a prototype and added ownership, lifecycle, concurrency, and native-handle tests.
I also ran a multi-file benchmark with 10 files, 32 wrappers per file, 320 concurrent 4 KiB direct random-read coroutines, and the Linux io_uring backend.
| Mode |
FDs per file |
Total file FDs |
Median IOPS (32 I/O threads) |
| Shared fd |
1 |
10 |
3,653,516 |
| Duplicated fd |
33 |
330 |
3,669,355 |
| Path-based open |
32 |
320 |
3,686,683 |
The maximum throughput difference was about 0.9%, while shared-fd mode reduced the file descriptor count from 320 to 10. All benchmark runs completed with zero I/O errors.
Are you willing to submit a PR?
Search before asking
What happened + What you expected to happen
In our searcher workload, we create multiple
random_coro_fileinstances for the same physical file to perform concurrent random reads across different executors or I/O contexts.Currently, every path-based
random_coro_fileopens the file independently and consumes one file descriptor. Therefore, FD usage grows approximately as:For example:
A searcher pod may contain many files and concurrent readers. The resulting FD usage can exceed the pod's
ulimit -n, causingopen()to fail withEMFILE(Too many open files). This may also prevent the process from opening unrelated files or sockets.I expect
random_coro_fileto support multiple instances sharing one existing file descriptor, especially for positioned operations such asasync_read_at.The desired behavior is:
random_coro_fileinstances to use the same fd concurrently.With this model, FD usage becomes proportional to the number of physical files instead of the number of
random_coro_fileinstances.Reproduction way
Create multiple
random_coro_fileinstances for the same file:Keep the process running and inspect its descriptors:
When
concurrencyis 320, approximately 320 descriptors refer to the same file.In a deployment with a sufficiently low FD limit, increasing the number of files or readers eventually causes:
Expected result after adding fd sharing:
All wrappers should still be able to perform concurrent
async_read_atoperations correctly.Anything else
A possible solution is to add constructors that accept an existing fd and explicitly define its ownership:
When
own_fdisfalse:open()ordup().close()and the destructor should detach or release the native handle without closing it.The implementation should cover both the thread-pool
pread/pwritebackend and the native asynchronous backend where supported.I have implemented a prototype and added ownership, lifecycle, concurrency, and native-handle tests.
I also ran a multi-file benchmark with 10 files, 32 wrappers per file, 320 concurrent 4 KiB direct random-read coroutines, and the Linux io_uring backend.
The maximum throughput difference was about 0.9%, while shared-fd mode reduced the file descriptor count from 320 to 10. All benchmark runs completed with zero I/O errors.
Are you willing to submit a PR?