Skip to Content
Go Realm v1 is released 🎉
Go RoutinesContext Cancellation, Timeout & Deadline

Context Cancellation, Timeout & Deadline

🧠 context (কনটেক্সট) -> সিগন্যাল পাঠানো

context হলো একটি অবজেক্ট যা একটি goroutine-কে সিগন্যাল পাঠাতে পারে। এই সিগন্যালগুলো হতে পারে:

  • ক্যানসেলেশন (Cancellation): “তোমার কাজ এখন বন্ধ করো।”
  • টাইমআউট (Timeout): “তোমার কাজ যদি ৫ সেকেন্ডের মধ্যে শেষ না হয়, তবে বন্ধ করে দিও।”
  • ডেডলাইন (Deadline): “তোমার কাজ রাত ১১:৫৯ মিনিটের মধ্যে শেষ করতে হবে, না হলে বন্ধ করে দিও।”

Context-এর প্রধান কাজ হলো goroutine-দের একটি নির্দিষ্ট কাজের “লাইফসাইকেল” বা জীবনকাল বেঁধে দেওয়া।


💧 সমস্যা: Goroutine Leak

Context ব্যবহারের প্রধান কারণ হলো Goroutine Leak (গোরুটিন লিক) প্রতিরোধ করা।

একটি “লিক” তখন ঘটে যখন আপনি একটি goroutine চালু করেন কিন্তু সেটি শেষ করার কথা ভুলে যান (বা করতে পারেন না)। goroutine-টি ব্যাকগ্রাউন্ডে চলতেই থাকে এবং মেমোরি ও CPU অপচয় করে।

উদাহরণ: ধরুন একটি ওয়েব সার্ভারে একজন ইউজার একটি রিকোয়েস্ট পাঠালো। আপনি একটি goroutine চালু করলেন ডেটাবেস থেকে তথ্য আনার জন্য। কিন্তু ইউজার রিকোয়েস্টটি ক্যানসেল করে দিলো (ব্রাউজার বন্ধ করে দিলো)।

  • Context ছাড়া: আপনার ডেটাবেস goroutine-টি চলতেই থাকবে। সে জানে না যে তার কাজের আর কোনো দরকার নেই। এটি একটি লিক।
  • Context সহ: রিকোয়েস্ট ক্যানসেল হওয়ার সাথে সাথে context আপনার goroutine-কে একটি “বন্ধ করো” সিগন্যাল পাঠাবে। goroutine-টি কাজ বন্ধ করে দেবে এবং রিসোর্স মুক্ত করবে।

🎮 Context-এর ধরণ

Go-তে context প্যাকেজটি এই সুবিধাগুলো দেয়।

🔹 1. context.Background()

  • 👉 এটা হলো root context
  • সাধারণত main ফাংশন বা একটি রিকোয়েস্টের শুরুতে এটি তৈরি করা হয়।
  • এটি একটি খালি context, এটা কখনো cancel হয় না, এর কোনো টাইমআউট নেই।
ctx := context.Background()

📘 এর ওপর ভিত্তি করেই WithCancel, WithTimeout বা WithDeadline context তৈরি করা হয়।


🔹 2. context.WithCancel(parentContext)

এটি একটি parentContext (যেমন Background()) নেয় এবং দুটি জিনিস রিটার্ন করে:

  1. একটি নতুন child context (ctx)।
  2. একটি cancel ফাংশন (cancel)।

আপনি যখনই cancel() ফাংশনটি কল করবেন, তখন সেই context এবং তার সব child context cancel হয়ে যায়।

ctx, cancel := context.WithCancel(context.Background()) // `ctx` → মূল context object // `cancel` → এই function কল করলে context “cancel” হয়ে যায়। // এখানে আমরা একটি goroutine চালু করছি যা ৩ সেকেন্ড পর পর্যন্ত ব্লক হয়ে থাকবে go func() { <-time.After(3 * time.Second) // ৩ সেকেন্ড পর পর্যন্ত ব্লক হয়ে থাকো, তারপর এগিয়ে যাও cancel() // cancel after 3 seconds }() <-ctx.Done() fmt.Println("Context canceled:", ctx.Err())

🧩 Output:

Context canceled: context canceled
অংশকাজ
time.After()নির্দিষ্ট সময় পরে signal পাঠায়
cancel()context cancel করে, Done() বন্ধ করে
ctx.Done()cancellation signal শোনে
ctx.Err()কেন cancel হলো (Canceled / Timeout) বলে

🔹 3. context.WithTimeout(parentContext, duration)

👉 এইটা হলো “auto-cancel” করা context — নির্দিষ্ট সময় পরে নিজে থেকেই (automatically) cancel হয়ে যায়

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() // এটা main() ফাংশন return করার ঠিক আগে রান হবে। select { case <-time.After(5 * time.Second): fmt.Println("Work finished") case <-ctx.Done(): fmt.Println("Timeout:", ctx.Err()) }

🧩 Output:

Timeout: context deadline exceeded

🧠 মানে ৩ সেকেন্ডের মধ্যে কাজ না হলে context নিজেই cancel করে দেয়।

🔹 4. context.WithDeadline(parentContext, time)

👉 WithTimeout এর মতোই, কিন্তু নির্দিষ্ট সময় (clock time) পর্যন্ত wait করে।

deadline := time.Now().Add(2 * time.Second) ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel()

🔹 5. ctx.Done()

👉 ctx.Done() হলো একটা channel যা close হয়ে যায় যখন context cancel হয়।

select { case <-ctx.Done(): fmt.Println("🛑 Stopped:", ctx.Err()) }

🧠 এর মাধ্যমে goroutine বুঝতে পারে — “আমার কাজ বন্ধ করতে হবে।”


📡 Context যেভাবে কাজ করে

Context-এর দুটি মূল অংশ: সিগন্যাল পাঠানো এবং সিগন্যাল গ্রহণ করা।

১. সিগন্যাল পাঠানো (ক্যানসেল করা): WithCancel-এর cancel() ফাংশন কল করা হলে, অথবা WithTimeout-এর সময় শেষ হলে context ক্যানসেল হয়ে যায়।

২. সিগন্যাল গ্রহণ করা (ctx.Done()): প্রতিটি context-এর একটি .Done() মেথড আছে, যা একটি চ্যানেল রিটার্ন করে।

  • Context যতক্ষণ অ্যাক্টিভ থাকে, এই চ্যানেলটি ব্লক থাকে।
  • Context ক্যানসেল হওয়ার সাথে সাথে এই চ্যানেলটি বন্ধ (closed) হয়ে যায়।

আমরা জানি, একটি বন্ধ চ্যানেল থেকে রিড করা হলে তা সাথে সাথে রিটার্ন করে। আমরা এই সুবিধাটি select-এর মধ্যে ব্যবহার করি:

select { case <-ctx.Done(): // Context ক্যানসেল হয়ে গেছে (টাইমআউট বা ম্যানুয়াল)! // এখানে সব কাজ বন্ধ করতে হবে, রিসোর্স পরিষ্কার করতে হবে। fmt.Println("কাজ বন্ধ করছি...") return // goroutine থেকে বের হয়ে যাই default: // Context এখনো অ্যাক্টিভ আছে, কাজ চালিয়ে যাই fmt.Println("কাজ চলছে...") }

🔗 Context Propagation (প্রোপাগেশন)

এটি context-এর সবচেয়ে গুরুত্বপূর্ণ বৈশিষ্ট্য।

যখন একটি Parent Context ক্যানসেল হয়, তখন তার থেকে তৈরি সব Child Context-ও স্বয়ংক্রিয়ভাবে ক্যানসেল হয়ে যায়।

এ কারণেই context সবসময় ফাংশনের প্রথম আর্গুমেন্ট হিসেবে পাস করা হয়: func myWorker(ctx context.Context, otherArg string)

  • main একটি ctx তৈরি করে।
  • main কল করে funcA(ctx)
  • funcA কল করে funcB(ctx)
  • funcB কল করে funcC(ctx)

যদি main ফাংশন ctx-কে ক্যানসেল করে (যেমন, ইউজারের রিকোয়েস্ট টাইমআউট হলো), সেই সিগন্যালটি funcA, funcB, এবং funcC-তে থাকা সব goroutine-এর কাছে পৌঁছে যাবে (<-ctx.Done())। সবাই একযোগে কাজ বন্ধ করে দেবে।


💻 আজকের অনুশীলন (Exercises)

আসুন, উপরের সব ধারণা কোড করে দেখি।

অনুশীলন ১ এবং ২: WithTimeout (ম্যানুয়াল ক্যানসেল এবং টাইমআউট)

এই অনুশীলনে আমরা একটি worker তৈরি করব যা একটি context গ্রহণ করে। আমরা দেখব ৩ সেকেন্ড পর এটি স্বয়ংক্রিয়ভাবে বন্ধ হয়ে যায়।

package main import ( "context" "fmt" "time" ) // worker হলো একটি goroutine যা context শেষ না হওয়া পর্যন্ত কাজ করে func worker(ctx context.Context, name string) { fmt.Printf("👷 %s: কাজ শুরু...\n", name) for { select { case <-ctx.Done(): // সিগন্যাল পেয়েছি! fmt.Printf("🛑 %s: ক্যানসেল সিগন্যাল পেলাম, কাজ বন্ধ করছি।\n", name) return // goroutine থেকে প্রস্থান default: // Context ঠিক থাকলে কাজ চালিয়ে যাই fmt.Printf("⚙️ %s: কাজ করছি...\n", name) time.Sleep(1 * time.Second) // কাজের সিমুলেশন } } } func main() { // ১. একটি parent context তৈরি করি parentCtx := context.Background() // ২. ৩ সেকেন্ডের একটি টাইমআউট সহ child context তৈরি করি ctx, cancel := context.WithTimeout(parentCtx, 3*time.Second) // ৩. (খুবই গুরুত্বপূর্ণ) কাজ শেষে cancel() কল করা // এটি নিশ্চিত করে যে context-এর সাথে যুক্ত সব রিসোর্স মুক্ত হবে। // যদিও টাইমআউট হলে এটি নিজে থেকেই ক্যানসেল হবে, // কিন্তু যদি worker আগে কাজ শেষ করে ফেলে, তবে defer cancel() রিসোর্স লিক ঠেকায়। defer cancel() fmt.Println("🚀 main: ওয়ার্কার চালু করছি (৩ সেকেন্ড টাইমআউট)...") go worker(ctx, "Worker-1") // main goroutine-কে ৫ সেকেন্ড চালু রাখি যাতে worker-এর প্রস্থান দেখতে পারি time.Sleep(5 * time.Second) fmt.Println("👋 main: প্রোগ্রাম শেষ।") }

আউটপুট:

🚀 main: ওয়ার্কার চালু করছি (৩ সেকেন্ড টাইমআউট)... 👷 Worker-1: কাজ শুরু... ⚙️ Worker-1: কাজ করছি... ⚙️ Worker-1: কাজ করছি... ⚙️ Worker-1: কাজ করছি... 🛑 Worker-1: ক্যানসেল সিগন্যাল পেলাম, কাজ বন্ধ করছি। 👋 main: প্রোগ্রাম শেষ।

বিশ্লেষণ: Worker-1 ঠিক ৩ সেকেন্ড কাজ করার পর <-ctx.Done() থেকে সিগন্যাল পায় এবং বন্ধ হয়ে যায়।


অনুশীলন ৩, ৪ এবং ৫: Context চেইন এবং ওয়ার্কার ক্যানসেলেশন

এখানে আমরা দেখব context কীভাবে একাধিক ফাংশনের মধ্যে (চেইন) পাস করা হয় এবং ক্যানসেলেশন কীভাবে কাজ করে।

package main import ( "context" "fmt" "time" ) // step3: আসল কাজ এখানে হয় (ডেটাবেস কোয়েরি সিমুলেশন) func step3(ctx context.Context) { fmt.Println(" [Step 3]: ডেটাবেস কোয়েরি শুরু... (২ সেকেন্ড লাগবে)") select { case <-time.After(2 * time.Second): // কাজ সম্পন্ন fmt.Println(" [Step 3]: ডেটাবেস কোয়েরি সফল।") case <-ctx.Done(): // কাজ শেষ হওয়ার আগেই ক্যানসেল সিগন্যাল এলো fmt.Println(" [Step 3]: 🛑 ডেটাবেস কোয়েরি ক্যানসেলড!") } } // step2: ডেটা প্রসেস করে func step2(ctx context.Context) { fmt.Println(" [Step 2]: ডেটা প্রসেসিং শুরু...") // কাজ করার আগে চেক করা (Early exit) if ctx.Err() != nil { fmt.Println(" [Step 2]: 🛑 শুরুতেই ক্যানসেল ছিলো, কাজ শুরুই করলাম না।") return } step3(ctx) // context নিচে পাস করা fmt.Println(" [Step 2]: ডেটা প্রসেসিং শেষ।") } // step1: রিকোয়েস্ট হ্যান্ডেল করে func step1(ctx context.Context) { fmt.Println("[Step 1]: রিকোয়েস্ট হ্যান্ডেলিং শুরু...") step2(ctx) // context নিচে পাস করা fmt.Println("[Step 1]: রিকোয়েস্ট হ্যান্ডেলিং শেষ।") } func main() { // === সিনারিও ১: সফল (টাইমআউটের আগেই কাজ শেষ) === fmt.Println("--- সিনারিও ১: সফল রিকোয়েস্ট (টাইমআউট ৩ সেকেন্ড) ---") ctxSuccess, cancelSuccess := context.WithTimeout(context.Background(), 3*time.Second) defer cancelSuccess() step1(ctxSuccess) fmt.Println("\n--- সিনারিও ২: ক্যানসেলড (টাইমআউট ১ সেকেন্ড) ---") // === সিনারিও ২: ক্যানসেলড (টাইমআউটের আগেই সিগন্যাল) === ctxTimeout, cancelTimeout := context.WithTimeout(context.Background(), 1*time.Second) defer cancelTimeout() step1(ctxTimeout) // ক্যানসেলেশনের প্রভাব দেখার জন্য একটু অপেক্ষা time.Sleep(2 * time.Second) fmt.Println("--- প্রোগ্রাম শেষ ---") }

আউটপুট:

--- সিনারিও ১: সফল রিকোয়েস্ট (টাইমআউট ৩ সেকেন্ড) --- [Step 1]: রিকোয়েস্ট হ্যান্ডেলিং শুরু... [Step 2]: ডেটা প্রসেসিং শুরু... [Step 3]: ডেটাবেস কোয়েরি শুরু... (২ সেকেন্ড লাগবে) [Step 3]: ডেটাবেস কোয়েরি সফল। [Step 2]: ডেটা প্রসেসিং শেষ। [Step 1]: রিকোয়েস্ট হ্যান্ডেলিং শেষ। --- সিনারিও ২: ক্যানসেলড (টাইমআউট ১ সেকেন্ড) --- [Step 1]: রিকোয়েস্ট হ্যান্ডেলিং শুরু... [Step 2]: ডেটা প্রসেসিং শুরু... [Step 3]: ডেটাবেস কোয়েরি শুরু... (২ সেকেন্ড লাগবে) [Step 3]: 🛑 ডেটাবেস কোয়েরি ক্যানসেলড! [Step 2]: ডেটা প্রসেসিং শেষ। [Step 1]: রিকোয়েস্ট হ্যান্ডেলিং শেষ। --- প্রোগ্রাম শেষ ---

বিশ্লেষণ:

  • সিনারিও ১: ৩ সেকেন্ডের টাইমআউট ছিলো, কিন্তু step3-এর কাজ ২ সেকেন্ডে শেষ হয়ে গেছে। তাই <-ctx.Done() সিগন্যাল আসার আগেই <-time.After() সিগন্যালটি জিতেছে।
  • সিনারিও ২: ১ সেকেন্ডের টাইমআউট ছিলো, কিন্তু step3-এর কাজ ২ সেকেন্ড লাগার কথা। select স্টেটমেন্টটি দেখলো যে <-ctx.Done() (১ সেকেন্ড) সিগন্যালটি <-time.After() (২ সেকেন্ড) সিগন্যালের আগে প্রস্তুত হয়েছে। তাই এটি ক্যানসেলেশন পাথটি বেছে নিলো।

🚫 সাধারণ ভুল এবং ✅ সঠিক ব্যবহার

Context খুব শক্তিশালী, কিন্তু এটি ভুলভাবে ব্যবহার করাও খুব সহজ।

সাধারণ ভুল (Avoid)✅ সঠিক ব্যবহার (Do)
context একটি struct-এর মধ্যে স্টোর করা।context-কে সবসময় ফাংশনের প্রথম আর্গুমেন্ট হিসেবে পাস করুন: func(ctx context.Context, ...)
context.Background() সব জায়গায় ব্যবহার করা।context.Background() শুধু main বা রিকোয়েস্টের শুরুতে ব্যবহার করুন। বাকি সব জায়গায় পাস করা ctx ব্যবহার করুন।
cancel() ফাংশন কল না করা।defer cancel() ব্যবহার করুন। context.With... কল করার সাথে সাথেই defer cancel() লিখে ফেলুন। এটি রিসোর্স লিক ঠেকায়।
context.TODO() প্রোডাকশন কোডে রেখে দেওয়া।TODO() শুধু তখনই ব্যবহার করুন যখন আপনি নিশ্চিত নন কোন context ব্যবহার করবেন (এবং পরে তা ঠিক করবেন)।
nil context পাস করা।nil পাস না করে, যদি কোনো context না থাকে তবে context.Background() পাস করুন।
context-এর মধ্যে ডেটা পাস করা (WithValue)।WithValue এড়িয়ে চলুন, যদি না রিকোয়েস্ট-স্কোপড ডেটা (যেমন trace ID, user ID) পাস করার দরকার হয়। এটি সিগন্যালিংয়ের জন্য নয়।

🔹 4. context.WithDeadline(parent, timePoint)

👉 WithTimeout এর মতোই, কিন্তু নির্দিষ্ট সময় (clock time) পর্যন্ত wait করে।

deadline := time.Now().Add(2 * time.Second) ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel()

🧩 Context Propagation কীভাবে কাজ করে?

Context হলো parent → child chain.

Background() └── WithCancel() └── WithTimeout() └── WithDeadline()

যদি parent cancel হয় → সব child context ও cancel হয়। এটাই বলে propagation


⚙️ কেন Context ফাংশনের প্রথম argument হয়?

👉 কারণ Go design principle অনুযায়ী context হলো request-scoped metadata। এটা function এর lifecycle নিয়ন্ত্রণ করে।

✅ Best Practice:

func fetchData(ctx context.Context, url string) error

❌ Wrong:

func fetchData(url string, ctx context.Context)

🧠 কারণ: Context সর্বদা উপরের দিক থেকে নিচের দিকে propagate হয়।


💡 Context কিভাবে Goroutine Leak আটকায়

Goroutine leak হয় যখন goroutine cancel signal না পেয়ে চিরদিন অপেক্ষা করে।

❌ উদাহরণ:

func worker(ch <-chan int) { for v := range ch { fmt.Println(v) } }

যদি ch কখনো close না হয় → goroutine চিরদিন block থাকবে।

✅ সমাধান: Context ব্যবহার করে বন্ধ করা।

func worker(ctx context.Context, ch <-chan int) { for { select { case v := <-ch: fmt.Println(v) case <-ctx.Done(): fmt.Println("Worker stopped") return } } }

💻 Exercises

🧩 1️⃣ Goroutine that stops when context is canceled

package main import ( "context" "fmt" "time" ) func main() { ctx, cancel := context.WithCancel(context.Background()) go func() { for { select { case <-ctx.Done(): fmt.Println("🛑 Goroutine stopped") return default: fmt.Println("🏃 Running...") time.Sleep(500 * time.Millisecond) } } }() time.Sleep(2 * time.Second) cancel() // stop after 2 seconds time.Sleep(1 * time.Second) }

🧩 2️⃣ WithTimeout — stop after 3 seconds

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() go func() { for { select { case <-ctx.Done(): fmt.Println("⏰ Timeout reached:", ctx.Err()) return default: fmt.Println("⚙️ Working...") time.Sleep(500 * time.Millisecond) } } }() time.Sleep(4 * time.Second)

🧩 3️⃣ Chain 3 Goroutines passing context downstream

func worker(ctx context.Context, name string) { for { select { case <-ctx.Done(): fmt.Println(name, "stopped") return default: fmt.Println(name, "working...") time.Sleep(700 * time.Millisecond) } } } func main() { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() go worker(ctx, "🧱 Worker 1") go worker(ctx, "🔧 Worker 2") go worker(ctx, "⚙️ Worker 3") <-ctx.Done() fmt.Println("All workers stopped:", ctx.Err()) }

🧩 4️⃣ Cleanup with <-ctx.Done()

select { case <-ctx.Done(): fmt.Println("Cleaning up resources...") return }

🧩 5️⃣ Worker respecting cancellation

func worker(ctx context.Context, id int) { for { select { case <-ctx.Done(): fmt.Printf("Worker %d shutting down\n", id) return default: fmt.Printf("Worker %d doing work\n", id) time.Sleep(500 * time.Millisecond) } } } func main() { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() for i := 1; i <= 3; i++ { go worker(ctx, i) } <-ctx.Done() fmt.Println("🛑 All workers canceled:", ctx.Err()) }

⚠️ Common Mistakes & Correct Usage

ভুল ব্যবহার ❌সঠিক ব্যবহার ✅
Context না পাঠানোসব goroutine/fn-এ context পাঠাও
Context শেষেও cancel() না করাসবসময় defer cancel() ব্যবহার করো
cancel() না ডাকাresource leak হতে পারে
context child chain না বানানোparent → child propagation ব্যবহার করো
context.TODO() ভুলভাবে ব্যবহারশুধুমাত্র placeholder হিসেবে ব্যবহার করো
<-ctx.Done() ignore করাসব worker এ respect করা উচিত

🧾 Summary (বাংলায় সারসংক্ষেপ)

বিষয়বর্ণনা
context.Background()root context
WithCancelmanual cancel
WithTimeoutনির্দিষ্ট সময় পর auto-cancel
WithDeadlineনির্দিষ্ট সময় পর্যন্ত valid
ctx.Done()context cancel signal
ctx.Err()কেন cancel হলো
Context chainparent cancel হলে সব child cancel
Leak preventiongoroutine cancel signal পেলে safely stop হয়

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

🚀 Go Context: The Nervous System of Production Apps

Go-তে Context কোনো সাধারণ ভেরিয়েবল নয়; এটি আপনার অ্যাপ্লিকেশনের Nervous System বা স্নায়ুতন্ত্র। একটি রিকোয়েস্ট যখন সার্ভারে ঢোকে, তখন Context তার সাথে পুরো সিস্টেমে প্রবাহিত হয় এবং নির্ধারণ করে রিকোয়েস্টটি বেঁচে থাকবে নাকি মারা যাবে (Cancel হবে)

Production Philosophy: “Never start a goroutine without knowing how it will stop.” — Context হলো সেই স্টপ বাটন।


১. Production Hierarchy (Context Tree)

প্রোডাকশন অ্যাপ্লিকেশনে Context সর্বদা একটি Tree এর মতো কাজ করে।

Background Context (Root) └─→ HTTP Request Context ├─→ Database Query Context ├─→ API Call Context └─→ Redis Cache Context
  • Root: context.Background() (অ্যাপ চালু হওয়ার সময়)।
  • Parent: যখন HTTP রিকোয়েস্ট আসে (http.Request).
  • Children: যখন আপনি DB কুয়েরি বা অন্য API কল করেন, তখন Parent থেকে নতুন Context তৈরি হয় (WithTimeout)।

Rule: যদি Parent মারা যায় (User cancel request), তবে তার সব Children (DB, API calls) সাথে সাথে সিগন্যাল পাবে এবং বন্ধ হয়ে যাবে। এটি সার্ভারের রিসোর্স বাঁচায়।


২. Context-এর প্রকারভেদ (Production Use-Cases)

Context Typeকখন ব্যবহার করবেন? (Real World)
Background()শুধু main() ফাংশন বা অ্যাপ স্টার্টআপে।
TODO()যখন কোড লিখছেন কিন্তু এখনো জানেন না কোন Context দেবেন (Refactoring-এর সময়)।
WithCancelযখন ম্যানুয়ালি কোনো প্রসেস বন্ধ করতে চান (যেমন: Graceful Shutdown)।
WithTimeoutসবচেয়ে বেশি ব্যবহৃত। DB Query, External API Call বা যেকোনো I/O অপারেশনে।
WithDeadlineযখন নির্দিষ্ট ঘড়ির সময় (যেমন: “রাত ১২টার আগে”) কাজ শেষ হতেই হবে।
WithValueশুধুমাত্র Request-Scoped Data (Trace ID, Auth Token) পাস করতে। কখনো ফাংশন প্যারামিটার পাস করবেন না।

৩. Production Pattern: HTTP Request Handling

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

func handler(w http.ResponseWriter, r *http.Request) { // ১. http.Request নিজেই একটি context নিয়ে আসে ctx := r.Context() fmt.Println("Processing request...") select { case <-time.After(5 * time.Second): // ভারী কাজ সিমুলেশন w.Write([]byte("Request Processed")) case <-ctx.Done(): // ২. যদি ইউজার ব্রাউজার কেটে দেয় err := ctx.Err() fmt.Println("Request canceled by user:", err) http.Error(w, err.Error(), http.StatusRequestTimeout) } }

৪. Production Pattern: Database Timeout (Crucial)

প্রোডাকশনে কখনো db.Query ব্যবহার করবেন না, সর্বদা db.QueryContext ব্যবহার করবেন। এটি ডেটাবেস হ্যাং হওয়া থেকে বাঁচায়।

func getUser(ctx context.Context, db *sql.DB, id int) { // ১. প্যারেন্ট context থেকে নতুন context, যার মেয়াদ মাত্র ৫০ মিলি-সেকেন্ড queryCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond) defer cancel() // অবশ্যই কল করবেন // ২. QueryContext ব্যবহার করুন // যদি ৫০ms-এর মধ্যে DB উত্তর না দেয়, এটি অটোমেটিক ক্যান্সেল হবে row := db.QueryContext(queryCtx, "SELECT name FROM users WHERE id=?", id) // ... scan and return }

৫. Context Values: The Dark Side (সতর্কতা)

context.WithValue খুব বিতর্কিত। এটি অনেকটা গ্লোবাল ভেরিয়েবলের মতো।

❌ যা কখনো Context-এ রাখবেন না:

  • Database Connection
  • Logger objects
  • Configuration
  • ফাংশনের প্রয়োজনীয় আর্গুমেন্ট (যেমন username, orderID)

✅ যা Context-এ রাখতে পারেন (Request Scoped Meta):

  • Trace ID / Request ID (লগ ট্রেসিংয়ের জন্য)
  • User ID / Role ( অথেন্টিকেশন মিডলওয়্যার থেকে)
  • IP Address

Best Practice Code:

// Key-এর জন্য আলাদা type ব্যবহার করুন যাতে collision না হয় type key int const userKey key = 1 // Value সেট করা func WithUser(ctx context.Context, u *User) context.Context { return context.WithValue(ctx, userKey, u) } // Value বের করা func UserFrom(ctx context.Context) (*User, bool) { u, ok := ctx.Value(userKey).(*User) return u, ok }

৬. Advanced: Detached Context (Fire and Forget)

মাঝেমধ্যে প্যারেন্ট রিকোয়েস্ট শেষ হয়ে গেলেও আমাদের কিছু কাজ (যেমন: লগ সেভ করা, মেট্রিক্স পাঠানো) চালিয়ে যেতে হয়। তখন WithoutCancel (Go 1.21+) ব্যবহার করা হয়।

func mainHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // সাধারণ কাজ processData(ctx) // রিকোয়েস্ট শেষ, কিন্তু আমরা লগ সেভ করতে চাই // প্যারেন্ট (ctx) ক্যানসেল হলেও এটি ক্যানসেল হবে না go saveAuditLog(context.WithoutCancel(ctx)) }

📝 Production Checklist for Context

কোড রিভিউ করার সময় এই পয়েন্টগুলো চেক করুন:

  1. First Parameter: ctx কি ফাংশনের প্রথম আর্গুমেন্ট হিসেবে আছে? (স্ট্যান্ডার্ড প্র্যাকটিস)।
  2. Struct Field: Context কি কোনো struct-এর ভেতরে ফিল্ড হিসেবে রাখা হয়েছে? (Never do this!)। Context প্রতিটি ফাংশন কলে পাস করতে হয়।
  3. Cancel Call: WithTimeout বা WithCancel কল করার ঠিক পরেই defer cancel() দেওয়া হয়েছে তো? (না হলে মেমরি লিক হবে)।
  4. Nil Context: কোথাও কি nil পাস করা হচ্ছে? nil এর বদলে context.TODO() বা context.Background() ব্যবহার করুন।
  5. Monitor: select { case <-ctx.Done(): ... } দিয়ে কি লং রানিং প্রসেস মনিটর করা হচ্ছে?

🆚 Context vs Channels (কনফিউশন দূরীকরণ)

বৈশিষ্ট্যContextChannels
মূল কাজSignaling & Lifecycle Management (কখন কাজ থামবে)।Data Flow (ডেটা আদান-প্রদান)।
Data Typesমেটাডেটা (Request ID, Token)।অ্যাপ্লিকেশনের আসল ডেটা (Struct, Int, String)।
Directionউপর থেকে নিচে (Parent → Child)।দুই দিকেই হতে পারে (কিন্তু সাধারণত Unidirectional ভালো)।

💡 Final Pro Tip for Interviews

যদি প্রশ্ন করে: “Go তে Context কেন ব্যবহার করা হয়?”

উত্তর: > “Context মূলত তিনটি কাজের জন্য ব্যবহৃত হয়:

১. Timeout & Cancellation Control (যাতে Goroutine leak না হয়)। ২. Propagation (প্যারেন্ট প্রসেস বন্ধ হলে চাইল্ড প্রসেসগুলোও যাতে বন্ধ হয়)। ৩. Request Scoped Values পাস করা (যেমন Trace ID)।”