Elixir প্রসেসগুলো আইসোলেটেড এবং ডিফল্টভাবে একে অপরের সাথে কিছু শেয়ার করে না। কোনো আনলিংকড চাইল্ড প্রসেস ক্র্যাশ করলে তার প্যারেন্ট প্রসেস প্রভাবিত হয় না।
প্রসেসগুলোকে একে অপরের সাথে লিংক করে এই আচরণ বদলানো যায়। দুইটি প্রসেস লিংক করা থাকলে, একটি প্রসেসের ব্যর্থতা অন্য প্রসেসেও ছড়িয়ে পড়বে। লিংক দ্বিমুখী।
spawn_link/1 ব্যবহার করে কলিং প্রসেসের সাথে আগে থেকেই লিংক করা অবস্থায় প্রসেস স্পন করা যায়, যা একটি অ্যাটমিক অপারেশন, অথবা Process.link/1 দিয়ে পরে লিংক করা যায়।
প্যারালালাইজড কাজ করার সময় প্রসেস লিংক করা কাজে লাগতে পারে, যখন একটি চাঙ্ক কাজ শেষ করতে ব্যর্থ হলে অন্য চাঙ্কটি আর চালিয়ে যাওয়া উচিত নয়।
প্রসেস সুপারভাইজ করতেও লিংক ব্যবহার করা যায়। কোনো প্রসেস যদি এক্সিট ট্র্যাপ করে, তাহলে তার সাথে লিংক করা কোনো প্রসেস ক্র্যাশ করলে সে নিজে ক্র্যাশ করবে না। বদলে সে ক্র্যাশ সম্পর্কে একটি মেসেজ পাবে। এতে সে সুন্দরভাবে ক্র্যাশটি সামলাতে পারে, যেমন ক্র্যাশ করা প্রসেসটি রিস্টার্ট করে।
Process.flag(:trap_exit, true) কল করে একটি প্রসেসকে এক্সিট ট্র্যাপ করার জন্য কনফিগার করা যায়। খেয়াল রাখুন, Process.flag/2 ফ্ল্যাগের পুরনো মান রিটার্ন করে, নতুনটি নয়।
কোনো লিংক করা প্রসেস ক্র্যাশ করলে যে মেসেজটি প্রসেসে পাঠানো হবে, তা {:EXIT, from, reason} প্যাটার্নের সাথে মিলবে, যেখানে from একটি PID। reason যদি অ্যাটম :normal ছাড়া অন্য কিছু হয়, তাহলে বুঝতে হবে প্রসেসটি ক্র্যাশ করেছে বা জোর করে কিল করা হয়েছে।
টাস্ক হলো প্রসেস, যা একটি নির্দিষ্ট অপারেশন চালানোর জন্য ব্যবহৃত হয়। এরা সাধারণত অন্য প্রসেসের সাথে যোগাযোগ করে না, তবে যে প্রসেস টাস্কটি শুরু করেছে তাকে একটি রেজাল্ট রিটার্ন করতে পারে।
কাজ প্যারালালাইজ করতে সাধারণত টাস্ক ব্যবহার করা হয়।
async/await
একটি টাস্ক শুরু করতে Task.async/1 ব্যবহার করুন। এটি আর্গুমেন্ট হিসেবে একটি অ্যানোনিমাস ফাংশন নেয় এবং কলার প্রসেসের সাথে লিংক করা একটি নতুন প্রসেসে সেটি চালায়। এটি একটি %Task{} স্ট্রাক্ট রিটার্ন করে।
এক্সিকিউশনের রেজাল্ট পেতে %Task{} স্ট্রাক্টটি Task.await/2-এ পাস করুন। এটি টাস্কটি শেষ হওয়া পর্যন্ত অপেক্ষা করবে এবং তার রেজাল্ট রিটার্ন করবে। দ্বিতীয় আর্গুমেন্টটি মিলিসেকেন্ডে একটি টাইমআউট, যার ডিফল্ট মান 5000।
খেয়াল রাখুন, টাস্ক শুরু করা এবং টাস্কটির জন্য অপেক্ষা করার মাঝে, যে প্রসেস টাস্কটি শুরু করেছে সেটি ব্লক থাকে না এবং অন্য কাজ করতে পারে।
Task.async/1 দিয়ে শুরু করা যেকোনো টাস্কের জন্য অপেক্ষা করা উচিত, কারণ এটি কলিং প্রসেসে একটি মেসেজ পাঠাবে। প্রতিটি টাস্কের জন্য Task.await/2 কেবল একবারই কল করা যায়।
start/start_link
যদি আপনি কেবল সাইড-এফেক্টের জন্য একটি টাস্ক শুরু করতে চান, তাহলে Task.start/1 বা Task.start_link/1 ব্যবহার করুন। Task.start/1 এমন একটি টাস্ক শুরু করবে যা কলিং প্রসেসের সাথে লিংক করা নয়, আর Task.start_link/1 এমন একটি টাস্ক শুরু করবে যা কলিং প্রসেসের সাথে লিংক করা। দুটি ফাংশনই একটি {:ok, pid} টাপল রিটার্ন করে।
Instruments of Texas-এ একটি পরীক্ষামূলক RPN ক্যালকুলেটর নিয়ে আপনার কাজ চলছে। আপনার দল কয়েকটি প্রোটোটাইপ তৈরি করেছে, যেগুলো পুঙ্খানুপঙ্খভাবে পরীক্ষা করতে হবে, যাতে বড় পরিসরে উৎপাদন করা যায় এমন সবচেয়ে ভালোটি বেছে নেওয়া যায়।
আপনি দুই ধরনের যাচাই চালাতে চান।
প্রথমত, একটি নির্ভরযোগ্যতা যাচাই, যা এমন ইনপুট শনাক্ত করবে যেগুলোর জন্য পরীক্ষাধীন ক্যালকুলেটর হয় ক্র্যাশ করে, নয়তো যথেষ্ট দ্রুত সাড়া দেয় না। ব্যর্থতাগুলো আলাদা করে ধরার জন্য প্রতিটি ইনপুটের হিসাব আলাদা একটি প্রসেসে চালাতে হবে। যে প্রসেস কল করছে সেটিতে লিংক করা এবং এক্সিট ট্র্যাপ করার মাধ্যমে শনাক্ত করা যায় যে হিসাবটি শেষ হয়েছে নাকি ক্র্যাশ করেছে।
দ্বিতীয়ত, একটি সঠিকতা যাচাই, যা দেখবে যে কোনো একটি ইনপুটের জন্য ক্যালকুলেটর যে ফলাফল রিটার্ন করে তা প্রত্যাশিত কি না। কেবল যে ক্যালকুলেটরগুলো আগেই নির্ভরযোগ্যতা যাচাইয়ে উত্তীর্ণ হয়েছে, সেগুলোরই সঠিকতা যাচাই হবে, তাই ক্র্যাশ নিয়ে চিন্তার কিছু নেই। তবে অপারেশনগুলো একসাথে চালানো উচিত যাতে কাজটি দ্রুত হয়, আর এটাই অ্যাসিনক্রোনাস টাস্কের জন্য একেবারে উপযুক্ত ইউজ কেস।
RPNCalculatorInspection.start_reliability_check/2 ফাংশনটি ইমপ্লিমেন্ট করুন। এটি দুটি আর্গুমেন্ট নেবে: একটি ফাংশন (ক্যালকুলেটর) এবং ক্যালকুলেটরের জন্য একটি ইনপুট। এটি এমন একটি ম্যাপ রিটার্ন করবে যাতে ইনপুটটি এবং স্পন করা প্রসেসের PID থাকবে।
স্পন করা প্রসেসটি দেওয়া ইনপুট দিয়ে দেওয়া ক্যালকুলেটর ফাংশনটি কল করবে। প্রসেসটি যে প্রসেস কল করছে সেটির সাথে লিংক করা থাকবে।
RPNCalculatorInspection.start_reliability_check(fn _ -> 0 end, "2 3 +")
# => %{input: "2 3 +", pid: #PID<0.169.0>}
RPNCalculatorInspection.await_reliability_check_result/2 ফাংশনটি ইমপ্লিমেন্ট করুন। এটি দুটি আর্গুমেন্ট নেবে। প্রথম আর্গুমেন্টটি হলো এমন একটি ম্যাপ, যাতে নির্ভরযোগ্যতা যাচাইয়ের ইনপুট এবং এই ইনপুটের জন্য নির্ভরযোগ্যতা যাচাই চালানো প্রসেসের PID থাকে, ঠিক যেভাবে RPNCalculatorInspection.start_reliability_check/2 রিটার্ন করে। দ্বিতীয় আর্গুমেন্টটি একটি ম্যাপ, যা বিভিন্ন ইনপুটের নির্ভরযোগ্যতা যাচাইয়ের ফলাফলের জন্য অ্যাকুমুলেটর হিসেবে কাজ করে।
ফাংশনটি একটি এক্সিট মেসেজের জন্য অপেক্ষা করবে।
যদি এটি নির্ভরযোগ্যতা যাচাই চালানো একই প্রসেস থেকে :normal কারণসহ একটি এক্সিট মেসেজ ({:EXIT, from, reason}) পায়, তাহলে ফলাফলের ম্যাপে input কী (key)-এর অধীনে :ok মান যোগ করে সেটি রিটার্ন করবে।
যদি এটি নির্ভরযোগ্যতা যাচাই চালানো একই প্রসেস থেকে ভিন্ন কোনো কারণসহ একটি এক্সিট মেসেজ পায়, তাহলে ফলাফলের ম্যাপে input কী (key)-এর অধীনে :error মান যোগ করে সেটি রিটার্ন করবে।
যদি 100ms-এর মধ্যে এই শর্তগুলোর সাথে মেলে এমন কোনো মেসেজ না পায়, তাহলে ফলাফলের ম্যাপে input কী (key)-এর অধীনে :timeout মান যোগ করে সেটি রিটার্ন করবে।
# when an exit message is waiting for the process in its inbox
send(self(), {:EXIT, pid, :normal})
RPNCalculatorInspection.await_reliability_check_result(
%{input: "5 7 -", pid: pid},
%{}
)
# => %{"5 7 -" => :ok}
# when there are no messages in the process inbox
RPNCalculatorInspection.await_reliability_check_result(
%{input: "3 2 *", pid: pid},
%{"5 7 -" => :ok}
)
# => %{"5 7 -" => :ok, "3 2 *" => :timeout}
RPNCalculatorInspection.reliability_check/2 ফাংশনটি ইমপ্লিমেন্ট করুন। এটি দুটি আর্গুমেন্ট নেবে: একটি ফাংশন (ক্যালকুলেটর) এবং ক্যালকুলেটরের জন্য ইনপুটের একটি অ্যারে।
অ্যারের প্রতিটি ইনপুটের জন্য এটি start_reliability_check/2 ব্যবহার করে একটি নতুন লিংক করা প্রসেসে নির্ভরযোগ্যতা যাচাই শুরু করবে। তারপর এভাবে চালু হওয়া প্রতিটি প্রসেসের জন্য await_reliability_check_result/2 ব্যবহার করে তার ফলাফলের জন্য অপেক্ষা করবে।
কোনো প্রসেস শুরু করার আগে, এক্সিট মেসেজ পেতে সক্ষম হতে ফাংশনটিকে বর্তমান প্রসেসে এক্সিট ট্র্যাপ করার ফ্ল্যাগ সেট করতে হবে। পরে এটি এই ফ্ল্যাগটি তার আগের মানে রিসেট করবে।
ফাংশনটি সব ইনপুটের নির্ভরযোগ্যতা যাচাইয়ের ফলাফলসহ একটি ম্যাপ রিটার্ন করবে।
fake_broken_calculator = fn input ->
if String.ends_with?(input, "*"), do: raise("oops")
end
inputs = ["2 3 +", "10 3 *", "20 2 /"]
RPNCalculatorInspection.reliability_check(fake_broken_calculator, inputs)
# => %{
# "2 3 +" => :ok,
# "10 3 *" => :error,
# "20 2 /" => :ok
# }
RPNCalculatorInspection.correctness_check/2 ফাংশনটি ইমপ্লিমেন্ট করুন। এটি দুটি আর্গুমেন্ট নেবে: একটি ফাংশন (ক্যালকুলেটর) এবং ক্যালকুলেটরের জন্য ইনপুটের একটি অ্যারে।
অ্যারের প্রতিটি ইনপুটের জন্য এটি একটি অ্যাসিনক্রোনাস টাস্ক চালু করবে, যা দেওয়া ইনপুট দিয়ে ক্যালকুলেটরটি কল করবে। তারপর এভাবে চালু হওয়া প্রতিটি টাস্কের জন্য এটি 100ms ধরে তার ফলাফলের জন্য অপেক্ষা করবে।
fast_cheating_calculator = fn input -> 14 end
inputs = ["13 1 +", "50 2 *", "1000 2 /"]
RPNCalculatorInspection.correctness_check(fast_cheating_calculator, inputs)
# => [14, 14, 14]
Exercism-এ সাইন আপ করুন, Elixir ট্র্যাকের 58টি কনসেপ্ট168টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।