प्रतिनिधि इंटरव्यू विषय

Linux इंटरव्यू: Level-Triggered और Edge-Triggered epoll में क्या अंतर है?

सामान्यकठिन
Offer.cc संपादकीय टीमप्रकाशित अपडेट किया गया

प्रश्न

एक नॉन-ब्लॉकिंग Linux TCP सर्विस epoll के साथ कनेक्शन रजिस्टर करती है। एक क्लाइंट के पास 8 KiB डेटा पढ़ने योग्य (readable) है, लेकिन हैंडलर 4 KiB के लिए एक बार recv कॉल करता है और लौट जाता है। LT काम करना जारी रखता है, जबकि ET कभी-कभी हमेशा के लिए स्टॉल (stall) हो जाता है। ऐसा क्यों होता है, और सही रीड, राइट, कॉनकरेंसी और वेरिफिकेशन डिज़ाइन क्या हैं?

प्रॉम्प्ट और यह कब लागू होता है

एक Linux TCP सर्विस अपने लिसनिंग सॉकेट और कनेक्टेड सॉकेट्स दोनों को नॉन-ब्लॉकिंग मोड पर सेट करती है और उन्हें epoll से ड्राइव करती है। एक क्लाइंट ने कनेक्शन के रिसीव बफर में 8 KiB डेटा रखा है। epoll_wait द्वारा EPOLLIN लौटाने के बाद, हैंडलर recv को एक बार कॉल करता है, 4 KiB उपयोग (consume) करता है, और वापस लौट जाता है।

डिफ़ॉल्ट लेवल-ट्रिगर्ड मोड, LT के साथ, अगला epoll_wait अक्सर कनेक्शन की दोबारा रिपोर्ट करता है। EPOLLET के साथ एज-ट्रिगर्ड मोड, ET को सक्षम करने के बाद, वही कार्यान्वयन (implementation) कभी-कभी आगे कोई पढ़ने योग्य नोटिफिकेशन प्राप्त नहीं करता है और क्लाइंट हमेशा के लिए प्रतीक्षा करता रहता है। समझाइए:

  • epoll इंटरेस्ट लिस्ट, रेडी लिस्ट और I/O रेडीनेस का क्या अर्थ है;
  • LT और ET नोटिफिकेशन अनुबंध (contracts) अलग-अलग परिणाम क्यों देते हैं;
  • ET को accept, recv, send, EAGAIN, हाफ-क्लोज़ (half-close), और त्रुटियों को कैसे संभालना चाहिए;
  • वर्कर थ्रेड्स, EPOLLONESHOT, हॉट-कनेक्शन फेयरनेस (fairness), और FD रियूज़ (reuse) जोखिम को कैसे बढ़ाते हैं;
  • कारण की पुष्टि कैसे करें और एक दोहराए जाने योग्य प्रयोग के साथ समाधान को कैसे सत्यापित करें।

8 KiB और 4 KiB मान इंटरव्यू के इनपुट हैं। वे TCP सेंड, सिस्टम कॉल या एप्लिकेशन संदेश के लिए निश्चित आकार नहीं हैं। मूल दक्षता (core competency) Linux I/O रेडीनेस कॉन्ट्रैक्ट है, जो बैकएंड, इंफ्रास्ट्रक्चर, SRE, सिस्टम और सामान्य सॉफ्टवेयर-इंजीनियरिंग भूमिकाओं पर लागू होती है, इसलिए श्रेणी general है।

इंटरव्यूअर क्या टेस्ट कर रहा है

पहला, क्या उम्मीदवार यह बता सकता है कि epoll रेडीनेस की रिपोर्ट करता है—क्या किसी प्रकार का I/O बिना ब्लॉक हुए आगे बढ़ सकता है—न कि यह कि एक पूरा अनुरोध आ गया है और न ही यह कि एसिंक्रोनस I/O पूरा हो गया है? TCP एक बाइट स्ट्रीम है, और एक recv वर्तमान में उपलब्ध किसी भी धनात्मक संख्या में बाइट्स लौटा सकता है।

दूसरा, क्या वे LT और ET को केवल एक नारे से परे समझते हैं? जब तक अनुरोधित रेडीनेस स्थिति बनी रहती है, तब तक LT रिपोर्ट करता रहता है। ET केवल इसलिए दूसरे नोटिफिकेशन का वादा नहीं करता क्योंकि स्थिति सत्य बनी हुई है। ET इवेंट के बाद, एप्लिकेशन को FD को तब तक कार्रवाई योग्य मानना चाहिए जब तक कि कोई नॉन-ब्लॉकिंग रीड या राइट EAGAIN या EWOULDBLOCK न लौटा दे। "ET केवल एक बार सूचित करता है" बहुत निरपेक्ष है और सही कार्यान्वयन नहीं देता है।

तीसरा, क्या वे रीड, राइट और लिसनिंग सॉकेट पर एक ही नियम लागू कर सकते हैं? रीड्स को EAGAIN तक ड्रेन (drain) करें; accept4 पर EAGAIN तक लूप करें; और ऐसे सॉकेट पर स्थायी रूप से EPOLLOUT की निगरानी न करें जो सामान्य रूप से लिखने योग्य (writable) है। इसकी निगरानी केवल तभी करें जब एप्लिकेशन के पास अनसेंड (unsent) बफर्ड डेटा हो।

चौथा, क्या वे समवर्ती स्थिति (concurrent state) का प्रबंधन कर सकते हैं? EPOLLONESHOT एक नोटिफिकेशन के बाद एक FD को अक्षम कर देता है। एक वर्कर को अपने स्टेट अपडेट समाप्त करने के बाद इसे EPOLL_CTL_MOD के साथ पुन: सक्रिय (rearm) करना चाहिए। बहुत जल्दी rearm करने से दो वर्कर एक ही कनेक्शन को छू सकते हैं; rearm करना भूल जाना एक स्थायी स्टॉल जैसा दिखता है।

पांचवां, क्या वे “EAGAIN तक ड्रेन करने” को इवेंट-लूप निष्पक्षता के साथ मिला सकते हैं? लगातार व्यस्त रहने वाला कनेक्शन किसी थ्रेड को बहुत लंबे समय तक व्यस्त रख सकता है। यदि निष्पक्षता लागू करने के लिए एप्लिकेशन जल्दी रुक जाता है, तो उसे ET द्वारा एक नया एज बनाने की उम्मीद करने के बजाय उस कनेक्शन को यूजर-स्पेस रनेबल कतार में बनाए रखना चाहिए।

उत्तर देने से पहले स्पष्ट करने योग्य प्रश्न

  • क्या सॉकेट वास्तव में नॉन-ब्लॉकिंग हैं? ET और ब्लॉकिंग FD के साथ, अगला रीड या राइट कई कनेक्शनों के लिए जिम्मेदार थ्रेड को ब्लॉक कर सकता है।
  • क्या स्टॉल accept, read, या write-back में होता है? एक एक्सेप्ट-लूप ड्रेन, शेष इनपुट, एक EPOLLOUT अपडेट, या एक EPOLLONESHOT रीआर्म का छूट जाना, ये सभी एक अटके हुए कनेक्शन की तरह दिख सकते हैं।
  • एप्लिकेशन प्रोटोकॉल संदेश को कैसे सीमांकित (delimit) करता है? TCP की कोई संदेश सीमाएं नहीं होती हैं। लंबाई उपसर्ग (length prefix), डेलीमीटर, HTTP पार्सर स्थिति, या कनेक्शन बंद होना यह निर्धारित करता है कि अनुरोध कब पूरा हुआ है।
  • हैंडलर कितना पढ़ता है, और यह कब लौटता है? प्रति इवेंट एक निश्चित रीड यहाँ सीधा बग है; ड्रेन लूप के अंदर महंगा व्यावसायिक कार्य निष्पक्षता की समस्या पैदा करता है।
  • क्या कोई कनेक्शन थ्रेड्स के बीच स्थानांतरित हो सकता है? इनपुट, आउटपुट, क्लोज़ और epoll_ctl के स्वामी की पहचान करें, और क्या EPOLLONESHOT सक्षम है।
  • क्या EPOLLOUT हमेशा सब्सक्राइब रहता है? सॉकेट अधिकांश समय लिखने योग्य होते हैं। एक स्थायी LT सब्सक्रिप्शन CPU-बर्निंग लूप में epoll_wait को तुरंत वापस लौटा सकता है।
  • EPOLLRDHUP, EPOLLHUP, और EPOLLERR को कैसे संभाला जाता है? HUP आने पर डेटा शेष रह सकता है, और ERR/HUP स्पष्ट सदस्यता के बिना भी रिपोर्ट किए जाते हैं।
  • क्या बंद किया गया FD नंबर जल्दी पुन: उपयोग किया जा सकता है? पूर्णांक FD को संपूर्ण कनेक्शन पहचान मानने से विलंबित इवेंट या कार्य एक नए कनेक्शन पर काम कर सकता है।

30-सेकंड का उत्तर

epoll एक इंटरेस्ट लिस्ट बनाए रखता है और रेडी लिस्ट से इवेंट्स लौटाता है। यह I/O रेडीनेस की रिपोर्ट करता है, न कि किसी पूर्ण संदेश की। जब तक स्थिति तैयार रहती है, LT रिपोर्ट करता रहता है, इसलिए केवल 4 KiB पढ़ने के बाद, शेष इनपुट सामान्य रूप से अगले wait को FD लौटाने का कारण बनता है। ET अपरिवर्तित रेडी स्थिति के लिए बार-बार रिपोर्ट का वादा नहीं करता है, इसलिए एक आंशिक रीड हमेशा के लिए ऐसे नोटिफिकेशन की प्रतीक्षा कर सकता है जो कभी नहीं आता।

सभी ET FD नॉन-ब्लॉकिंग होने चाहिए। लिसनिंग सॉकेट को accept4 से EAGAIN तक, रीड्स को recv से EAGAIN तक, और राइट्स को send से EAGAIN तक ड्रेन करें; केवल तभी EPOLLOUT की निगरानी करें जब आउटपुट बफर खाली न हो। शून्य recv का अर्थ एक व्यवस्थित पीयर राइट-हाफ क्लोज़ है, जबकि अन्य त्रुटियों को अलग से संभालने की आवश्यकता होती है। एकाधिक वर्करों के साथ, EPOLLONESHOT एक कनेक्शन को क्रमबद्ध (serialize) कर सकता है, लेकिन स्टेट अपडेट के बाद इसे MOD के साथ पुन: सक्रिय किया जाना चाहिए। निष्पक्षता बजट ठीक है, लेकिन EAGAIN से पहले रुकने के लिए एक यूजर-स्पेस रेडी कतार की आवश्यकता होती है। मैं खंडित इनपुट, आंशिक रीड्स और राइट्स, हाफ-क्लोज़ और कॉनकरेंसी के साथ समस्या को पुनरुत्पादित करूंगा, फिर यह साबित करूंगा कि प्रत्येक कनेक्शन EAGAIN तक पहुंचता है, सही ढंग से पुन: सक्रिय होता है, और बिज़ी-लूप में नहीं जाता है।”

स्टेप-बाय-स्टेप डीप डाइव

स्टेप 1: epoll रेडीनेस मॉडल स्थापित करें

epoll_create1 एक epoll इंस्टेंस बनाता है, जिसे स्वयं एक FD द्वारा संदर्भित किया जाता है। वैचारिक रूप से, इंस्टेंस स्थिति के दो सेट रखता है:

  • इंटरेस्ट लिस्ट: epoll_ctl(EPOLL_CTL_ADD/MOD/DEL) के माध्यम से पंजीकृत FD और इवेंट मास्क;
  • रेडी लिस्ट: इंटरेस्ट-लिस्ट प्रविष्टियां जिनमें वर्तमान में इवेंट उपलब्ध हैं, जिनसे epoll_wait परिणाम लौटाता है।

पढ़ने योग्य (Readable) का अर्थ है कि कोई रीड वर्तमान में बिना प्रतीक्षा किए डेटा, EOF, या त्रुटि प्राप्त कर सकता है। लिखने योग्य (Writable) का अर्थ है कि कोई राइट वर्तमान में कम से कम कुछ प्रगति कर सकता है; यह वादा नहीं करता कि पूरी प्रतिक्रिया फिट होगी। एप्लिकेशन अभी भी recv, send, या accept4 को कॉल करता है और रिटर्न मान की व्याख्या करता है। इवेंट कार्रवाई को प्रेरित करता है; सिस्टम-कॉल परिणाम स्टेट मशीन को चलाता है।

स्टेप 2: अपठित (unread) 4 KiB के साथ LT और ET की तुलना करें

डिफ़ॉल्ट LT, poll जैसा दिखता है: जब तक रिसीव बफर में डेटा रहता है, रीड रेडीनेस सही बनी रहती है और अगला epoll_wait उस FD को फिर से लौटा सकता है। इसलिए गलत "प्रति इवेंट एक रीड" कार्यान्वयन कार्यात्मक प्रतीत हो सकता है, लेकिन इसके लिए अधिक वेकअप और सिस्टम कॉल की कीमत चुकानी पड़ती है।

EPOLLET के साथ, कर्नेल रेडीनेस में बदलाव (edges) की रिपोर्ट करता है। हैंडलर द्वारा 4 KiB उपयोग करने के बाद, दूसरा 4 KiB बचा रहता है और FD अभी भी पढ़ने योग्य है; एप्लिकेशन ने इसे कभी भी "अब और डेटा नहीं है" तक आगे नहीं बढ़ाया। ET उस अपरिवर्तित स्थिति के लिए दूसरी रिपोर्ट का वादा नहीं करता है, इसलिए नए इवेंट की प्रतीक्षा अनिश्चित काल के लिए ब्लॉक हो सकती है।

विश्वसनीय नियम यह है कि ET द्वारा लौटाए गए FD को रेडी माना जाए और नॉन-ब्लॉकिंग I/O तब तक करते रहें जब तक कि यह EAGAIN या EWOULDBLOCK न लौटा दे। उन मानों का अर्थ है कि कोई भी आगे का ऑपरेशन वर्तमान में बिना ब्लॉक किए प्रगति नहीं कर सकता है। केवल तभी एप्लिकेशन नोटिफिकेशन की जिम्मेदारी epoll को वापस सौंपता है।

स्टेप 3: पढ़ने को एक स्पष्ट स्टेट मशीन बनाएं

यह C-जैसा स्केच एप्लिकेशन-विशिष्ट पार्सर, लाइफटाइम और लॉगिंग विवरण को छोड़ देता है:

c
void drain_read(Connection *conn) {
  unsigned char buf[4096];

  for (;;) {
    ssize_t n = recv(conn->fd, buf, sizeof buf, 0);
    if (n > 0) {
      append_and_parse(conn, buf, (size_t)n);
      continue;
    }
    if (n == 0) {
      conn->peer_write_closed = true;
      break;
    }
    if (errno == EINTR) {
      continue;
    }
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
      break;
    }
    close_with_error(conn, errno);
    return;
  }

  if (conn->peer_write_closed && output_is_empty(conn)) {
    close_connection(conn);
  }
}

n > 0 का केवल यह अर्थ है कि वे बाइट्स आ गए हैं; पार्सर के पास अभी भी पूरे अनुरोध की कमी हो सकती है। n == 0 स्ट्रीम सॉकेट पर एक व्यवस्थित पीयर क्लोज़ है, जब पहले से बफ़र किया गया डेटा उपयोग कर लिया जाता है। EINTR को पुनः प्रयास किया जा सकता है, EAGAIN/EWOULDBLOCK इस ड्रेन पास को पूरा करता है, और अन्य त्रुटियां क्लोज़ पाथ में प्रवेश करती हैं। एक शॉर्ट रीड संदेश के पूरा होने या खाली सॉकेट का प्रमाण नहीं है।

लिसनिंग सॉकेट उसी पैटर्न का अनुसरण करता है। रीडेबिलिटी पर, accept4 पर लूप करें, प्रत्येक नए कनेक्शन पर परमाणु रूप से (atomically) SOCK_NONBLOCK | SOCK_CLOEXEC लागू करें, जब तक कि EAGAIN न हो जाए। केवल एक कनेक्शन स्वीकार करने से ET के तहत अन्य पहले से कतारबद्ध कनेक्शन बिना किसी बाद के नोटिफिकेशन की गारंटी के छूट सकते हैं।

स्टेप 4: आउटपुट बफरिंग और EPOLLOUT को नियंत्रित करें

बफ़र किए गए आउटपुट को तुरंत भेजने का प्रयास करें। आंशिक send पर, ऑफ़सेट को आगे बढ़ाएं और जारी रखें। EINTR का पुनः प्रयास करें। EAGAIN/EWOULDBLOCK पर, शेष बाइट्स को बनाए रखें और EPOLL_CTL_MOD के माध्यम से EPOLLOUT जोड़ें। अगले राइटेबल इवेंट पर ड्रेन जारी रखें। जैसे ही आउटपुट बफर खाली हो जाए, इंटरेस्ट मास्क से EPOLLOUT हटा दें।

एक स्थायी EPOLLOUT सब्सक्रिप्शन एक अलग विफलता मोड बनाता है: सॉकेट्स अक्सर लंबी अवधि के लिए लिखने योग्य होते हैं, इसलिए LT बार-बार तुरंत लौटता है और व्यावसायिक प्रगति के बिना CPU उपयोग बढ़ जाता है। ET एप्लिकेशन आउटपुट बफ़र्स, बैकप्रेशर, अधिकतम-बफ़र सीमा, या स्लो-क्लाइंट नीति को समाप्त नहीं करता है; यह केवल नोटिफिकेशन व्यवहार को बदलता है।

स्टेप 5: हाफ-क्लोज़, HUP, ERR, और क्लोज़ ऑर्डरिंग को संभालें

EPOLLRDHUP इंगित करता है कि स्ट्रीम पीयर ने कनेक्शन या उसके राइट हाफ को बंद कर दिया है। EPOLLHUP कहता है कि पीयर ने चैनल के अपने हिस्से को बंद कर दिया है, लेकिन डेटा बिना पढ़े रह सकता है; इसे तुरंत बंद करने से यह डिस्कार्ड हो सकता है। EPOLLERR और EPOLLHUP स्पष्ट रूप से अनुरोध न किए जाने पर भी रिपोर्ट किए जाते हैं। ERR पर, एप्लिकेशन द्वारा प्रोटोकॉल नीति के अनुसार इसे रिकॉर्ड करने और बंद करने से पहले getsockopt(SO_ERROR) लंबित सॉकेट त्रुटि को पुनः प्राप्त कर सकता है।

क्लोज़ पाथ को सबसे पहले नया काम सौंपे जाने से रोकना चाहिए और यह सुनिश्चित करना चाहिए कि एसिंक्रोनस कार्य समाप्त हो चुकी पहचान (expired identity) को बनाए न रखें। अंतर्निहित ओपन फाइल विवरण को संदर्भित करने वाले अंतिम FD को बंद करने से कर्नेल पंजीकरण को हटा सकता है। यदि dup या fork उस विवरण को साझा करता है, तो एक FD को बंद करने से संबंधित इवेंट्स को तुरंत समाप्त करने की आवश्यकता नहीं होती है, इसलिए स्वामित्व प्रोटोकॉल को स्पष्ट रूप से प्रविष्टि को DEL करना चाहिए या प्रत्येक संदर्भ को बंद करना चाहिए। नियंत्रित लाइफटाइम और जेनरेशन या टोकन वाले कनेक्शन ऑब्जेक्ट का उपयोग करें ताकि त्वरित पूर्णांक-FD पुन: उपयोग पुराने कार्य को नए कनेक्शन पर पुनर्निर्देशित न कर सके।

स्टेप 6: वर्कर स्वामित्व के लिए EPOLLONESHOT का उपयोग करें

कई थ्रेड्स एक epoll इंस्टेंस पर प्रतीक्षा कर सकते हैं। ET FD के लिए, कर्नेल सामान्य रूप से एक वेटर को जगाता है जब FD तैयार हो जाता है, लेकिन अकेले वह पूरे प्रसंस्करण के दौरान कनेक्शन को सिंगल-ओनर नहीं बनाता है। कतारबद्ध कार्य और बाद की घटनाएं अभी भी समवर्ती पहुंच (concurrent access) बना सकती हैं।

EPOLLONESHOT एक इवेंट डिलीवरी के बाद FD को अक्षम कर देता है। I/O को ड्रेन करने और प्रोटोकॉल स्थिति और इंटरेस्ट मास्क को अपडेट करने के बाद, एक वर्कर जो कनेक्शन को जीवित रखता है, उसे पुन: सक्रिय करने के लिए epoll_ctl(EPOLL_CTL_MOD) को कॉल करता है। पुन: सक्रियण (Rearm) अंतिम हैंडऑफ़ कदम होना चाहिए। MOD को भूल जाना कनेक्शन को रोक देता है; इसे बहुत जल्दी करने से पुराना मालिक समाप्त होने से पहले एक नया वर्कर प्रवेश कर सकता है।

स्टेप 7: ET शुद्धता और निष्पक्षता को एक साथ बनाए रखें

EAGAIN तक ड्रेन करने से लगातार व्यस्त रहने वाला कनेक्शन किसी थ्रेड पर कब्जा कर सकता है और अन्य कनेक्शनों में देरी कर सकता है। लूप प्रति कनेक्शन बाइट, संदेश या समय बजट लागू कर सकता है। यदि वह बजट EAGAIN से पहले समाप्त हो जाता है, तो यह केवल वापस नहीं लौट सकता और कर्नेल की प्रतीक्षा नहीं कर सकता। इसे कनेक्शन को यूजर-स्पेस रेडी कतार में रनेबल के रूप में चिह्नित करना चाहिए और बाद में इसे तब तक फिर से शुरू करना चाहिए जब तक कि यह EAGAIN तक न पहुंच जाए।

कतार को डुप्लिकेट प्रविष्टियों को रोकना चाहिए। कनेक्शन क्लोज़, वर्कर ट्रांसफर और EPOLLONESHOT रीआर्म को समान स्वामित्व प्रोटोकॉल साझा करना चाहिए। यह एक हॉट FD को अन्य FD को भूखा (starve) रखने की अनुमति दिए बिना ET अनुबंध को संरक्षित करता है।

स्टेप 8: एक सत्यापन बनाएं जो छूटे हुए किनारों (lost edges) को उजागर करे

एक एकल सामान्य अनुरोध अपर्याप्त है। कम से कम निम्नलिखित को कवर करें:

  1. एक 8 KiB क्लाइंट राइट जबकि सर्वर जानबूझकर प्रति कॉल अधिकतम 4 KiB पढ़ता है, जो फिक्स से पहले के ET स्टॉल और फिक्स के बाद EAGAIN तक ड्रेन को दर्शाता है;
  2. एक एप्लिकेशन संदेश जिसे यादृच्छिक सीमाओं पर विराम के साथ कई सेंड्स में विभाजित किया गया है, यह साबित करता है कि पार्सिंग एक recv आकार से स्वतंत्र है;
  3. विवश सर्वर भेजना जो आंशिक राइट्स और EAGAIN बनाता है, यह साबित करता है कि कोई बाइट खोई नहीं है और ड्रेन के बाद EPOLLOUT हटा दिया गया है;
  4. पीयर हाफ-क्लोज़, अपठित डेटा के साथ HUP, कनेक्शन रीसेट, और EINTR;
  5. कई वर्करों के साथ बार-बार EPOLLONESHOT डिलीवरी, प्रत्येक लाइव कनेक्शन के लिए ठीक एक मालिक और एक रीआर्म साबित करना;
  6. एक लगातार भेजने वाला हॉट कनेक्शन और कई धीमे कनेक्शन, निष्पक्षता, CPU, इवेंट-लूप विलंब और रेडी-कतार लंबाई का अवलोकन करना;
  7. तेजी से कनेक्शन निर्माण और बंद करना, यह साबित करना कि विलंबित कार्य पुन: उपयोग किए गए FD नंबर को लक्षित नहीं कर सकता है।

प्रत्येक recv/send/accept4 पास के अंतिम कारण, EAGAIN काउंट्स, इंटरेस्ट-मास्क परिवर्तन, वनशॉट रीआर्म, एप्लिकेशन रेडी कतार, प्रति-कनेक्शन बजट, इवेंट-लूप विलंब और कोई प्रगति न करने वाले कनेक्शनों का निरीक्षण करें। पास होने का अर्थ है सही बाइट और प्रोटोकॉल स्थिति, कोई स्थायी स्टॉल नहीं, कोई खाली बिज़ी-लूप नहीं, कोई समवर्ती मालिक नहीं, और धीमे कनेक्शनों की कोई लंबी भुखमरी नहीं।

मजबूत नमूना उत्तर

“एक epoll इंस्टेंस एक इंटरेस्ट लिस्ट बनाए रखता है, और epoll_wait इसकी रेडी लिस्ट से इवेंट्स लौटाता है। यह I/O रेडीनेस की आपूर्ति करता है, न कि पूर्ण TCP संदेशों या एसिंक्रोनस पूर्णता की। सिस्टम-कॉल रिटर्न मान कनेक्शन स्टेट मशीन को चलाते हैं।

LT यहाँ ठीक होता हुआ प्रतीत होता है क्योंकि 4 KiB उपयोग करने के बाद, दूसरा 4 KiB शेष रहता है, इसलिए रीड रेडीनेस अभी भी बनी रहती है और अगला वेट फिर से FD की रिपोर्ट करता है। EPOLLET के साथ, FD कभी भी नॉन-रीडेबल स्थिति में वापस नहीं आया। ET ऐसी स्थिति के लिए बार-बार नोटिफिकेशन का वादा नहीं करता है जो सत्य बनी रहती है, इसलिए एक रीड के बाद प्रतीक्षा हमेशा के लिए स्टॉल हो सकती है।

मैं लिसनिंग और कनेक्टेड दोनों सॉकेट्स को नॉन-ब्लॉकिंग बनाऊंगा। एक्सेप्ट पाथ accept4 पर EAGAIN तक लूप करता है। रीड पाथ recv पर EAGAIN तक लूप करता है, सकारात्मक बाइट्स को एक वृद्धिशील पार्सर को भेजता है, शून्य को पीयर राइट-हाफ क्लोज़ मानता है, EINTR का पुनः प्रयास करता है, और अन्य त्रुटियों पर बंद हो जाता है। आउटपुट तुरंत send का प्रयास करता है और आंशिक राइट के बाद ऑफ़सेट को सुरक्षित रखता है। यह केवल डेटा लंबित होने पर EAGAIN के बाद EPOLLOUT की निगरानी करता है और ड्रेन के बाद इसे हटा देता है। HUP को बंद करने से पहले ड्रेन किया जाता है, और ERR का निदान SO_ERROR से किया जाता है।

कई वर्करों के साथ, मैं प्रति कनेक्शन एक मालिक निर्दिष्ट करता हूं और EPOLLONESHOT का उपयोग कर सकता हूं: वर्कर केवल I/O ड्रेन, स्टेट अपडेट और मास्क गणना के बाद ही MOD के साथ पुन: सक्रिय होता है। यदि कोई निष्पक्षता बजट EAGAIN से पहले काम रोकता है, तो मैं गैर-मौजूद एज की प्रतीक्षा करने के बजाय कनेक्शन को एक डिडुप्लिकेटेड यूजर-स्पेस रेडी कतार में शामिल करता हूं। कनेक्शन पहचान FD पुन: उपयोग से सुरक्षित रूप से बचने के लिए लाइफटाइम या जनरेशन स्थिति रखती है।

मैं 8 KiB राइट और 4 KiB रीड सीमा के साथ पुनरुत्पादन करूंगा, फिर आंशिक राइट्स, हाफ-क्लोज़, रीसेट, वनशॉट, एकाधिक वर्कर और एक हॉट-कनेक्शन लोड जोड़ूंगा। समाधान तब पास होता है जब प्रत्येक ET हैंडलिंग पास EAGAIN या एक स्पष्ट क्लोज़ तक पहुंचता है, सभी आउटपुट वितरित किए जाते हैं, EPOLLOUT स्पिन नहीं करता है, प्रत्येक लाइव वनशॉट कनेक्शन पुन: सक्रिय होता है, और कोई भी कनेक्शन स्थायी रूप से निष्क्रिय या भूखा नहीं रहता है।”

सामान्य गलतियाँ

  • रेडीनेस को एक पूर्ण संदेश मानना → TCP एक बाइट स्ट्रीम है, और एक recv एक एप्लिकेशन संदेश नहीं है → वृद्धिशील पार्सिंग (incremental parsing) और एक अलग इनपुट बफर का उपयोग करें।
  • ET को “यह हमेशा एक बार सूचित करता है” तक सीमित करना → कई परिवर्तन कई इवेंट्स उत्पन्न कर सकते हैं; गायब गारंटी एक अपरिवर्तित तैयार स्थिति के लिए दोहराव है → EAGAIN तक प्रोसेस करें।
  • ET के तहत एक बार पढ़ना → नए किनारे के बिना बफ़र किया गया इनपुट बना रहता है → recv पर EAGAIN/EWOULDBLOCK तक लूप करें।
  • ET को ब्लॉकिंग सॉकेट्स के साथ जोड़ना → ड्रेन लूप पूरे इवेंट लूप को ब्लॉक और स्टार्व कर सकता है → पंजीकरण से पहले नॉन-ब्लॉकिंग मोड सेट करें।
  • प्रति लिसनर इवेंट एक कनेक्शन स्वीकार करना → पहले से कतारबद्ध कनेक्शनों को बाद में कोई नोटिफिकेशन प्राप्त नहीं हो सकता है → accept4 पर EAGAIN तक लूप करें।
  • हमेशा EPOLLOUT की सदस्यता लेना → एक सामान्य रूप से लिखने योग्य सॉकेट वेट को तुरंत लौटाता रहता है → केवल लंबित आउटपुट के साथ सदस्यता लें और ड्रेन के बाद हटा दें।
  • HUP पर तुरंत बंद करना → अपठित डेटा रह सकता है → रीड स्टेट मशीन के माध्यम से ड्रेन करें, फिर EOF और आउटपुट स्थिति के अनुसार बंद करें।
  • EPOLLONESHOT को पुन: सक्रिय करना भूल जाना → FD इंटरेस्ट लिस्ट में अक्षम रहता है → स्टेट अपडेट के बाद EPOLL_CTL_MOD का उपयोग करें।
  • निष्पक्षता के लिए रुकना और किसी अन्य ET इवेंट की प्रतीक्षा करना → नए किनारे के बिना FD तैयार रह सकता है → इसे यूजर-स्पेस रेडी कतार से फिर से शुरू करें।
  • पहचान के रूप में केवल पूर्णांक FD का उपयोग करना → एक नया कनेक्शन बंद होने के बाद संख्या का पुन: उपयोग कर सकता है → नियंत्रित लाइफटाइम और जनरेशन-युक्त पहचान का उपयोग करें।
  • यह दावा करना कि ET स्वाभाविक रूप से तेज़ है → परिणाम सक्रिय अनुपात, सिस्टम कॉल, एप्लिकेशन कार्य और कार्यान्वयन शुद्धता पर निर्भर करते हैं → प्रतिनिधि लोड के तहत CPU, लेटेंसी, थ्रूपुट और निष्पक्षता को मापें।

फॉलो-अप्स और प्रतिक्रिया कैसे दें

फॉलो-अप 1: एक शॉर्ट recv यह साबित क्यों नहीं करता कि सॉकेट ड्रेन हो गया है?

recv सामान्य रूप से अनुरोधित लंबाई तक जो भी वर्तमान में उपलब्ध है उसे लौटाता है। नेटवर्क विभाजन, शेड्यूलिंग और भेजने का समय सभी एक शॉर्ट रीड उत्पन्न कर सकते हैं जबकि अधिक बाइट्स बाद में आते हैं। ET ड्रेन सीमा एक नॉन-ब्लॉकिंग EAGAIN/EWOULDBLOCK है; एप्लिकेशन-संदेश सीमा प्रोटोकॉल पार्सर से आती है। वे अलग-अलग सीमाएं हैं।

फॉलो-अप 2: क्या ET हमेशा LT से तेज़ होता है?

अकेले मोड इसका उत्तर नहीं दे सकता। ET लगातार तैयार FD के लिए बार-बार होने वाले नोटिफिकेशन को कम कर सकता है, लेकिन यह ड्रेन, यूजर-स्पेस कतार और स्थिति-प्रबंधन जटिलता को जोड़ता है। जब अधिकांश कनेक्शन निष्क्रिय होते हैं, एप्लिकेशन कार्य हावी होता है, या कार्यान्वयन कई epoll_ctl कॉल जोड़ता है, तो लाभ छोटा हो सकता है। शुद्धता को प्राथमिकता देते हुए, लक्षित कनेक्शन संख्या और गतिविधि वितरण पर CPU, सिस्टम कॉल, थ्रूपुट, p99 और निष्पक्षता को मापें।

फॉलो-अप 3: आउटपुट बफर ड्रेन होने के बाद EPOLLOUT को क्यों हटाएं?

लिखने योग्य का अर्थ आमतौर पर यह होता है कि कर्नेल सेंड बफर कम से कम कुछ बाइट्स स्वीकार कर सकता है, एक ऐसी स्थिति जो कई कनेक्शनों के लिए सत्य रहती है। लंबित एप्लिकेशन आउटपुट के बिना इसे देखने से बेकार नोटिफिकेशन उत्पन्न होते हैं और यह एक LT बिज़ी-लूप बना सकता है। केवल तब तक राइट्स पर नज़र रखें जब तक बाइट्स शेष हों; जब बाद में नया एप्लिकेशन आउटपुट दिखाई दे, तो फिर से सब्सक्राइब करने से पहले तुरंत send का प्रयास करें।

फॉलो-अप 4: क्या EPOLLONESHOT और ET एक ही सुविधा हैं?

नहीं। EPOLLET रेडीनेस-नोटिफिकेशन व्यवहार को बदलता है। EPOLLONESHOT एक इवेंट डिलीवरी के बाद FD को तब तक अक्षम करता है जब तक कि एप्लिकेशन इसे EPOLL_CTL_MOD के साथ पुन: सक्रिय नहीं करता। उन्हें जोड़ा जा सकता है। ONESHOT वर्कर के स्वामित्व को स्थानांतरित करने में मदद करता है, लेकिन ET को अभी भी सही ड्रेनिंग की आवश्यकता होती है और एप्लिकेशन को अभी भी एक रीआर्म प्रोटोकॉल की आवश्यकता होती है।

फॉलो-अप 5: निष्पक्षता बजट EAGAIN से पहले समाप्त हो गया; आप इवेंट को खोने से कैसे बचते हैं?

कनेक्शन को यूजर स्पेस में अभी भी रनेबल के रूप में चिह्नित करें और इसे एक डिडुप्लिकेटेड रेडी कतार में रखें। शेड्यूलर recv/send को EAGAIN, क्लोज़, या किसी अन्य बजट समाप्ति तक फिर से शुरू करता है। कतार में रहने के दौरान एक वैध मालिक बनाए रखें। ONESHOT के साथ, केवल यूजर-स्पेस कार्य पूरा होने के बाद ही पुन: सक्रिय करें और कनेक्शन को फिर से कर्नेल नोटिफिकेशन की आवश्यकता होती है।

फॉलो-अप 6: FD को बंद करने के बाद पुराने इवेंट्स के बारे में चिंता क्यों करें?

एक इवेंट बैच पहले से ही यूजर स्पेस में हो सकता है, और एक एसिंक्रोनस वर्कर एक कनेक्शन संदर्भ बनाए रख सकता है जबकि कर्नेल जल्दी से एक नए सॉकेट को वही पूर्णांक प्रदान करता है। एकाधिक FD एक ओपन फाइल विवरण को भी संदर्भित कर सकते हैं। क्लोज़ पाथ को नए डिस्पैच को रोकना चाहिए, ऑब्जेक्ट लाइफटाइम का प्रबंधन करना चाहिए और पुराने टोकन को अमान्य करना चाहिए। केवल पूर्णांक FD की तुलना करने से यह साबित नहीं हो सकता कि कोई इवेंट वर्तमान कनेक्शन से संबंधित है।

सार्वजनिक स्रोत

संबंधित प्रश्न