কম্পিউটার টিউটোরিয়াল

রুবি এক্সেপশন হ্যান্ডলিং শিখুন: রিট্রাই, এনश्यার, এবং রি-রেইজ

রেইজড এক্সেপশন রescued হতে পারে একটি অ্যালটারনেটিভ কোড পাথ এক্সিকিউট করার জন্য যখন জিনিসগুলো ভুল হয়, কিন্তু এক্সেপশন হ্যান্ডল করার আরও উপায় আছে। AppSignal Academy-এর এই সংস্করণে, আমরা retry এবং ensure কীওয়ার্ডগুলোর ওপর যাব, এবং আমরা রescue করা এক্সেপশন রি-রেইজ করার ওপর নজর দিব।

চলুন ধরি আমরা একটি অনবিশ্বস্ত ওয়েব API-এর সাথে কমিউনিকেট করছি। এটি হয়ে থাকা ডাউন হওয়ার পাশাপাশি, এটি তত ধীর যে রিকোয়েস্টগুলো সেকেন্ডস নিতে পারে। আমাদের লাইব্রেরি এই API-র ওপর ডিপেন্ড করে, এবং আমাদের এটিকে যতটা সম্ভব রেজিলিয়েন্ট করতে হবে।

ensure

ensure কীওয়ার্ডটি নিশ্চিত করার জন্য ব্যবহৃত হয় একটি ব্লক কোড চলে, এমনকি যখন একটি এক্সেপশন হয়।

আমাদের লাইব্রেরিতে, আমরা নিশ্চিত করতে চাই Net::HTTP.start দ্বারা ওপেন করা TCP কানেকশন বন্ধ হয়, এমনকি যদি রিকোয়েস্ট ফেইল করে কারণ এটি টাইম আউট হয়, উদাহরণস্বরূপ। এটি করতে, আমরা প্রথমে আমাদের রিকোয়েস্টকে একটি begin/ensure/end ব্লকে র‍্যাপ করব। ensure অংশের কোড সবসময় চলবে, এমনকি যদি পূর্ববর্তী begin ব্লকে একটি এক্সেপশন রেইজ করা হয়।

ensure ব্লকে, আমরা নিশ্চিত করব TCP কানেকশন বন্ধ হয় Net::HTTP#finish কল করে যদি 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 যোগ করব যা টাইমআউট ১০ সেকেন্ড সেট করে। যদি আমাদের রিকোয়েস্টের রেসপন্স সেটি সময়ে না আসে, এটি একটি Net::ReadTimeout রেইজ করবে।

আমরা Errno::ECONNREFUSED-এও ম্যাচ করব API সম্পূর্ণরূপে ডাউন থাকা হ্যান্ডল করতে, যা আমাদের TCP কানেকশন খোলা থেকে বাধা দেবে। সেক্ষেত্রে, http ভেরিয়েবল nil হবে।

এক্সেপশন রescue করা হয় এবং retry কল করা হয় begin ব্লকটি আবার শুরু করার জন্য, ফলে কোডটি একই রিকোয়েস্ট বারবার করে যতক্ষণ না কোনো টাইমআউট ঘটে না। আমরা 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...
... (in an endless loop)

যেও সেটি নিশ্চিত করতে পারে কোনো এক্সেপশন কোনো টাইমআউটের জন্য আর রেইজ হয় না, এইভাবে রিট্রাই-হামার করা নিঃসন্দেহে সেই API-কে আবার চলতে সাহায্য করবে না। এটি সমস্যাজনক কারণ এই কোডটি সেদিন পর্যন্ত লুপ করতে থাকবে যদি API অনরেসপন্সিভ থাকে। বদলে, আমাদের রিট্রাইগুলো ছড়িয়ে দিতে হবে এবং কিছুক্ষণ পর গিব আপ করতে হবে।

গিব আপ করা: raise ব্যবহার করে এক্সেপশন রি-রেইজ করা

যখন একটি এক্সেপশন রescue করা হয়, রেইজড এক্সেপশন অবজেক্টটি rescue ব্লকে পাস করা হয়। আমরা সেটি ব্যবহার করতে পারি এক্সেপশন থেকে ডেটা এক্সট্রাক্ট করতে, যেমন মেসেজ লগে প্রিন্ট করা, কিন্তু আমরা সেটি ব্যবহার করতে পারি একই এক্সেপশনটি রি-রেইজ করতে, একই স্ট্যাক ট্রেসসহ।

begin
  raise "Exception!"
rescue RuntimeError => e
  puts "Exception happened: #{e}"
  raise e
end

যেহেতু আমাদের rescue ব্লকে এক্সেপশন অবজেক্টের অ্যাক্সেস আছে, আমরা এররটি কনসোলে বা একটি এরর মনিটরে লগ করতে পারি। বস্তুত, রescue করা এবং রি-রেইজ করা ঠিক সেইভাবে AppSignal-এর ইন্টিগ্রেশনগুলো এরর ট্র্যাক করে।

নোট: রুবি লাস্ট রেইজড এক্সেপশন একটি ভেরিয়েবলে সংরক্ষণ করে নামের $!, এবং 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

retries ভেরিয়েবলটি sleep কলটিতে ব্যবহার করে, আমরা প্রতিটি নতুন চেষ্টার জন্য অপেক্ষা করার সময় বাড়াতে পারি।

$ 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 ক্লায়েন্ট বানালাম। এটি রিকোয়েস্ট রিট্রাই করবে যদি সেটি ডাউন বা অনরেসপন্সিভ থাকে, এবং আমরা গিব আপ করব যখন সেটি আবার আপ হয়ে আসে না।

আমরা আশা করি এক্সেপশন হ্যান্ডলিং সম্পর্কে আপনি কিছু নতুন শিখেছেন এবং এই আর্টিকেল (অথবা AppSignal Academy সিরিজের অন্য যেকোনো আর্টিকেল) সম্পর্কে আপনার মতামত জানতে চাই। দয়া করে আপনি কী ভাবেন সেটা আমাদের জানাতে বা আপনার যেকোনো রুবি বিষয় আছে যা আপনি আরও শিখতে চান সেটা জানাতে সংকোচ করবেন না।

  1. RuboCop দিয়ে রুবি কোড কোয়ালিটি উন্নত: লিন্টিং ও অটো-ফরম্যাটিং

  2. রুবিতে কাস্টোম এক্সেপশন মাস্টার করুন: ধাপে ধাপে গাইড

  3. রুবি এক্সেপশনে কনটেক্সচুয়াল ডেটা যোগ করুন: একটি প্র্যাক্টিক্যাল গাইড

  4. রুবি লগিং মাস্টারি: Logger এবং Lograge-এ এক্সপার্ট গাইড