Reported in #743: with `parallel_relay > 1`, a single POST (e.g. submitting a comment) reached the destination as N concurrent requests, so the comment got posted twice. Root cause is unfixable from the Rust side: `select_ok` cancels only OUR futures, but Apps Script has no way to learn the cancellation, so every fan-out call still runs to completion and each `UrlFetchApp.fetch()` still hits the destination. Fan-out now only triggers for idempotent methods (GET / HEAD / OPTIONS); POST / PUT / PATCH / DELETE always go sequential. Same pattern as `SAFE_REPLAY_METHODS` in Code.gs `_doBatch` fallback — safe methods are idempotent so re-firing is at worst wasteful, unsafe methods can have side effects so re-firing is incorrect. New regression test locks down `is_method_safe_for_fanout` predicate. Tests: 180 lib + 35 tunnel-node green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2.2 KiB
• Fix parallel_relay causing duplicate POSTs (#743): وقتی parallel_relay > 1 تنظیم بود، تکPOST کاربر (مثل ارسال کامنت در GitHub) بهعنوان دو/چند درخواست همزمان به سایت مقصد میرسید + کامنت دو بار ثبت میشد. علت: select_ok فقط futureهای Rust سمت ما را cancel میکنه، ولی Apps Script سرورسایده هیچ خبری از cancel ندارد — هر فراخوانی fan-out روی Apps Script کامل میشه و UrlFetchApp.fetch() هر کدام به مقصد میرسه. حالا fan-out فقط برای متدهای idempotent (GET / HEAD / OPTIONS) اجرا میشه؛ POST / PUT / PATCH / DELETE همیشه sequential میرن — کاربر روی browse کاهش tail latency رو نگه میداره و روی form submit از duplicate side-effect ایمن میمونه. الگوی همان SAFE_REPLAY_METHODS که در Code.gs _doBatch fallback داریم. تست regression جدید locks down predicate. ۱۸۰ lib + ۳۵ tunnel-node test همه pass.
• Fix parallel_relay causing duplicate POSTs (#743): with parallel_relay > 1 set, a single user POST (e.g. submitting a comment on GitHub) was reaching the destination as two or more concurrent requests, so the comment got posted twice. Root cause: select_ok only cancels the loser futures on our side, but Apps Script has no way to learn about that cancellation server-side, so every fan-out call still runs to completion and each UrlFetchApp.fetch() to the destination still fires. Fan-out now only triggers for idempotent methods (GET / HEAD / OPTIONS); POST / PUT / PATCH / DELETE always go sequential — users keep the p95 tail-latency win on browsing without losing correctness on form submits. Same pattern as the SAFE_REPLAY_METHODS guard in Code.gs _doBatch fallback. New regression test locks down the predicate. 180 lib + 35 tunnel-node tests passing.