+
+ // We want to call access(), but it could block on I/O. io_uring doesn't support
+ // access(), but we can do a dummy asynchonous statx() to populate the kernel's cache,
+ // which nearly always makes the next access() instantaneous.
+
+ // See if there's already a pending stat that matches this,
+ // or is a subdirectory.
+ auto it = pending_stats.lower_bound(parent_path);
+ if (it != pending_stats.end() && it->first.size() >= parent_path.size() &&
+ it->first.compare(0, parent_path.size(), parent_path) == 0) {
+ it->second.emplace_back(PendingStat{ filename, move(cb) });
+ } else {
+ it = pending_stats.emplace(filename, vector<PendingStat>{}).first;
+ engine->submit_stat(filename, [this, it, filename{ strdup(filename) }, cb{ move(cb) }] {
+ // The stat returned, so now do the actual access() calls.
+ // All of them should be in cache, so don't fire off new statx()
+ // calls during that check.
+ check_access(filename, /*allow_async=*/false, move(cb));
+ free(filename);
+
+ // Call all others that waited for the same stat() to finish.
+ // They may fire off new stat() calls if needed.
+ vector<PendingStat> pending = move(it->second);
+ pending_stats.erase(it);
+ for (PendingStat &ps : pending) {
+ check_access(ps.filename.c_str(), /*allow_async=*/true, move(ps.cb));
+ }
+ });