Ge

GenServer में Elixir

4 अभ्यास

GenServer के बारे में

GenServer (जेनेरिक सर्वर) एक बिहेवियर है, जो Elixir प्रोसेस के बीच होने वाली आम क्लाइंट-सर्वर बातचीत को सरल बना देता है।

जब हमने प्रोसेस के बारे में सीखा था, तब का रिसीव लूप याद है? GenServer बिहेवियर ऐसे लूप बनाने के लिए, और ऐसा लूप चलाने वाले प्रोसेस से संदेशों का आदान-प्रदान करने के लिए ऐब्स्ट्रैक्शन देता है। इससे स्थिति बनाए रखना और असिंक्रोनस कोड चलाना आसान हो जाता है।

Note

ध्यान दीजिए कि GenServer नाम के कई मतलब हैं। इसका इस्तेमाल उस मॉड्यूल के लिए भी होता है जो GenServer बिहेवियर का उपयोग करता है, और उस प्रोसेस के लिए भी जिसे ऐसे मॉड्यूल से शुरू किया गया था जो GenServer बिहेवियर का उपयोग करता है।

GenServer बिहेवियर एक ज़रूरी कॉलबैक init/1 परिभाषित करता है, और कुछ दिलचस्प वैकल्पिक कॉलबैक भी: handle_call/3, handle_cast/2 और handle_info/3। GenServer इस्तेमाल करने वाले क्लाइंट इन कॉलबैक को सीधे कॉल नहीं करते। इसके बजाय, GenServer मॉड्यूल ऐसे फंक्शन देता है जिनसे क्लाइंट किसी GenServer प्रोसेस से बात कर सकते हैं।

अक्सर एक ही मॉड्यूल दोनों काम करता है: यह एक क्लाइंट API परिभाषित करता है, यानी ऐसे फंक्शन जिन्हें आपकी Elixir ऐप के बाकी हिस्से इस 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/1

किसी सर्वर को GenServer.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 को शुरुआती स्थिति बनाकर शुरू करेगा। state किसी भी टाइप का हो सकता है।
  • {:stop, reason}। reason किसी भी टाइप का हो सकता है। सर्वर अपना रिसीव लूप शुरू नहीं करेगा। प्रोसेस दिए गए कारण के साथ बंद हो जाएगा।

और भी एडवांस्ड संभावनाएँ हैं, जिनके बारे में हम अभी नहीं बताएँगे।

अगर सर्वर का रिसीव लूप शुरू हो जाता है, तो GenServer.start/3 और GenServer.start_link/3 फंक्शन {:ok, pid} टपल लौटाते हैं। वरना वे {:error, reason} लौटाते हैं।

handle_call/3

जिस संदेश का जवाब चाहिए, उसे GenServer.call/2 से किसी सर्वर प्रोसेस को भेजा जा सकता है। यह फंक्शन पहले आर्गुमेंट के तौर पर चल रहे सर्वर प्रोसेस का pid लेता है, और दूसरे आर्गुमेंट के तौर पर संदेश। संदेश किसी भी टाइप का हो सकता है।

handle_call/3 कॉलबैक सिंक्रोनस संदेशों को सँभालने और उनका जवाब देने का काम करता है। इसे तीन आर्गुमेंट मिलते हैं:

  1. message - वह वैल्यू जो GenServer.call/2 को दूसरे आर्गुमेंट के तौर पर दी गई थी।
  2. from - उस प्रोसेस का pid जो GenServer.call/2 कॉल कर रहा है। ज़्यादातर मौकों पर इस आर्गुमेंट को नज़रअंदाज़ किया जा सकता है।
  3. state - सर्वर की मौजूदा स्थिति। याद रखें कि इसकी शुरुआती वैल्यू init/1 कॉलबैक में तय की गई थी।

handle_call/3 कॉलबैक आम तौर पर {:reply, reply, state} जैसा तीन एलिमेंट वाला टपल लौटाता है। इसका मतलब है कि टपल का दूसरा एलिमेंट, यानी reply, जो किसी भी टाइप का हो सकता है, कॉल करने वाले को वापस भेज दिया जाएगा। टपल का तीसरा एलिमेंट, state, इस संदेश को सँभालने के बाद सर्वर की नई स्थिति है।

और भी एडवांस्ड संभावनाएँ हैं, जिनके बारे में हम अभी नहीं बताएँगे।

Note

इस कॉलबैक का काम नाम से याद रखने के लिए, सोचिए कि आप किसी को फोन पर "कॉल" कर रहे हैं।

अगर वह व्यक्ति उपलब्ध है, तो आपको तुरंत (सिंक्रोनस रूप से) जवाब मिल जाएगा।

handle_cast/2

जिस संदेश का जवाब नहीं चाहिए, उसे GenServer.cast/2 से किसी सर्वर प्रोसेस को भेजा जा सकता है। इसके आर्गुमेंट GenServer.call/2 वाले आर्गुमेंट जैसे ही होते हैं।

handle_cast/2 कॉलबैक ऐसे संदेशों को सँभालने का काम करता है। इसे दो आर्गुमेंट मिलते हैं, message और state, जो handle_call/3 कॉलबैक वाले आर्गुमेंट ही हैं (from को छोड़कर)।

handle_cast/2 कॉलबैक आम तौर पर {:noreply, state} जैसा दो एलिमेंट वाला टपल लौटाता है।

और भी एडवांस्ड संभावनाएँ हैं, जिनके बारे में हम अभी नहीं बताएँगे।

Note

इस कॉलबैक का काम नाम से याद रखने के लिए, याद रखें कि "to cast" का मतलब "फेंकना" भी होता है।

अगर आप समुद्र में बोतल में बंद कोई संदेश फेंक दें, तो आपको तुरंत जवाब मिलने की उम्मीद नहीं होती, और शायद कभी भी नहीं।

call इस्तेमाल करें या cast?

लगभग हमेशा call ही इस्तेमाल कीजिए, भले ही आपके क्लाइंट कोड को सर्वर से जवाब की ज़रूरत न हो।

call इस्तेमाल करने पर जवाब का इंतज़ार होता है, जो बैकप्रेशर तंत्र का काम करता है (ताकि क्लाइंट एक साथ बहुत ज़्यादा संदेश न भेजें)। सर्वर से जवाब मिलना ही यह पक्का करने का एकमात्र तरीका है कि सर्वर ने क्लाइंट का संदेश पाया और उसे सँभाला भी।

handle_info/2

संदेश सर्वर के इनबॉक्स में GenServer.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 के साथ कॉल किया जाता है।

GitHub के ज़रिए संपादित करें यह लिंक नई विंडो या टैब में खुलता है।

GenServer सीखिए

अभ्यास लॉक है

GenServer पर अभ्यास करने के लिए 2 और अभ्यास अनलॉक कीजिए