folly::coro::AsyncGenerator — 비동기 스트림
한 줄 요약:
AsyncGenerator<T>는co_yield로 값을 한 번에 하나씩 비동기로 내보내고 소비자는for co_await로 받는다. backpressure가 자연스럽고 cancellation도 협력적으로 동작한다.
#동기
비동기 스트림을 표현하는 방법은 셋이다.
- callback —
OnNext,OnComplete,OnError콜백 3종. RxCpp, Folly Observable 패턴. 가독성과 backpressure가 약점. - push channel —
Channel<T>::send(...)/recv(). Go의 channel, fibers::Channel. backpressure는 buffer size로 조절. - pull generator —
co_yield/for co_await. 소비자가 원할 때만 값을 당겨온다.
AsyncGenerator<T>는 셋째다. pull-based라 자동으로 backpressure가 생긴다. 소비자가 느리면 producer의 co_yield가 suspend 상태로 머문다.
folly::coro::AsyncGenerator<int> Range(int n) { for (int i = 0; i < n; ++i) { co_await folly::coro::sleep(std::chrono::milliseconds(10)); co_yield i; }}
folly::coro::Task<int> Sum(int n) { int total = 0; auto gen = Range(n); while (auto v = co_await gen.next()) { total += *v; } co_return total;}co_yield i는 i를 소비자에게 넘기고 generator는 suspend 한다. 소비자가 다음 next()를 부르면 generator가 resume.
#API
#include <folly/coro/AsyncGenerator.h>
folly::coro::AsyncGenerator<int> Source();
folly::coro::Task<void> Consume() { auto gen = Source();
// 방법 1: next() — Optional 반환 while (auto opt = co_await gen.next()) { process(*opt); }
// 방법 2: for co_await (C++23 range-based) // CO_FOREACH macro, 또는 직접 풀어쓴 loop}next()는 folly::coro::AsyncGenerator<T>::NextResult를 반환한다. 이것이 bool 변환 가능(값이 있으면 true). end-of-stream이면 비어 있다.
#Reference vs Value yield
// Value generator — co_yield가 복사/이동folly::coro::AsyncGenerator<std::string> Lines(std::istream& in) { std::string line; while (std::getline(in, line)) { co_yield line; }}
// Reference generator — co_yield가 참조 전달, 큰 객체 효율folly::coro::AsyncGenerator<const LargeMessage&> Messages(Stream& s) { for (;;) { auto msg = co_await s.next(); if (!msg) break; co_yield *msg; }}reference variant는 소비자가 다음 co_await 전까지만 reference가 유효하다는 계약이다. consume-or-copy 패턴.
#for co_await
folly::coro::Task<void> Print(folly::coro::AsyncGenerator<int> gen) { while (auto v = co_await gen.next()) { std::cout << *v << "\n"; }}표준에 for co_await가 도입되면 이렇게 쓸 수 있다.
folly::coro::Task<void> Print(folly::coro::AsyncGenerator<int> gen) { CO_FOREACH (int v, gen) { // folly macro std::cout << v << "\n"; }}현재 folly가 제공하는 CO_FOREACH macro가 for co_await를 흉내낸다. 표준이 따라잡으면 사라질 헬퍼.
#내부 구조
// folly/coro/AsyncGenerator.h 약식template <class Ref, class Value = std::remove_cvref_t<Ref>>class AsyncGenerator { public: class promise_type { public: AsyncGenerator get_return_object() noexcept; std::suspend_always initial_suspend() noexcept; // lazy auto final_suspend() noexcept; // notify consumer auto yield_value(Ref v) noexcept; // co_yield void return_void() noexcept; void unhandled_exception() noexcept;
private: Ref* current_ = nullptr; std::coroutine_handle<> consumer_; folly::exception_wrapper exception_; };
class NextAwaitable { auto await_suspend(std::coroutine_handle<> h) noexcept; auto await_resume(); };
NextAwaitable next() noexcept;};핵심은 두 코루틴(producer/consumer)이 서로 resume하는 패턴이다.
- consumer가
co_await gen.next()호출. await_suspend가 producer handle을 반환 → symmetric transfer로 producer resume.- producer가
co_yield v→ 값을current_에 저장, consumer handle 반환 → consumer resume. - 반복.
end-of-stream은 producer가 return_void에 도달하면 final_suspend에서 consumer를 resume + 빈 NextResult.
#합성 — chain / transform
template <class T, class F>folly::coro::AsyncGenerator<std::invoke_result_t<F, T>>transform(folly::coro::AsyncGenerator<T> src, F f) { while (auto v = co_await src.next()) { co_yield f(std::move(*v)); }}
folly::coro::Task<void> Pipeline() { auto evens = transform(Range(10), [](int x) { return x * 2; }); while (auto v = co_await evens.next()) { std::cout << *v << "\n"; }}함수형 stream 조합이 일반 함수 호출로 표현된다. RxCpp의 operator 카탈로그 같은 별도 DSL이 필요 없다.
#Cancellation 전파
folly::coro::Task<void> ConsumeWithTimeout( folly::coro::AsyncGenerator<int> gen) { auto src = folly::CancellationSource{}; auto token = src.getToken();
auto consumeTask = [&]() -> folly::coro::Task<void> { while (auto v = co_await gen.next()) { process(*v); } }();
// 5초 뒤 cancel std::thread([s = src]() mutable { std::this_thread::sleep_for(std::chrono::seconds(5)); s.requestCancellation(); }).detach();
co_await folly::coro::co_withCancellation(token, std::move(consumeTask));}cancellation은 producer 코루틴의 co_await suspend point에서 trigger된다. producer가 awaitable이 cancel-aware라면 OperationCancelled 예외로 깨어난다.
#Push channel과의 비교
| 항목 | AsyncGenerator | Channel |
|---|---|---|
| 방향 | pull (consumer가 당김) | push (producer가 보냄) |
| backpressure | 자동 (suspend) | buffer size로 조절 |
| multi-consumer | 안 됨 (single owner) | 가능 |
| multi-producer | 안 됨 | 가능 |
| 합성 | 함수 합성 | 별도 connect/wire |
단일 producer ↔ 단일 consumer 모델이라면 AsyncGenerator가 단순. 팬아웃/팬인이 필요하면 Channel.
#std와의 비교
| 항목 | std::generator (C++23) | folly::coro::AsyncGenerator |
|---|---|---|
| 동기/비동기 | sync only | async (suspend 가능) |
| co_await | 안 됨 | yield 사이에 가능 |
| reference yield | 지원 | 지원 |
| for-range | for (auto v : gen) | CO_FOREACH 또는 next() |
| 표준화 | C++23 | 비표준 |
std::generator가 sync only라는 점이 결정적 차이. 비동기 stream은 표준에 아직 자리가 없다.
#코드 리뷰 포인트
- generator를 두 번 iterate 시도 — single-shot이라 두 번째는 즉시 종료.
- reference yield인데 소비자가 다음
next()후에 reference를 잡고 있음 — UAF. - producer 코루틴이 cancel-aware하지 않은 외부 작업(blocking I/O)을 await — cancellation이 안 통함.
- generator를 lifetime이 짧은 stack-allocated 객체로 보유 → 코루틴 frame이 살아있는 동안 generator도 살아야 함.
#자주 보는 안티패턴
// 1. reference yield 결과를 collection에 보관std::vector<const LargeMessage*> all;while (auto v = co_await gen.next()) { all.push_back(&*v); // 다음 iter에서 dangling}
// 2. generator를 다중 consumer에게 공유auto gen = Source();std::thread t1([&] { folly::coro::blockingWait(consume(gen)); });std::thread t2([&] { folly::coro::blockingWait(consume(gen)); }); // race
// 3. eager 평가 시도auto all = collectAll(gen); // AsyncGenerator는 collect 직접 안 됨#정리
AsyncGenerator<T>는 pull-based 비동기 stream이다.co_yield로 값을 내보내고co_await gen.next()로 받는다.- producer/consumer는 symmetric transfer로 서로 resume.
- reference yield로 큰 객체를 복사 없이 전달 가능, 단 consume-or-copy 계약.
std::generator(sync)와는 다른 도메인 — async stream은 표준에 아직 없다.
#다음 편
Part 15-04: blockingWait / collectAll에서 sync 경계와 fan-in을 본다.
#관련 항목
Folly Code Review · 67 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 blockingWait·collectAll — 동기 경계와 fan-in
blockingWait, collectAll, collectAllRange — sync 경계 연결과 병렬 합성, deadlock 회피 규칙.
같은 시리즈에서 이어 읽기