GO Context — কন্টেক্সট কি এবং কিভাবে ব্যবহার করতে হয়

কন্টেক্সট এর বাংলা হলো প্রসংগ। তবে আমাদের গো এর ভাষায় একটু অন্য জিনিস।

গো কন্টেক্সট কে আমরা সবচেয়ে বেশি ব্যবহার করি কনকারেন্সির সময়। বিভিন্ন গোরুটিনকে নিয়ন্ত্রণ করতে। আমার দেখা মতে গো এর কনকারেন্সি বেস্ট। মডার্ণ ল্যাঙ্গুয়েজ এমনই হওয়া উচিত ছিল।

গোরুটিন হচ্ছে ঘুড়ির মতো। একবার ছেড়ে দিলে উড়তেই থাকবে কাজ না শেষ হওয়া পর্যন্ত। কিন্ত সমস্যা বাধে যখন আপনি বুঝতে পারেন আপনার গোরুটিন কোনো ঝামেলায় পড়েছে অথবা গোরুটিন যে কাজ করতেছে তা ক্যান্সেল করবেন। কিন্ত কিভাবে আটকাবেন উড়ন্ত ঘুড়িকে?

ঘুড়ির সুতা আর নাটাই এর সাহায্যে যেভাবে ঘুড়িকে নিয়ন্ত্রণ করতে হয় তেমনি বিভিন্ন কন্টেক্সট এর সাহায্যে আমরা গোরুটিনকে নিয়ন্ত্রণ করতে পারি।

কন্টেক্সট কাজ করে প্যারেন্ট চাইল্ড হিসেবে।

পূর্ব-শর্ত

০১ এডভান্সড গো এবং কনকারেন্সি

০২ ওয়েব এপ্লিকেশন কনসেপ্ট

Background হচ্ছে রুট কন্টেক্সট। এটা থেকেই বাকি কন্টেক্সট তৈরি করতে হবে। ব্যাকগ্রাউন্ড ছাড়াও গো তে ৪ টি কন্টেক্সট আছেঃ

০১ WithCancel

০২ WithTimeout

০৩ WithDeadline

০৪ WithValue

আমরা একে একে সবগুলি নিয়েই কাজ করবো।

WithCancel

আমরা ব্যাকগ্রাউন্ড কন্টেক্সট এর উপর ভিত্তি করে WithCancel এর একটা কন্টেক্সট বানাতে পারি। এই কন্টেক্সট এর সাহায্যে আমরা যেকোনো গোরুটিনকে যেকোন সময় ক্যান্সেল করে দিতে পারি।

ধরুন, আপনি একটা ফাংশনে রিক্যুয়েস্ট পাঠালেন কিছু ডাটা রিড করে আপনাকে দেয়ার জন্য। এখন ডাটা রিড করার মাঝামাঝি আপনি রিক্যুয়েস্ট টা কেন্সেল করে দিলেন। কিন্ত ডাটাবেজ থেকে ডাটা রিড হতে থাকবে এবং রিসোর্স ওয়াস্ট হবে। এ থেকে বাচার জন্য আমরা কন্টেক্সট কেন্সেলেশন ব্যবহার করতে পারি।

ক্যান্সেলেশন এর ধাপ ২ টি

০১ কেন্সেলেশন ইভেন্ট ইমিট করা

০২ কেন্সেলেশন ইভেন্ট এর জন্য অপেক্ষা করা এবং হ্যান্ডেল করা

package mainimport (
"time"
"fmt"
"context"
"bufio"
"os"
)
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
go func() {
s := bufio.NewScanner(os.Stdin)
s.Scan()
cancel()
}()
SayHello(ctx, time.Second*5, "Hello")}func SayHello(ctx context.Context, d time.Duration, msg string) {
select {
case <-ctx.Done():
fmt.Println(ctx.Err())
case
<-time.After(d):
fmt.Println(msg)
}
}

এখানে আমরা SayHello নামে একটা ফাংশন বানিয়েছি যেটা একটা নির্দিষ্ট সময় পরে যেকোন একটা মেসেজ প্রিন্ট করতে পারে।

আমাদের ক্যেন্সেলেশন এর বিষয়টা ইভেন্ট নির্ভর, তাই আমরা গো চ্যানেল ব্যবহার করে ইভেন্ট ইমিট ও হ্যান্ডেল করতে পারি

আমরা প্রথমে দেখি কিভাবে ইভেন্ট ইমিট করবো, আমরা মেইন ফাংশনে ব্যাকগ্রাউন্ড থেকে ক্যান্সেল এর একটা কন্টেক্সট বানিয়েছি। এই কনটেক্সট এর সাথে আমরা ক্যান্সেল করার ফাংশন পেয়েছি। আমরা একে ব্যবহার করে ক্যান্সেল এর ইভেন্ট ইমিট করতে পারি।

ধরুন আমরা চাইলাম ইউজার যখন কিবোর্ড থেকে কিছু টাইপ করবে তখন আমরা ক্যান্সেল এর ইভেন্ট ইমিট করবো। ইউজারের কাছ থেকে কিবোর্ড এর বাটন প্রেস পাবার জন্য আমরা আলাদা একটা গোরুটিন চালু করলাম যেন বাকি কাজ গুলি কনকারেন্টলি চলতে পারে। যখনই ইউজার কিছু প্রেস করবে আমরা ক্যান্সেল এর ইভেন্ট ইমিট করে দিবো ঐ কনটেক্সট এর মাধ্যমে।

এবার আমাদের কাজ হলো যে ফাংশন বা গোরুটিনকে আমরা এই কন্টেক্সট দিয়ে নিয়ন্ত্রণ করতে চাই তার সাথে জুড়ে দেয়া যাতে করে ঐ ফাংশন বা গোরুটিনটি কন্টেক্সট থেকে ইভেন্ট পেয়ে হ্যান্ডেল করতে পারে। তাই আমরা ফাংশনের প্রথমেই ctx কনটেক্সটটি দিয়ে দিয়েছি।

এবার আমরা শিখবো কিভাবে ক্যান্সেলেশন এর ইভেন্ট হ্যান্ডেল করতে হয়।

আমরা যখন ক্যান্সেল ইভেন্ট ইমিট করি তখন সেটা ctx.Done() চ্যানেলে একটা ইভেন্ট পাঠায়। তাহলে আমরা সেই ইভেন্ট পেয়ে আমাদের কাজ ক্যান্সেল করে দিলেই হয়ে যাবে। আমরা সেটাই করেছি উপরের কোডে।

আবার time.After() ফাংশনও একটা নির্দিষ্ট সময় পরে ইভেন্ট পাঠায়, যেটাকে দিয়ে আমরা আমাদের মেসেজ প্রিন্ট করেছি।

এখানে আমরা গো কনকারেন্সির select স্ট্যাটমেন্ট ব্যবহার করেছি। এটা অনেকটা switch বা if…else এর মতই, কিন্ত পার্থক্য হলো এটা শুধু চ্যানেলের জন্য কাজ করে।

এবং সর্বশেষ কন্টেক্সট ক্যেন্সেল করলে ctx.Done() চ্যানেলে ডাটা যায়। সেই ডাটা কেউ রিড করলে Done() চ্যানেলটি ক্লোজ হয়ে যায়। আর ক্লোজ হলে ctx.Err() এ একটা এরর মেসেজ আসে।

WithTimeout

এটাকে আমরা তুলনা করতে পারি টাইম বোম্বের সাথে। এই কন্টেক্সট একটা নির্দিষ্ট সময় পরে ক্যান্সেল করে দিবে।

এটার মাধ্যমে আমরা টাইমআউট সিস্টেম বানাতে পারি। ধরুন আমরা চাইলাম কোন রিক্যুয়েস্ট হ্যান্ডেল করতে যদি ৫ সেকেন্ডের বেশি লাগে তাহলে আমরা সেটাকে ক্যান্সেল করে দিব, তাহলে আমরা এটার সাহায্যে ইমপ্লিমেন্ট করতে পারবো।

package mainimport (
"time"
"fmt"
"context"
)
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Second*2)
defer cancel()
SayHello(ctx, time.Second*5, "Hello")}func SayHello(ctx context.Context, d time.Duration, msg string) {
select {
case <-ctx.Done():
fmt.Println(ctx.Err())
case
<-time.After(d):
fmt.Println(msg)
}
}

আমাদের SayHello ফাংশন রান হতে ৫ সেকেন্ড সময় লাগবে। কিন্তু আমরা চাইলাম ২ সেকেন্ড পরেই ক্যান্সেল করে দিতে। তাহলে আমরা উপরের মত করে ইমপ্লিমেন্ট করবো। আর WithTimeout যেহেতু WithCancel এর চাইল্ড তাই এটা এমনিতেই cancel করার ক্ষমতা পেয়েছে।

WithValue

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

package mainimport (
"time"
"fmt"
"context"
)
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, "name", "cyan")
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
SayHello(ctx, time.Second*5, "Hello")}func SayHello(ctx context.Context, d time.Duration, msg string) {
select {
case <-ctx.Done():
fmt.Println(ctx.Err())
case
<-time.After(d):
v := ctx.Value("name")
fmt.Println(v)
fmt.Println(msg)
}
}

দেখুন আমরা হায়েরারকি অনুযায়ী সাজিয়েছি। প্রথমে ব্যাকগ্রাউন্ড, এরপর ভ্যালু, এরপর ক্যান্সেল, এরপর টাইমআউট বা ডেডলাইন কন্টেক্সট এভাবে করে।

আমরা ভ্যালু কন্টেক্সট এর সাহায্যে একটা কি-ভ্যালু ডাটা পাস করেছি। যেকোনো ফাংশন যার কাছে কন্টেক্সটটি আছে সে-ই এই কনটেক্সট এক্সেস করতে পারবে।

এতক্ষন আমরা দেখলাম সিঙ্গেল প্রসেসের মাঝে কন্টেক্সট এর ব্যাপারটা। কিন্ত আমরা যদি চাই নেটওয়ার্ক কানেক্টিভিটির মাধ্যমে অন্য কম্পিউটারে রান হওয়া সার্ভার প্রসেসকে আমাদের কম্পিউটারের ক্লায়েন্ট প্রসেস দিয়ে কন্ট্রোল করে ক্যান্সেল করতে? সেটাও আমারা পারবো, আসুন দেখি

মজার ব্যাপার হলো গো এর HTTP প্যাকেজের Request অবজেক্টের সাথে একটা কন্টেক্সট হিডেন অবস্থায় অলরেডি দিয়ে দেয়া আছে। আমরা সেটার সাহায্যে আমাদের রিক্যুয়েস্টকে কন্ট্রোল করতে পারব।

ctx := r.Context()

এর মাধ্যমে আমরা r রিক্যুয়েস্ট অবজেক্ট এর কন্টেক্সটকে পেলাম। কিন্ত কথা হলো এর সাথে কিভাবে ক্লায়েন্ট থেকে কন্টেক্সট এটাচ করে কন্ট্রোল করবো?

খুব সহজ, আমরা চাইলেই ক্লায়েন্ট থেকে রিক্যুয়েস্ট পাঠানোর আগেই একটা কন্টেক্সট এটাচ করে দিতে পারি যেটা এই সার্ভার রিক্যুয়েস্ট কনটেক্সট এর সাথে এটাচ হয়ে যাবে। অর্থাৎ ক্লায়েন্ট থেকে কন্টেক্সট পাঠালে সেটা আমরা সার্ভার থেকে এক্সেস করতে পারবো।

সার্ভার

package mainimport (
"net/http"
"log"
"time"
)
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8045", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
log.Println("Handler started")
ctx := r.Context()
select {
case <-time.After(10 * time.Second):
log.Println("Hello")
case <-ctx.Done():
log.Println(ctx.Err())
}
log.Println("Handler ended")
}

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

ক্লায়েন্ট

package mainimport (
"context"
"net/http"
"time"
)
func main() {
client := http.Client{}
req, _ := http.NewRequest("GET", "http://localhost:8045", nil)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
req.WithContext(ctx)
client.Do(req)
time.Sleep(time.Second*2)
cancel()
}

আমরা ক্লায়েন্ট এর এখানে ইন্টারেস্টিং কিছু কাজ করেছি। আমরা চাইলেই সরাসরি http.Get() মেথড দিয়ে রিক্যুয়েস্ট পাঠাতে পারতাম। কিন্ত আমরা যেহেতু কন্টেক্সট লেভেল প্রোগ্রামিং করতেছি তাই আমরা রিক্যুয়েস্ট পাঠানোর আগে কিছু জিনিস মডিফাই করে নিবো। তাই আমরা রিক্যুয়েস্ট এর সাথে একটা ক্যান্সেলেশন কন্টেক্সট যুক্ত করে দিলাম। এর ফলে সার্ভার থেকে কন্টেক্সট বের করলে আমাদের এই কন্টেক্সটই পাবো।

এর ফলে ক্লায়েন্ট চাইলেই রিক্যুয়েস্ট ক্যান্সেল করে দিতে পারে নির্দিষ্ট সময় পরে। সার্ভার সাথে সাথে সব কাজ ক্যান্সেল করে দিবে।

আমরা চাইলে টাইমআউট কন্টেক্সটও যোগ করে দিতে পারি এর সাথে। লিমিটলেস পসিবিলিটি আমাদের হাতে এখন।

তো আজকে এই পর্যন্তই। কন্টেক্সট নিয়ে আমি জেনারেল একটা ওভারাভিউ দিয়ে দিলাম। আশা করি আপনাদের প্রজেক্টে কাজে লাগবে।

আল্লাহ হাফেজ।

--

--

Cyan Tarek
প্রোগ্রামিং পাতা

Software Engineer, Backend Ninja, DevOps Player, Microservice learner, Gopher/Golang Lover