GenServer (জেনেরিক সার্ভার) হলো একটি বিহেভিয়ার, যা এলিক্সির প্রসেসগুলোর মধ্যে ঘটা সাধারণ ক্লায়েন্ট-সার্ভার মিথস্ক্রিয়ার একটি বিমূর্তন দেয়।
আমরা যখন প্রসেস সম্পর্কে শিখেছিলাম, তখনকার receive লুপটি কি মনে আছে? GenServer বিহেভিয়ার এমন লুপ বাস্তবায়নের জন্য এবং এমন একটি লুপ চালানো প্রসেসের সাথে মেসেজ আদান-প্রদানের জন্য বিমূর্তন দেয়। এটি স্টেট ধরে রাখা এবং অ্যাসিনক্রোনাস কোড চালানো সহজ করে তোলে।
মনে রাখবেন, GenServer নামটির একাধিক অর্থে ব্যবহার হয়। এটি দিয়ে এমন একটি মডিউল-কেও বোঝানো হয়, যা GenServer বিহেভিয়ার ব্যবহার করে, আবার এমন একটি প্রসেস-কেও, যেটি GenServer বিহেভিয়ার ব্যবহার করে এমন একটি মডিউল থেকে চালু করা হয়েছে।
GenServer বিহেভিয়ার একটি আবশ্যক কলব্যাক init/1 এবং কয়েকটি আকর্ষণীয় ঐচ্ছিক কলব্যাক সংজ্ঞায়িত করে: handle_call/3, handle_cast/2 ও handle_info/3। একটি GenServer ব্যবহার করা ক্লায়েন্টদের ওই কলব্যাকগুলো সরাসরি কল করার কথা নয়। বরং GenServer মডিউলটি এমন কিছু ফাংশন দেয়, যা ব্যবহার করে ক্লায়েন্টরা একটি GenServer প্রসেসের সাথে যোগাযোগ করতে পারে।
প্রায়শই একটি মডিউলই একসাথে দুটি জিনিস সংজ্ঞায়িত করে: ক্লায়েন্ট API, অর্থাৎ এমন কিছু ফাংশনের সেট যা আপনার এলিক্সির অ্যাপের অন্য অংশ থেকে কল করে এই GenServer প্রসেসের সাথে যোগাযোগ করা যায়; এবং সার্ভার কলব্যাক বাস্তবায়ন, যেখানে এই GenServer-এর লজিক থাকে।
চলুন প্রথমে GenServer-এর একটি সহজ উদাহরণ দেখি, তারপর প্রতিটি কলব্যাকের অর্থ কী তা জেনে নিই।
এটি একটি উদাহরণ সার্ভার, যা দীর্ঘ সড়কযাত্রায় বিরক্তিকর যাত্রীদের বারবার করা একই জিজ্ঞাসার জবাব দিতে পারে, আর সেই জিজ্ঞাসাটি হলো: "আমরা কি এখনো পৌঁছে যাইনি?"। এটি কতবার এই প্রশ্নটি করা হয়েছে তা গণনা রাখে এবং ক্রমশ আরও বিরক্তিকর জবাব দেয়।
defmodule AnnoyingPassengerAutoresponder do
use GenServer
# Client API
def start_link(init_arg) do
GenServer.start_link(__MODULE__, init_arg)
end
def are_we_there_yet?(pid) do
GenServer.call(pid, :are_we_there_yet?)
end
# Server callbacks
@impl GenServer
def init(_init_arg) do
# the initial count of questions asked is always 0
state = 0
{:ok, state}
end
@impl GenServer
def handle_call(:are_we_there_yet?, _from, state) do
reply =
cond do
state <= 3 -> "No."
state <= 10 -> "I told you #{state} times already. No."
true -> "..."
end
# increase the count of questions asked
new_state = state + 1
# reply to the caller
{:reply, reply, new_state}
end
end
init/1GenServer.start/3 বা GenServer.start_link/3 কল করে একটি সার্ভার চালু করা যায়। এই ফাংশন দুটির মধ্যে পার্থক্য আমরা লিংক ধারণা-তে শিখেছি।
এই দুটি ফাংশন:
GenServer বিহেভিয়ার বাস্তবায়ন করে এমন একটি মডিউল নেয়।init_arg নামের যেকোনো কিছু নেয়। নাম থেকেই বোঝা যায়, এই আর্গুমেন্টটি init/1 কলব্যাকে পাঠানো হয়।GenServer.start/3 বা GenServer.start_link/3 কল করে সার্ভার চালু করলে ব্লকিং পদ্ধতিতে init/1 কলব্যাকটি চালু হয়। init/1-এর রিটার্ন ভ্যালু ঠিক করে দেয়, সার্ভারটি সফলভাবে চালু হতে পারবে কি না।
init/1 কলব্যাক সাধারণত নিচের মানগুলোর একটি রিটার্ন করে:
{:ok, state}। সার্ভারটি state-কে প্রাথমিক স্টেট ধরে তার receive লুপ শুরু করবে। state যেকোনো টাইপের হতে পারে।{:stop, reason}। reason যেকোনো টাইপের হতে পারে। সার্ভারটি তার receive লুপ শুরু করবে না। প্রসেসটি প্রদত্ত কারণ নিয়ে বন্ধ হয়ে যাবে।এছাড়া আরও উন্নত কিছু সম্ভাবনা আছে, যা আমরা এখন আলোচনা করব না।
সার্ভারের receive লুপ শুরু হলে GenServer.start/3 ও GenServer.start_link/3 ফাংশন দুটি একটি {:ok, pid} টাপল রিটার্ন করে। অন্যথায় তারা {:error, reason} রিটার্ন করে।
handle_call/3যে মেসেজের জবাব প্রয়োজন, তা GenServer.call/2 দিয়ে একটি সার্ভার প্রসেসে পাঠানো যায়। এই ফাংশনটি প্রথম আর্গুমেন্ট হিসেবে চলমান একটি সার্ভার প্রসেসের pid এবং দ্বিতীয় আর্গুমেন্ট হিসেবে মেসেজটি আশা করে। মেসেজটি যেকোনো টাইপের হতে পারে।
handle_call/3 কলব্যাক সিনক্রোনাস মেসেজ হ্যান্ডল করা ও তার জবাব দেওয়ার দায়িত্বে থাকে। এটি তিনটি আর্গুমেন্ট পায়:
message - GenServer.call/2-এ দ্বিতীয় আর্গুমেন্ট হিসেবে পাঠানো মান।from - GenServer.call/2 কল করা প্রসেসের pid। বেশিরভাগ ক্ষেত্রেই এই আর্গুমেন্টটি উপেক্ষা করা যায়।state - সার্ভারের বর্তমান স্টেট। মনে রাখবেন, এর প্রাথমিক মানটি init/1 কলব্যাকে সেট করা হয়েছিল।handle_call/3 কলব্যাক সাধারণত {:reply, reply, state} আকারের ৩-এলিমেন্টের একটি টাপল রিটার্ন করে। এর মানে হলো, টাপলের দ্বিতীয় এলিমেন্ট reply, যা যেকোনো টাইপের হতে পারে, কলকারীর কাছে ফেরত পাঠানো হবে। টাপলের তৃতীয় এলিমেন্ট state হলো এই মেসেজ হ্যান্ডল করার পর সার্ভারের নতুন স্টেট।
এছাড়া আরও উন্নত কিছু সম্ভাবনা আছে, যা আমরা এখন আলোচনা করব না।
নাম দেখে এই কলব্যাকটি কী করে তা মনে রাখতে, ভাবুন আপনি কাউকে ফোনে "কল" করছেন।
যদি ওই ব্যক্তি হাতের কাছে থাকেন, আপনি সঙ্গে সঙ্গে (সিনক্রোনাসভাবে) একটি জবাব পাবেন।
handle_cast/2যে মেসেজের জবাব প্রয়োজন নেই, তা GenServer.cast/2 দিয়ে একটি সার্ভার প্রসেসে পাঠানো যায়। এর আর্গুমেন্টগুলো GenServer.call/2-এর মতোই।
handle_cast/2 কলব্যাক এই মেসেজগুলো হ্যান্ডল করার দায়িত্বে থাকে। এটি দুটি আর্গুমেন্ট পায়, message ও state, যা handle_call/3 কলব্যাকের মতোই (from ছাড়া)।
handle_cast/2 কলব্যাক সাধারণত {:noreply, state} আকারের ২-এলিমেন্টের একটি টাপল রিটার্ন করে।
এছাড়া আরও উন্নত কিছু সম্ভাবনা আছে, যা আমরা এখন আলোচনা করব না।
নাম দেখে এই কলব্যাকটি কী করে তা মনে রাখতে, মনে রাখুন "to cast" শব্দের অর্থ "ছুড়ে ফেলা"-ও।
সমুদ্রে বোতলবন্দি একটি চিঠি ছুড়ে দিলে আপনি সঙ্গে সঙ্গে জবাব পাওয়ার আশা করেন না, হয়তো কখনোই না।
call নাকি cast ব্যবহার করব?আপনার ক্লায়েন্ট কোডের সার্ভার থেকে জবাব না দরকার হলেও প্রায় সবসময়ই call ব্যবহার করুন।
call ব্যবহার করলে জবাবের জন্য অপেক্ষা করা হয়, যা ব্যাকপ্রেশার ব্যবস্থা হিসেবে কাজ করে (যাতে ক্লায়েন্টরা একসাথে অনেক বেশি মেসেজ না পাঠায়)। সার্ভার থেকে জবাব পাওয়াই একমাত্র উপায়, যাতে নিশ্চিত হওয়া যায় যে সার্ভার ক্লায়েন্টের মেসেজটি পেয়েছে ও হ্যান্ডল করেছে।
handle_info/2GenServer.call/2 বা GenServer.cast/2 কল ছাড়াও অন্য উপায়ে মেসেজ সার্ভারের ইনবক্সে জমা হতে পারে, যেমন সাধারণ send/2 ফাংশনটি কল করে।
এমন মেসেজ হ্যান্ডল করতে handle_info/2 কলব্যাক ব্যবহার করুন। এই কলব্যাকটি handle_cast/2-এর মতো ঠিক একইভাবে কাজ করে।
GenServer বিহেভিয়ার handle_info/2-এর একটি ক্যাচ-অল বাস্তবায়ন দেয়, যা অপ্রত্যাশিত মেসেজ নিয়ে এরর লগ করে। আপনি যদি সেই ডিফল্ট বাস্তবায়নটি ওভাররাইড করেন, তাহলে অবশ্যই নিজের একটি ক্যাচ-অল বাস্তবায়ন রাখবেন। ভুলে গেলে সার্ভারটি কোনো অপ্রত্যাশিত মেসেজ পেলে ক্র্যাশ করবে।
উপরে বর্ণিত চারটি কলব্যাকের প্রতিটির রিটার্ন ভ্যালু আরও একটি টাপল এলিমেন্ট দিয়ে বাড়ানো যায়, যেটি হলো একটি টাইমআউট। যেমন, init/1 থেকে {:ok, state} রিটার্ন করার বদলে {:ok, state, timeout} রিটার্ন করুন।
নির্দিষ্ট একটি সময় ধরে মেইলবক্সে কোনো মেসেজ না এলে তা শনাক্ত করতে টাইমআউট ব্যবহার করা যায়। সার্ভার তার কোনো কলব্যাক থেকে একটি টাইমআউট রিটার্ন করলে এবং নির্দিষ্ট সংখ্যক মিলিসেকেন্ড পার হওয়ার পরও কোনো মেসেজ না এলে, handle_info/2-কে প্রথম আর্গুমেন্ট হিসেবে :timeout দিয়ে কল করা হয়।