Handler nhận request, bắn ba job:
void audit(); void notify(); await save();
rồi return 200. Process restart: audit/notify dở dang, lỗi nuốt,
metric “success” trong khi side-effect không xong.
Đó là
async mồ côi — không cha, không cancel, không
join.
Structured concurrency: mọi task con nằm trong scope (nursery). Scope chỉ kết thúc khi con xong — hoặc bị cancel theo cây khi cha hủy / lỗi policy.
Vấn đề fire-and-forget
// JS — “xong” với caller, chưa xong với thế giới
async function handle(req) {
void writeAudit(req) // lỗi? ai bắt?
void sendEmail(req.user) // inventory ẩn
return { ok: true }
}
// Client disconnect: hai task vẫn chạy (orphan)
// xem cancellation + inflight-inventory
- Lỗi con không bubble → fail im.
- In-flight không đếm → OOM “không hiểu”.
- Cancel request không lan → việc mồ côi.
Scope = cha
// Ý structured (pseudo)
await withScope(async (scope) => {
scope.fork(() => writeAudit(req))
scope.fork(() => sendEmail(req.user))
await save(req)
}) // đợi / cancel cả fork; policy lỗi rõ ràng
Kotlin coroutineScope, Java structured concurrency
API, Trio nursery, Swift task group — cùng ý: cây task, không
“đẻ rơi”.
Policy khi con fail
- Cancel siblings + escalate: một fail → hủy anh em, scope throw (all-or-fail).
- Isolate: log lỗi con, cha vẫn chạy (cần nói to — giống best-effort ignore).
Promise.all gần all-or-fail nhưng không cancel sibling
mặc định (trừ AbortSignal tự truyền). Structured = cancel + lifetime
gắn scope, không chỉ “đợi cả đám”.
Với series
- Cancel theo cây, không chỉ một fetch.
- Budget gắn scope cha — hết giờ hủy cả nhóm.
- Không thay idempotency khi job bị cancel giữa chừng rồi redelivery.
Checklist
- Request path có
void/ fire-and-forget không? - Disconnect/budget có hủy hết con không?
- Lỗi con có metric + policy không?
- Shutdown process: join hay kill mồ côi?
Một câu
Task có cha có mẹ — đừng đẻ async mồ côi.
Scope = lifetime + cancel + chỗ bắt lỗi, không chỉ
Promise.all.