असिंक्रोनस Rust में cancellation को संभालना
(sunshowers.io)- असिंक्रोनस Rust वातावरण में cancellation हैंडलिंग सुविधाजनक है, लेकिन इसे गलत तरीके से संभालने पर अप्रत्याशित बग और कठिनाइयाँ पैदा हो सकती हैं
- सिंक्रोनस Rust में स्पष्ट flag checks या process termination जैसी चीज़ों की ज़रूरत होती है, लेकिन असिंक्रोनस Rust में सिर्फ future को drop करने से cancellation बहुत आसानी से हो सकता है
- cancellation safety और cancellation correctness अलग-अलग अवधारणाएँ हैं, और किसी एक future का cancellation पूरे सिस्टम में समस्या पैदा कर सकता है
- cancellation से जुड़े प्रमुख समस्या पैटर्न में Tokio mutex,
select!macro,try_join, और future के उपयोग में होने वाली गलतियाँ शामिल हैं - कोई पूर्ण समाधान नहीं है, लेकिन cancellation-safe API का उपयोग, future pinning, task separation आदि से cancellation से होने वाली समस्याएँ कम की जा सकती हैं
परिचय
- यह पोस्ट असिंक्रोनस Rust में cancellation पर RustConf 2025 प्रस्तुति की सामग्री पर आधारित है
- सामान्य Rust async code के उदाहरणों में, message receive या send loop में timeout जोड़ने पर अक्सर messages के खो जाने की समस्या सामने आती है
- Oxide Computer Company जैसे वास्तविक बड़े सिस्टमों में async Rust का उपयोग करते समय आए cancellation issues और वास्तविक bug cases पर चर्चा की गई है
- लेख तीन भागों में विभाजित है: 1) cancellation की अवधारणा, 2) cancellation analysis, 3) व्यावहारिक समाधान
- लेखक ने Rust signal handling,
cargo-nextestdevelopment आदि के माध्यम से असिंक्रोनस Rust के लाभ और कठिनाइयों का अनुभव किया है
1. cancellation क्या है?
cancellation का अर्थ
- cancellation वह स्थिति है जिसमें कोई asynchronous task शुरू की जाती है, लेकिन बीच में उसे रोक दिया जाता है
- उदाहरण: बड़े download/network request, partial file read आदि को बीच में cancel किया जा सकता है
सिंक्रोनस Rust में cancellation के तरीके
- आम तौर पर atomic flag के माध्यम से समय-समय पर cancellation की जाँच, विशेष exceptions (
panic) का उपयोग, या पूरे process को forcefully terminate करने जैसी विधियाँ मौजूद हैं - कुछ frameworks (जैसे Salsa) panic payload का उपयोग करते हैं, लेकिन यह Rust के सभी platforms पर काम नहीं करता, खासकर Wasm environment में
- सिर्फ thread को forcefully terminate करना Rust safety और mutex संरचना के कारण अनुमति नहीं है
- संक्षेप में, सिंक्रोनस Rust में कोई सार्वभौमिक और सुरक्षित cancellation protocol मौजूद नहीं है
असिंक्रोनस Rust: Future क्या है?
- Future Rust compiler द्वारा बनाई गई एक state machine है, जो memory में साधारण data के रूप में मौजूद रहती है
- इसे सिर्फ बनाने से यह execute नहीं होती; यह केवल
awaitयाpollcall होने पर आगे बढ़ती है - Rust की Future passive होती है; स्पष्ट poll/await के बिना यह कोई काम नहीं करती
- यह Go/JavaScript/C# जैसी भाषाओं से अलग है, जहाँ future बनते ही execution शुरू हो जाता है
असिंक्रोनस Rust का cancellation protocol
- Future cancellation का मतलब बस उसे
dropकर देना, याpoll/awaitको आगे न चलाना है - state machine होने के कारण Future को किसी भी समय छोड़ दिया जा सकता है
- असिंक्रोनस Rust में cancellation बहुत शक्तिशाली है और बहुत आसानी से लागू हो जाता है
- लेकिन यह बहुत ज़्यादा आसान भी है, जिससे future चुपचाप drop हो सकता है, और ownership model के अनुसार child futures भी क्रमशः cancel हो सकते हैं
- इस वजह से cancellation एक non-local घटना बन जाती है, जो पूरे call chain को प्रभावित कर सकती है
2. cancellation analysis
cancellation safety और cancellation correctness
- cancellation safety: किसी individual future का वह गुण कि उसे बिना side effects के सुरक्षित रूप से cancel किया जा सके
- उदाहरण: Tokio का sleep future cancellation-safe है
- जबकि Tokio का MPSC send, drop होने पर message loss का जोखिम रखता है, इसलिए यह cancellation-safe नहीं है
- cancellation correctness: यह पूरे सिस्टम का global property है कि cancellation की स्थिति में उसकी मूल विशेषताएँ बनी रहें
- यदि cancellation-safe न होने वाला future सिस्टम में मौजूद ही न हो, तो correctness problem नहीं होती
- समस्या तभी उत्पन्न होती है जब cancellation-safe न होने वाला future वास्तव में cancel हो जाए
- cancellation के कारण data loss, invariant violation, या cleanup छूट जाना cancellation correctness violation माना जाता है
Tokio mutex की कठिनाई
- Tokio mutex lock लेकर data को adjust करने और फिर release करने के तरीके से काम करता है
- समस्या: lock के भीतर state को अस्थायी रूप से invalid बनाना, जैसे
Option<T>कोNoneकरना, और फिरawaitके दौरान future cancel हो जाए तो data गलत state में अटक सकता है - वास्तविक production work, जैसे Oxide में sled state management, में
awaitpoint पर cancellation के कारण unstable state देखी गई है - इस तरह असिंक्रोनस code में state management के दौरान cancellation बहुत खतरनाक defect का कारण बन सकता है
cancellation होने के पैटर्न और उदाहरण
.awaitके बिना future call: Rust unused future पर warning देता है, लेकिन यदि return value केResultको_में लिया जाए तो warning नहीं मिलती; इसके लिए नए Clippy lint की ज़रूरत होती हैtry_joinजैसे try operations: एक future fail होते ही बाकी cancel हो जाते हैं, जिससे वास्तविक service shutdown logic में bugs हो सकते हैंselect!macro: कई futures को parallel चलाकर, जो complete नहीं हुए उन्हें cancel कर देता है;selectloop में data loss का जोखिम बड़ा हो सकता है- इन patterns का documentation में उल्लेख है, लेकिन व्यवहार में async cancellation कई जगहों पर implicitly हो सकता है
3. क्या किया जा सकता है?
- cancellation correctness से जुड़ी समस्याओं का कोई मौलिक और पूर्ण समाधान अभी उपलब्ध नहीं है
- फिर भी व्यवहारिक रूप से, नीचे दिए गए तरीकों से cancellation defects की संभावना कम की जा सकती है
cancellation-safe futures में पुनर्रचना
- MPSC send उदाहरण: reserve और actual send को अलग करके partial cancellation-safety हासिल की जा सकती है
- reserve operation cancel होने पर संबंधित message खोता नहीं है
- permit मिलने के बाद बिना cancellation की चिंता के send किया जा सकता है
- AsyncWrite का
write_all: पूरे buffer परwrite_allcancellation के प्रति अस्थिर है, जबकिwrite_all_bufbuffer cursor का उपयोग करके cancellation होने पर progress track कर सकता है- loop के भीतर
write_all_bufसे partial progress को सुरक्षित रूप से resume किया जा सकता है
- loop के भीतर
cancellation से बचने वाला future संचालन
- future pinning:
selectloop आदि में future को pin करके cancel होने से बचाया जा सकता है, और reference के रूप मेंpollकरके इंतज़ार किया जा सकता है- उदाहरण: reserve future को reuse करने पर reservation wait order बना रहता है
- task का उपयोग:
tokio::spawnआदि से future को task के रूप में चलाने पर, handle को drop कर देने के बाद भी task स्वयं runtime द्वारा अलग से manage होती है और forcefully cancel नहीं होती- Oxide Dropshot HTTP server आदि में प्रत्येक request को अलग task में चलाया जाता है, ताकि client connection टूटने पर भी request processing पूरी हो सके
कोई व्यवस्थित समाधान?
- फिलहाल safe Rust स्तर पर सीमाएँ हैं, लेकिन कुछ approaches पर चर्चा चल रही है
- Async drop: future cancel होने पर asynchronous cleanup code चलाने की अनुमति
- linear types:
dropके समय किसी विशेष code को अनिवार्य रूप से चलाना, या कुछ futures को non-cancellable के रूप में चिह्नित करना
- इन सभी approaches के implementation में कठिनाइयाँ मौजूद हैं
निष्कर्ष और सिफारिशें
- Future passive होती है — इस मूल गुण को स्पष्ट रूप से समझना आवश्यक है
- cancellation safety और cancellation correctness की अवधारणाओं को अच्छी तरह समझना चाहिए
- cancellation bugs के प्रमुख उदाहरणों और code patterns को पहचानकर, पहले से mitigation strategy तैयार रखनी चाहिए
- कुछ व्यावहारिक सिफारिशें:
- जहाँ संभव हो, Tokio mutex से बचें और alternatives पर विचार करें
- partial-completion API या cancellation-safe API को design/उपयोग करें
- cancellation-safe न होने वाले futures के लिए ऐसा code structure अपनाएँ जो completion को सुनिश्चित करे
- अतिरिक्त रूप से, cooperative cancellation, actor model, structured concurrency, panic safety, mutex poisoning जैसे advanced topics की भी समीक्षा करने की सिफारिश की जाती है
- संबंधित सामग्री sunshowers/cancelling-async-rust पर उपलब्ध है
पढ़ने के लिए धन्यवाद। प्रस्तुति और संदर्भ सामग्री की समीक्षा तथा feedback देने वाले Oxide सहयोगियों के प्रति आभार।
1 टिप्पणियां
Hacker News टिप्पणियाँ
इन दोनों ही मामलों में context cancel होने की वजह से काम पूरा न होना स्वाभाविक व्यवहार है। अगर काम का पूरा होना ज़रूरी है, तो उसे अलग independent task में बाँट देना चाहिए। मुझे लग रहा है शायद मैं कोई महत्वपूर्ण nuance मिस कर रहा हूँ। मेरी समझ में futures का design intent यही है कि work cancellation की वजह से गायब हो सकता है। तो फिर समस्या क्या है, यह कोई फिर से समझाए तो अच्छा होगा