SequentialSubscription: các flow theo thread
Cơ sở: apple/servicetalk tại e1704cb (code gốc) và PR 3677 tại 506ce73. Mở từng flow, bấm vào một mũi tên hoặc một thread để xem ghi chú và phạm vi ảnh hưởng. Bốn mục dưới đây cho cùng nội dung ở dạng bảng.
Quy ước tên thread
| Tên | Ý nghĩa |
|---|---|
D | Thread của downstream, gọi request(n) và cancel(). |
W | Thread đang chạy switchTo và giao demand cho nguồn mới (thread "switch"). |
X | Thread thứ hai gọi switchTo (resubscribe bất đồng bộ, ví dụ timer của repeatWhen). |
E | Thread đang ở trong callback request() của một nguồn phát đồng bộ. |
M | Một thread duy nhất làm mọi việc (trường hợp cơ bản). |
9 flow (mở bằng trình duyệt)
sequenceđo + đọc code 02 Cuộc đua mất demand (code gốc)Hai thread cùng switch, nhánh SWITCHING không CAS.
sequenceđo: 121 trên 200.000 03 Cùng chồng lấn với PR 3677Mailbox và drain: X để lại việc, W giao demand đúng một lần.
sequenceđo: 0 trên 1.000.000 04 Cancel re-entrant (cùng thread)Cancel ngay trong onNext: code gốc, PR, và gợi ý comment 1.
sequenceđo: 1 so với 1.000.000 item 05 Cancel từ thread khácD cancel khi E đang phát đồng bộ trong request().
sequenceđo: 1.000 so với 1.000.000 06 switchTo trong lúc request() thườngDemand cho nguồn B chạy trên X (gốc) hay trên E (PR).
sequenceđo 07 switchMap: ai gọi switchToThread inner mới hoặc thread inner cũ sau khi onNext xong.
sequenceđọc code 08 Máy trạng thái sourceRequestedidle, SWITCHING, REQUESTED, CANCELLED và đường dẫn tới mất demand.
lifecycleđọc code 09 Luồng drain của PR 3677Lock, mailbox, vòng lặp và vị trí 3 comment.
workflowđọc code
Ma trận kịch bản thread (đã chạy thật)
Mỗi kịch bản dùng latch (không dựa vào timing) và ghi lại thread nào chạy lời gọi xuống nguồn. Ba cột: code gốc, PR 3677, PR 3677 cộng các gợi ý của maintainer (comment 1, 2, 3). Ô màu cam là chỗ khác code gốc.
| # | Kịch bản | Code gốc | PR 3677 | PR cộng gợi ý |
|---|---|---|---|---|
| R1 | request từ D khi callback switch đang chạy trên W | D trả về ngay; demand +5 do W giao sau | giống | giống |
| R2 | cancel từ D khi callback switch đang chạy trên W | D trả về ngay; cancel tới nguồn sau, trên W | giống | giống |
| R3 | cancel từ D khi callback request thường đang chạy trên E | cancel tới nguồn ngay, trên D | hoãn; cancel tới nguồn trên E sau khi callback xong | giống PR |
| R4 | switchTo từ X khi callback request thường đang chạy trên E | nguồn B nhận demand ngay, trên X | B nhận demand trên E sau khi callback xong; X trả về ngay | giống PR |
| R5 | switchTo từ X khi callback switch đang chạy trên W | X trả về ngay; W giao demand cho B | giống | giống |
| R6 | request(1) re-entrant từ onNext, cùng thread | nguồn nhận 6 lời gọi, lồng sâu 2 | 2 lời gọi, sâu 1 (gộp), cùng tổng demand | giống PR |
| R7 | switchTo re-entrant trong callback request | B nhận 1 trên thread M | giống | giống |
| R8 | cancel sau switchTo chồng lấn | chỉ B bị cancel (A: 0, B: 1) | cả A và B bị cancel (A: 1, B: 1) | chỉ B (như code gốc) |
| R9 | cancel re-entrant trong onNext (đo riêng) | tới nguồn ngay; nguồn phát 1 item | hoãn; nguồn phát 1.000.000 item | tới nguồn ngay; 1 item, cancel đúng 1 lần |
| R10 | cancel từ thread khác khi nguồn phát đồng bộ (đo riêng) | dừng ở 1.000 item | phát đủ 1.000.000; chặn 2 giây | vẫn như PR (gợi ý chỉ phủ cùng thread) |
Kết luận từ ma trận: PR thay đổi bốn thứ. (a) Thread chạy lời gọi xuống nguồn: demand cho nguồn mới chạy trên thread đang giữ lock (R4). (b) Cancel bị hoãn đến khi callback xong (R3, R9, R10). (c) Request re-entrant được gộp, ít lồng hơn (R6). (d) Nguồn cũ cũng bị cancel (R8). Các gợi ý của maintainer khôi phục (d) và cancel re-entrant (R9), nhưng không khôi phục cancel từ thread khác (R10) và việc dịch chuyển thread (R4). Cả hai kịch bản R9 và R10 được đo bằng test riêng trên cùng ba bản code.
Danh mục trường hợp thread
| # | Trường hợp | Bằng chứng |
|---|---|---|
| T1 | Một thread, nguồn đồng bộ, switchTo re-entrant trong callback | Đã đo (R7) và có test trong repo |
| T2 | request re-entrant trong onNext | Đã đo (R6) |
| T3 | cancel re-entrant trong onNext | Đã đo (R9) |
| T4 | request từ D khi callback switch chạy trên W | Đã đo (R1) |
| T5 | cancel từ D khi callback switch chạy trên W | Đã đo (R2) |
| T6 | switchTo từ X khi callback switch chạy trên W, gồm cả phần đuôi chưa thoát | Đã đo (R5); cuộc đua ở phần đuôi: 121 trên 200.000 trên code gốc, hang tái hiện 2 trên 2 |
| T7 | cancel từ D khi callback request thường chạy trên E | Đã đo (R3, R10) |
| T8 | switchTo từ X khi callback request thường chạy trên E | Đã đo (R4) |
| T9 | Nguồn nào bị cancel sau switchTo chồng lấn | Đã đo (R8) |
| T10 | Hai thread D1 và D2 cùng request | Chưa đo. Đọc code: code gốc có thể gọi request xuống nguồn đồng thời; PR tuần tự hóa qua lock |
| T11 | itemReceived (thread phát) so với switchTo: khả năng nhìn thấy sourceEmitted | Chưa đo. Dựa vào quan hệ happens-before của tín hiệu tuần tự; PR công bố qua getAndSet volatile |
| T12 | request hoặc cancel ném exception trong callback | Chưa đo ở đây. PR drain tiếp rồi ném lại; test của PR: requestThrowsAfterSwitchStillDrainsPendingDemand |
| T13 | switchMap: cancel đồng thời từ D và từ thread phát ở nhánh lỗi | Chưa đo. Đọc code PublisherSwitchMap |
Mỗi operator: thread nào gọi gì
| Operator | request / cancel | itemReceived | switchTo chạy trên | Nguồn kế tiếp bắt đầu từ |
|---|---|---|---|---|
ConcatPublisher | D | thread phát của nguồn hiện tại | thread onSubscribe của nguồn kế (cùng thread với onComplete nếu đồng bộ) | onComplete của nguồn trước |
OnErrorResumePublisher | D | như trên | thread onSubscribe của nguồn thay thế (chỉ một lần) | onError của nguồn trước |
RedoPublisher (repeat, retry) | D; cancel còn được gọi trên thread phát khi onNext ném lỗi | thread phát | thread của terminal nếu nguồn đồng bộ | terminal của nguồn trước, resubscribe ngay |
RedoWhenPublisher (repeatWhen, retryWhen) | D; cancel còn hủy cả decider | thread phát | thread hoàn tất của decider (timer, executor, event loop) | decider hoàn tất |
PublisherSwitchMap | D; cancel còn được gọi trên thread phát ở nhánh lỗi | thread phát của inner hiện tại | thread onSubscribe của inner mới, hoặc thread phát của inner cũ sau khi onNext xong | outer onNext |
Giới hạn
- Mọi số đo chạy local trên macOS (JDK 25, arm64), một máy; chưa có số liệu trên x86 Linux hoặc JDK khác cho phần ma trận.
- Ma trận dùng nguồn giả lập; các trường hợp ghi "đọc code" chưa được chạy thử.
- Chưa gắn được một lần CI fail cụ thể vào cuộc đua ở flow 02 (log CI không có thread dump).
- Các flow được xem trong khung 800 px của trình duyệt nhúng, chưa ở kích thước đầy đủ; chưa xuất ảnh chụp.
Nguồn gốc của các file
Các file 01 đến 09 do Archify 3.0.1 (commit 7f483b6, MIT) sinh từ JSON trong thư mục candidates/, chạy bằng finalize --quality showcase: cả 4 cổng (validate, deliver, check, browser-check) đều qua. Kiểm tra cập nhật của Archify đã tắt (ARCHIFY_UPDATE_CHECK_DISABLED=1) nên không có kết nối mạng. Tiêu đề, nhãn và giao diện điều khiển của người xem bằng tiếng Anh là mặc định của Archify (nhãn nội dung bằng tiếng Việt). Trang chỉ mục này không do Archify sinh ra.