उठाए गए एक्सेप्शन्स को रेस्क्यू किया जा सकता है ताकि चीजें गलत होने पर वैकल्पिक कोड पथ निष्पादित किया जा सके, लेकिन एक्सेप्शन्स को संभालने के और भी तरीके हैं। ऐपसिग्नल अकादमी के इस संस्करण में, हम retry और ensure कीवर्ड्स पर जाएंगे, और हम रेस्क्यू किए गए एक्सेप्शन्स को फिर से उठाने को देखेंगे।
आइए मान लें कि हम एक अविश्वसनीय वेब API के साथ कम्युनिकेट कर रहे हैं। इसके हर बार डाउन होने के अलावा, यह इतना धीमा है कि इसके अनुरोधों में सेकंड लग सकते हैं। हमारी लाइब्रेरी इस API पर निर्भर करती है, और हमें इसे यथासंभव लचीला बनाने की आवश्यकता है।
ensure
ensure कीवर्ड का उपयोग सुनिश्चित करने के लिए किया जाता है कि कोड का एक ब्लॉक चले, भले ही एक्सेप्शन हो।
हमारी लाइब्रेरी में, हम Net::HTTP.start द्वारा खोले गए TCP कनेक्शन को बंद करना सुनिश्चित करना चाहेंगे, भले ही अनुरोध विफल हो जाए क्योंकि यह टाइम आउट हो जाता है, उदाहरण के लिए। ऐसा करने के लिए, हम पहले अपने अनुरोध को begin/ensure/end ब्लॉक में लपेटेंगे। ensure भाग में कोड हमेशा चलेगा, भले ही पूर्ववर्ती begin ब्लॉक में एक्सेप्शन उठाया गया हो।
ensure ब्लॉक में, हम Net::HTTP#finish को कॉल करके TCP कनेक्शन बंद करना सुनिश्चित करेंगे जब तक http वेरिएबल nil न हो, जो TCP कनेक्शन खोलने में विफल होने पर हो सकता है (जो एक एक्सेप्शन भी उठाएगा)।
require "net/http"
begin
puts "Opening TCP connection..."
http = Net::HTTP.start(uri.host, uri.port)
puts "Sending HTTP request..."
puts http.request_get(uri.path).body
ensure
if http
puts "Closing the TCP connection..."
http.finish
end
end
नोट: हम बाद में पुनः प्रयास करने के लिए कनेक्शन का उपयोग करने की अनुमति देने के लिए TCP कनेक्शन को मैन्युअल रूप से बंद करते हैं। हालांकि, चूंकि Net::HTTP.start एक ब्लॉक लेता है जो कनेक्शन बंद होने को सुनिश्चित करता है, उपरोक्त नमूने को ensure को हटाने के लिए रीराइट किया जा सकता है। दिलचस्प रूप से पर्याप्त, ensure ब्लॉक यह भी है कि इसे Net::HTTP में कैसे लागू किया गया है।
retry
retry कीवर्ड ब्लॉक में कोड के एक टुकड़े को पुनः प्रयास करने की अनुमति देता है। rescue ब्लॉक के साथ संयुक्त, हम इसका उपयोग फिर से प्रयास करने के लिए कर सकते हैं यदि हम कनेक्शन खोलने में विफल होते हैं, या यदि API प्रतिक्रिया देने में बहुत लंबा समय लेता है।
ऐसा करने के लिए, हम Net::HTTP.start कॉल में read_timeout जोड़ेंगे जो टाइमआउट को 10 सेकंड पर सेट करता है। यदि तब तक हमारे अनुरोध की प्रतिक्रिया नहीं आई है, तो यह Net::ReadTimeout उठाएगा।
हम Errno::ECONNREFUSED पर भी मैच करेंगे ताकि API पूरी तरह से डाउन होने को संभाला जा सके, जो हमें TCP कनेक्शन खोलने से रोकेगा। उस स्थिति में, http वेरिएबल nil है।
एक्सेप्शन रेस्क्यू किया जाता है और begin ब्लॉक को फिर से शुरू करने के लिए retry कहा जाता है, जिसके परिणामस्वरूप कोड वही अनुरोध करता है जब तक कोई टाइमआउट नहीं होता। हम http ऑब्जेक्ट का पुन: उपयोग करेंगे जो कनेक्शन रखता है यदि यह पहले से मौजूद है।
require "net/http"
http = nil
uri = URI("https://localhost:4567/")
begin
unless http
puts "Opening TCP connection..."
http = Net::HTTP.start(uri.host, uri.port, read_timeout: 10)
end
puts "Executing HTTP request..."
puts http.request_get(uri.path).body
rescue Errno::ECONNREFUSED, Net::ReadTimeout => e
puts "Timeout (#{e}), retrying in 1 second..."
sleep(1)
retry
ensure
if http
puts "Closing the TCP connection..."
http.finish
end
end
अब, हमारा अनुरोध हर सेकंड पुनः प्रयास करेगा जब तक कोई Net::ReadTimeout नहीं उठाया जाता।
$ ruby retry.rb
Opening TCP connection...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 1 second...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 1 second...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 1 second...
Executing HTTP request...
... (अंतहीन लूप में)
हालांकि यह सुनिश्चित कर सकता है कि किसी भी टाइमआउट के लिए कभी कोई एक्सेप्शन नहीं उठाया जाता, इस तरह रिट्राई-हैमरिंग निश्चित रूप से उस API को फिर से चालू करने में मदद नहीं करेगा। यह समस्याग्रस्त है क्योंकि यदि API अनुत्तरदायी रहता है तो यह कोड हमेशा के लिए लूप करता रहेगा। इसके बजाय, हमें अपने पुनः प्रयासों को फैलाना चाहिए और थोड़ी देर बाद हार मान लेनी चाहिए।
हार मानना: raise का उपयोग करके एक्सेप्शन्स को फिर से उठाना
जब एक एक्सेप्शन रेस्क्यू किया जाता है, तो उठाया गया एक्सेप्शन ऑब्जेक्ट rescue ब्लॉक को पास किया जाता है। हम इसका उपयोग एक्सेप्शन से डेटा निकालने के लिए कर सकते हैं, जैसे मैसेज को लॉग में प्रिंट करना, लेकिन हम इसका उपयोग बिल्कुल उसी एक्सेप्शन को फिर से उठाने के लिए भी कर सकते हैं, उसी स्टैक ट्रेस के साथ।
begin
raise "Exception!"
rescue RuntimeError => e
puts "Exception happened: #{e}"
raise e
end
चूंकि हमारे पास rescue ब्लॉक में एक्सेप्शन ऑब्जेक्ट तक पहुंच है, हम कंसोल या एरर मॉनिटर पर एरर लॉग कर सकते हैं। वास्तव में, रेस्क्यू करना और फिर से उठाना ठीक वही है जो ऐपसिग्नल के इंटीग्रेशन एरर्स को ट्रैक करने के लिए करते हैं।
नोट: रूबी अंतिम उठाए गए एक्सेप्शन को $! नामक वेरिएबल में स्टोर करता है, और raise कीवर्ड डिफ़ॉल्ट रूप से इसका उपयोग करेगा। बिना किसी आर्ग्युमेंट्स के raise कॉल करने से अंतिम एक्सेप्शन फिर से उठेगा।
हमारी लाइब्रेरी में, हम कुछ पुनः प्रयासों के बाद API का दबाव कम करने के लिए फिर से उठाने का उपयोग कर सकते हैं। ऐसा करने के लिए, हम retries वेरिएबल में किए गए पुनः प्रयासों की संख्या को ट्रैक करेंगे।
जब भी टाइमआउट होता है, हम नंबर बढ़ाएंगे और जांचेंगे कि क्या यह तीन से कम या बराबर है, क्योंकि हम अधिकतम तीन पुनः प्रयास करना चाहेंगे। यदि हां, तो हम retry करेंगे। यदि नहीं, तो हम अंतिम एक्सेप्शन को फिर से उठाने के लिए raise करेंगे।
require "net/http"
http = nil
uri = URI("https://localhost:4567/")
retries = 0
begin
unless http
puts "Opening TCP connection..."
http = Net::HTTP.start(uri.host, uri.port, read_timeout: 1)
end
puts "Executing HTTP request..."
puts http.request_get(uri.path).body
rescue Errno::ECONNREFUSED, Net::ReadTimeout => e
if (retries += 1) <= 3
puts "Timeout (#{e}), retrying in #{retries} second(s)..."
sleep(retries)
retry
else
raise
end
ensure
if http
puts 'Closing the TCP connection...'
http.finish
end
end
sleep कॉल में retries वेरिएबल का उपयोग करके, हम प्रत्येक नए प्रयास के लिए प्रतीक्षा समय बढ़ा सकते हैं।
$ ruby reraise.rb
Opening TCP connection...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 1 second(s)...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 2 second(s)...
Executing HTTP request...
Timeout (Net::ReadTimeout), retrying in 3 second(s)...
Executing HTTP request...
Closing the TCP connection...
/lib/ruby/2.4.0/net/protocol.rb:176:in `rbuf_fill': Net::ReadTimeout (Net::ReadTimeout)
... from reraise.rb:13:in `'
हमारा अनुरोध तीन बार पुनः प्रयास किया जाता है इससे पहले कि कोड हार मान ले और अंतिम एरर को फिर से उठाए। हम तब एरर को एक लेवल ऊपर संभाल सकते हैं, या अपने ऐप को क्रैश कर सकते हैं यदि यह API की प्रतिक्रिया के बिना अपना काम खत्म नहीं कर सकता।
एक लचीला वेब API क्लाइंट
इन मेथड्स को संयोजित करके, हमने लगभग बीस लाइन्स कोड में एक लचीला वेब API क्लाइंट बनाया है। यह अनुरोधों को पुनः प्रयास करेगा यदि यह डाउन या अनुत्तरदायी है, और हम हार मान लेंगे जब यह फिर से चालू नहीं होगा।
हमें उम्मीद है कि आपने एक्सेप्शन्स को संभालने के बारे में कुछ नया सीखा और इस लेख (या ऐपसिग्नल अकादमी श्रृंखला के अन्य लेखों) के बारे में आपका क्या विचार है, यह जानना पसंद करेंगे। कृपया हमें बताने में संकोच न करें कि आप क्या सोचते हैं, या यदि आपके पास कोई रूबी विषय है जिसके बारे में आप अधिक सीखना चाहेंगे।