folly coro blockingWait·collectAll — 동기 경계와 fan-in
한 줄 요약:
blockingWait는 코루틴 세계와 동기 세계의 유일한 합법적 경계다.collectAll/collectAllRange는 여러 Task를 fan-out + join 한다. 둘을 잘못 섞으면 deadlock이 정해진 일.
#동기 — 왜 별도 도구가 필요한가
코루틴은 비동기 합성에 강하다. 그러나 두 경계가 있다.
- main() 진입점 —
main은 코루틴이 아니다. 어딘가에서 sync로 결과를 기다려야 한다. - legacy 동기 API와의 통합 —
std::vector<int> foo()처럼 sync 시그니처가 코루틴 결과를 호출하려면.
blockingWait가 이 두 자리를 채운다. 한편 production async의 진짜 가치는 병렬 합성에서 나온다. collectAll/collectAllRange가 그 도구.
#blockingWait
#include <folly/coro/BlockingWait.h>#include <folly/coro/Task.h>
folly::coro::Task<int> Compute();
int main() { folly::CPUThreadPoolExecutor pool(4); int v = folly::coro::blockingWait(Compute().scheduleOn(&pool)); std::cout << v << "\n";}blockingWait는 현재 thread를 block하고 코루틴이 완료될 때까지 기다린다. 호출 thread 자체는 다른 코루틴을 실행하지 않으므로 executor가 코루틴을 진행시킨다는 사실이 중요하다.
// Badint Caller() { return folly::coro::blockingWait(Compute()); // executor 없음 → 영원히 대기}Task를 scheduleOn(executor)로 묶지 않으면 lazy start가 풀리지 않는다. blocking thread 혼자서는 코루틴을 깨우지 못한다.
#Deadlock 피하기
// Deadlock 1: 단일 thread pool + blockingWait가 그 pool에서 호출folly::CPUThreadPoolExecutor pool(1);folly::coro::Task<int> Inner() { co_return 1; }
folly::coro::Task<int> Outer() { // Outer가 pool의 유일한 thread에서 실행 중 return folly::coro::blockingWait(Inner().scheduleOn(&pool)); // ↑ Inner도 같은 pool인데 thread 1개라 schedule 불가}규칙: blockingWait는 코루틴/executor 외부에서만 안전하다. main(), test fixture, sync API boundary.
// OKint main() { folly::CPUThreadPoolExecutor pool(4); folly::coro::blockingWait(MyApp().scheduleOn(&pool));}
// Riskfolly::coro::Task<int> Library() { return folly::coro::blockingWait(Helper()); // pool 고갈 시 deadlock}#collectAll — 정해진 수의 fan-out
#include <folly/coro/Collect.h>
folly::coro::Task<std::tuple<int, std::string, double>> ParallelFetch() { auto [a, b, c] = co_await folly::coro::collectAll( FetchInt(), FetchString(), FetchDouble()); co_return std::make_tuple(a, b, c);}collectAll(t1, t2, t3, ...)은 모든 Task를 동시에 시작하고 전부 완료되면 결과 tuple로 반환한다. 어느 하나라도 실패하면 첫 예외가 throw, 나머지는 폐기.
#collectAllRange — 동적 수
folly::coro::Task<std::vector<int>> ProcessAll(std::vector<int> ids) { std::vector<folly::coro::Task<int>> tasks; for (int id : ids) tasks.push_back(ProcessOne(id)); co_return co_await folly::coro::collectAllRange(std::move(tasks));}collectAllRange는 range를 받아 std::vector<T>를 반환한다. tuple destructuring이 불가능한 경우(런타임 결정 크기).
#collectAllTry
auto results = co_await folly::coro::collectAllTry(t1, t2, t3);// results: tuple<Try<R1>, Try<R2>, Try<R3>>for (auto& r : { results.t0, results.t1, results.t2 }) { if (r.hasException()) { ... }}collectAll이 fail-fast인 반면 collectAllTry는 모든 Task가 완료될 때까지 기다리고 각각의 결과를 Try<T>로 반환. 부분 실패를 허용하는 fan-in.
#collectAny
auto idx = co_await folly::coro::collectAny(a, b, c);// 가장 먼저 끝난 Task의 인덱스race 패턴. 가장 빨리 끝나는 결과만 채택, 나머지는 cancellation 신호로 정리(awaitable이 cancel-aware라면).
#내부 — 단순한 합성
// 약식template <class... Tasks>folly::coro::Task<std::tuple<typename Tasks::result_t...>>collectAll(Tasks... tasks) { // 1. 각 Task를 SemiFuture로 변환 auto futures = std::tuple(std::move(tasks).start()...);
// 2. SemiFuture::collect에 위임 auto results = co_await folly::collectAll(std::move(futures));
// 3. tuple로 묶어 반환 co_return std::move(results);}내부적으로 코루틴 Task들이 SemiFuture<T>로 변환되고 기존 folly::collectAll Future 합성에 위임된다. Futures 인프라 위에 코루틴 API를 얹은 형태. 이런 재사용이 Meta가 빠르게 production-ready로 만든 비결.
#awaitable 합성 패턴
folly::coro::Task<Result> FetchWithFallback(Key k) { auto primary = FetchPrimary(k); auto secondary = FetchSecondary(k);
// 5초 timeout — 둘 다 안 끝나면 fallback auto result = co_await folly::coro::co_awaitTry( folly::coro::timeout( folly::coro::collectAny(std::move(primary), std::move(secondary)), std::chrono::seconds{5}));
if (result.hasException()) { co_return co_await FetchFromCache(k); } co_return /* ... */;}timeout, co_awaitTry, collectAny가 자유롭게 조합된다. 같은 logic을 callback 또는 future chain으로 짜면 3배 길이.
#std와의 비교
| 항목 | 표준 | folly::coro |
|---|---|---|
| sync wait | 없음 (std::future::wait만) | blockingWait |
| fan-in | 없음 | collectAll/collectAllRange |
| fail-fast | N/A | collectAll |
| collect-all-results | N/A | collectAllTry |
| first-wins | N/A | collectAny |
| timeout | N/A | coro::timeout |
| cancellation | N/A | 자동 전파 |
표준은 단일 코루틴까지만. 합성/대기는 라이브러리.
#코드 리뷰 포인트
blockingWait가 라이브러리 함수 안에 있다 → 거의 항상 잘못된 위치.collectAll을 single-thread executor에서 호출 → 직렬화. parallel 의도라면 multi-thread executor 필요.collectAll후 결과 tuple destructuring을 안 하고 통째로 보유 → 큰 결과는 메모리.- timeout과 cancellation의 조합이 누락 → 외부 요청이 영원히 hang 가능.
#자주 보는 안티패턴
// 1. blockingWait 안에서 같은 pool의 Task 기다림folly::coro::Task<int> Helper(folly::Executor* e) { return co_await folly::coro::blockingWait(Inner().scheduleOn(e)); // 같은 e면 deadlock}
// 2. range의 Task를 전부 .scheduleOn(같은 pool) 후 collectAllstd::vector<folly::coro::Task<int>> ts;for (int i = 0; i < 1000; ++i) ts.push_back(Compute(i).scheduleOn(&pool));co_await folly::coro::collectAllRange(std::move(ts));// pool 크기보다 한참 많으면 starvation
// 3. collectAny 사용 후 패자 Task 폐기 - cancellation aware하지 않으면 leakauto idx = co_await folly::coro::collectAny(slowQuery1, slowQuery2);// slow 측이 cancel을 무시하면 background에서 계속 실행#정리
blockingWait는 main()과 sync boundary 전용 — 라이브러리 안에서 쓰지 말 것.collectAll/collectAllRange로 정해진/동적 수의 Task fan-out.collectAllTry로 부분 실패 허용,collectAny로 race.- 내부적으로
folly::collectAllFuture 합성 재사용 — Futures 인프라 위의 어댑터. - timeout/cancellation과 조합해 production-grade 패턴 완성.
#다음 편
Part 15-05: coro::Baton / Mutex에서 코루틴-aware 동기화 원시를 본다.
#관련 항목
- Folly Part 15-02 — Task
- Folly Part 2-05 — collect — Future 버전 collectAll
- Folly Part 9-03 — Baton — non-coro Baton과 비교
- 원문 — folly/coro/Collect.h
Folly Code Review · 68 of 89
- 1 Folly Code Review — Meta의 production-grade C++ 라이브러리 코드 분석
- 2 Folly 개요 — Meta가 production에서 검증한 utility 모음 분석
- 3 Folly vs Abseil 철학 비교 — performance-first vs std-compatible
- 4 Folly 빌드와 fbcode 환경 — monorepo의 그림자
- 5 Folly API stability 정책 — 어떤 보장도 없다는 솔직함
- 6 Folly production validation 문화 — peta-scale에서 단련된 코드
- 7 folly::Future 분석 — std::future의 한계를 넘는 composable async
- 8 folly::Promise·makeFuture — Future를 만드는 두 길
- 9 folly::SemiFuture vs Future — executor binding의 명시화
- 10 folly::Future thenValue·thenError·thenTry — continuation 체인 분석
- 11 folly::collect·collectAll·collectAny — fan-in 패턴 분석
- 12 folly::Future retry·window·via — 제어 흐름 조합자
- 13 folly::fibers 분석 — M:N stackful coroutine
- 14 folly::InlineExecutor — 호출자 thread에서 즉시 실행
- 15 folly::CPUThreadPoolExecutor — CPU-bound 작업의 표준 thread pool
- 16 folly::IOThreadPoolExecutor — libevent 기반 I/O pool
- 17 folly::ManualExecutor — 결정적 테스트를 위한 수동 진행
- 18 folly::EventBase 분석 — libevent 이벤트 루프의 핵심
- 19 folly::IOBuf 분석 — zero-copy buffer chain의 기본 단위
- 20 folly::IOBufQueue — chain의 push/pull 추상화
- 21 folly::io::Cursor·RWCursor — chain 위의 stream
- 22 folly Zero-copy 패턴 — IOBuf로 ScatterGather I/O 표현
- 23 folly::IOBuf shared semantics — clone·unshare·takeOwnership
- 24 folly::FBString 분석 — SSO + COW 구현
- 25 folly의 fmt::format 통합 — 모던 포맷팅 채택
- 26 folly::StringPiece — string_view 호환 분석
- 27 folly Join·Split utilities — 문자열 분해와 결합
- 28 folly::to·tryTo — text↔num 변환 분석
- 29 folly Conv Customization — 사용자 타입 지원
- 30 folly Conv 성능 비교 — sprintf·stringstream 대비
- 31 folly::F14ValueMap vs std::unordered_map
- 32 folly::F14NodeMap — stable pointer가 필요할 때
- 33 folly::F14VectorMap — cache-friendly iteration
- 34 folly::F14FastMap — auto-select 동작
- 35 folly F14 internals — SIMD probing 메커니즘
- 36 folly::small_vector — inline storage 분석
- 37 folly::FixedString — compile-time string
- 38 folly::AtomicHashMap — lock-free read 분석
- 39 folly::ConcurrentHashMap — sharded 동시 해시 맵
- 40 folly::EvictingCacheMap — LRU 구현 분석
- 41 folly::Synchronized — lock wrapper 패턴
- 42 folly::SharedMutex 분석
- 43 folly::Baton — one-shot wait 동기화
- 44 folly::RWSpinLock 분석
- 45 folly::PicoSpinLock — 1-byte spinlock
- 46 folly::ProducerConsumerQueue — SPSC 큐 분석
- 47 folly::MPMCQueue — multi-producer multi-consumer
- 48 folly::UnboundedQueue — 동적 크기 lock-free
- 49 folly::fibers::Channel — Go-like channel
- 50 folly::dynamic — JSON-like dynamic type 분석
- 51 folly JSON conversion — toJson·parseJson
- 52 folly dynamic ↔ struct — manual marshaling
- 53 folly dynamic Visitor pattern — type별 분기
- 54 folly::Singleton vs Meyers/static — 왜 Folly의 Singleton인가
- 55 folly::SingletonVault 분석 — 등록·소멸·의존성
- 56 folly::Singleton try_get·try_get_fast — TLS-cached 접근
- 57 folly::ExceptionWrapper — type-erased exception holder
- 58 folly::ScopeGuard·SCOPE_EXIT — RAII cleanup
- 59 folly::Optional vs std::optional
- 60 folly::Function vs std::function
- 61 folly::Lazy — 지연 초기화 wrapper
- 62 folly Meta 스타일 code review 패턴
- 63 folly anti-patterns — 잘못 쓰면 std보다 느림
- 64 folly vs std 선택 기준 분석
- 65 folly::coro 개요 — production C++20 코루틴 어댑터
- 66 folly::coro::Task — lazy single-shot 코루틴
- 67 folly::coro::AsyncGenerator — 비동기 스트림
- 68 folly coro blockingWait·collectAll — 동기 경계와 fan-in
- 69 folly::coro::Baton·Mutex — 코루틴-aware 동기화
- 70 folly::Expected — 결과 또는 오류
- 71 folly::Try — Future 결과 wrapper
- 72 folly::Try vs Expected 선택 기준
- 73 folly::Range — 일반 iterator pair
- 74 folly::Uri — URL 파서
- 75 folly Fingerprint64·128 — 분산 hash
- 76 folly SpookyHashV2 — fast non-crypto hash
- 77 folly::Init — main() 부트스트랩
- 78 folly::Indestructible — global lifetime 패턴
- 79 folly::MicroLock — 1-byte 락
- 80 folly::MicroSpinLock — 가장 좁은 spin lock
- 81 folly::format — legacy formatter 분석
- 82 folly::demangle — typeid 디망글링
- 83 folly::DynamicConverter — dynamic ↔ struct
- 84 folly::RecordIO — append-only 로그 파일 포맷
- 85 folly::io::Compression — zstd·lz4·snappy wrapper
- 86 folly::AsyncIO — io_uring·Linux AIO
- 87 folly::CancellationToken — 코루틴·Future 취소 전파
- 88 folly::observer — hot config의 atomic refresh
- 89 fbcode 패턴 모음 — folly 사용의 실전
관련 글
folly::CancellationToken — 코루틴·Future 취소 전파
CancellationSource/Token의 전파 모델 — coroutine·Future·callback 트리에서 협력적 취소.
같은 시리즈에서 이어 읽기
folly::coro::Baton·Mutex — 코루틴-aware 동기화
coro::Baton과 coro::Mutex — thread를 block하지 않고 코루틴만 suspend하는 동기화 프리미티브.
같은 시리즈에서 이어 읽기
folly::coro::AsyncGenerator — 비동기 스트림
AsyncGenerator<T>의 pull-based 모델, co_yield, for co_await — 비동기 iterator의 표준 후보 패턴.
같은 시리즈에서 이어 읽기