|
9 | 9 | #include <folly/ExceptionWrapper.h> |
10 | 10 | #include <folly/FBVector.h> |
11 | 11 | #include <folly/MapUtil.h> |
| 12 | +#include <folly/coro/Collect.h> |
| 13 | +#include <folly/coro/safe/NowTask.h> |
12 | 14 | #include <folly/functional/Invoke.h> |
13 | 15 | #include <folly/futures/Future.h> |
14 | 16 |
|
@@ -190,4 +192,82 @@ auto applyToVirtualInode( |
190 | 192 | }); |
191 | 193 | } |
192 | 194 |
|
| 195 | +/** |
| 196 | + * Coroutine-native version of applyToVirtualInode. Uses the same |
| 197 | + * VirtualInodeLoader for efficient tree-shaped path resolution, but applies |
| 198 | + * func via co_await instead of deferValue/collectAll. func may return a |
| 199 | + * SemiFuture<T> or now_task<T> — anything co_awaitable. |
| 200 | + * |
| 201 | + * Both inode resolution and func application are parallelized: resolution |
| 202 | + * via the loader's tree-shaped plan, func via collectAllRange. |
| 203 | + */ |
| 204 | +template <typename Func> |
| 205 | +folly::coro::now_task< |
| 206 | + std::vector<folly::Try<typename folly::isFutureOrSemiFuture< |
| 207 | + folly::invoke_result_t<Func&, VirtualInode, RelativePath>>::Inner>>> |
| 208 | +co_applyToVirtualInode( |
| 209 | + InodePtr rootInode, |
| 210 | + const std::vector<std::string>& paths, |
| 211 | + Func func, |
| 212 | + const std::shared_ptr<ObjectStore>& store, |
| 213 | + const ObjectFetchContextPtr& fetchContext) { |
| 214 | + using FuncRet = folly::invoke_result_t<Func&, VirtualInode, RelativePath>; |
| 215 | + using Result = typename folly::isFutureOrSemiFuture<FuncRet>::Inner; |
| 216 | + |
| 217 | + detail::VirtualInodeLoader loader; |
| 218 | + |
| 219 | + // Func may not be copyable, so wrap it in a shared_ptr. |
| 220 | + auto cb = std::make_shared<Func>(std::move(func)); |
| 221 | + |
| 222 | + // Set up load futures for each path. If path parsing fails, store a failed |
| 223 | + // SemiFuture so the error appears in the corresponding result entry. |
| 224 | + std::vector<folly::SemiFuture<VirtualInode>> loadFutures; |
| 225 | + std::vector<RelativePath> relPaths; |
| 226 | + loadFutures.reserve(paths.size()); |
| 227 | + relPaths.reserve(paths.size()); |
| 228 | + for (const auto& path : paths) { |
| 229 | + try { |
| 230 | + auto relPath = RelativePathPiece{path}; |
| 231 | + loadFutures.push_back(loader.load(relPath)); |
| 232 | + relPaths.push_back(relPath.copy()); |
| 233 | + } catch (const std::exception&) { |
| 234 | + loadFutures.push_back( |
| 235 | + folly::makeSemiFuture<VirtualInode>( |
| 236 | + folly::exception_wrapper(std::current_exception()))); |
| 237 | + relPaths.emplace_back(); |
| 238 | + } |
| 239 | + } |
| 240 | + |
| 241 | + // Resolve all inodes via the loader's tree-shaped plan. |
| 242 | + co_await loader |
| 243 | + .loaded( |
| 244 | + folly::Try<VirtualInode>(VirtualInode{std::move(rootInode)}), |
| 245 | + RelativePath(), |
| 246 | + store, |
| 247 | + fetchContext) |
| 248 | + .semi(); |
| 249 | + |
| 250 | + // Apply func to each resolved inode in parallel. |
| 251 | + std::vector<folly::coro::Task<folly::Try<Result>>> tasks; |
| 252 | + tasks.reserve(loadFutures.size()); |
| 253 | + for (size_t i = 0; i < loadFutures.size(); ++i) { |
| 254 | + tasks.push_back( |
| 255 | + folly::coro::co_invoke( |
| 256 | + [cb, |
| 257 | + loadFuture = std::move(loadFutures[i]), |
| 258 | + path = std::move(relPaths[i])]() mutable |
| 259 | + -> folly::coro::Task<folly::Try<Result>> { |
| 260 | + auto inodeTry = |
| 261 | + co_await folly::coro::co_awaitTry(std::move(loadFuture)); |
| 262 | + if (inodeTry.hasException()) { |
| 263 | + co_return folly::Try<Result>(inodeTry.exception()); |
| 264 | + } |
| 265 | + co_return co_await folly::coro::co_awaitTry( |
| 266 | + (*cb)(std::move(inodeTry.value()), std::move(path))); |
| 267 | + })); |
| 268 | + } |
| 269 | + |
| 270 | + co_return co_await folly::coro::collectAllRange(std::move(tasks)); |
| 271 | +} |
| 272 | + |
193 | 273 | } // namespace facebook::eden |
0 commit comments