Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
141: context.WithCancel and context.WithTimeout
Imagine you've hired a contractor to renovate your kitchen. You tell them, "Go ahead and start tearing out the old cabinets." But while they're working, you suddenly realize you can't afford the project anymore. You don't just walk away and hope they stop; you call them and say, "Stop everything immediately. Don't spend another dime or another hour on this."
Or, imagine you're ordering a pizza. You're starving, but you have a flight to catch in 40 minutes. You tell the shop, "I want a large pepperoni, but if it's not at my door in 20 minutes, I'm cancelling the order because I'll be at the airport."
In Go, context.WithCancel and context.WithTimeout are exactly those phone calls. They are the mechanism we use to tell a goroutine—which might be deep inside a database driver or a network call—that its work is no longer needed. Without this, you end up with "goroutine leaks," where processes keep running in the background, eating CPU and memory, long after the user has closed their browser tab.
Cutting the Cord Manually
context.WithCancel gives you a way to trigger a shutdown whenever you feel like it. It returns a copy of the parent context and a cancel function. When you call that function, the ctx.Done() channel closes. Any goroutine listening to that channel knows it's time to pack up and go home.
I've seen a lot of developers forget to call the cancel function, which is a mistake. Even if the work finishes successfully, you should call cancel() to release the resources associated with that context. The idiomatic way to do this is with defer.
package main
import (
"context"
"fmt"
"time"
)
func heavyWork(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Worker: Stopping work because I was cancelled!")
return
default:
fmt.Println("Worker: Still grinding away...")
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
// Create a cancelable context
ctx, cancel := context.WithCancel(context.Background())
// I'm deferring this so we don't leak resources
// even if the function panics.
defer cancel()
go heavyWork(ctx)
// Let the worker run for a bit
time.Sleep(2 * time.Second)
fmt.Println("Main: I've changed my mind. Stop!")
cancel() // This triggers <-ctx.Done() in the goroutine
// Give the worker a moment to print its exit message
time.Sleep(100 * time.Millisecond)
}
Setting a Hard Deadline
In a production environment, you can't wait forever for an API response. If a third-party service is hanging, you don't want your own users staring at a loading spinner for thirty seconds. This is where context.WithTimeout comes in.
It works exactly like WithCancel, but it adds a timer. The context will be cancelled automatically after the duration expires, OR when you call the cancel function manually—whichever happens first.
package main
import (
"context"
"fmt"
"time"
)
func simulateApiCall(ctx context.Context) {
select {
case <-time.After(5 * time.Second): // Simulate a slow network
fmt.Println("API: Finally finished the request!")
case <-ctx.Done():
fmt.Println("API: Request aborted because it took too long.")
}
}
func main() {
// We only want to wait 2 seconds for this API
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
go simulateApiCall(ctx)
// Wait to see what happens
<-ctx.Done()
fmt.Println("Main: Moving on...")
}
The "Done" Pattern
The most important thing to remember here is that context is not a magic "kill switch." It cannot forcibly terminate a goroutine. If you write a goroutine that just does for { ... } without ever checking <-ctx.Done(), that goroutine will run forever, regardless of whether you called cancel() or the timeout expired.
You are essentially asking the goroutine to please stop. It's the goroutine's responsibility to be "context-aware" and check for that signal frequently, especially before starting a heavy operation or during a long loop.
📋 Practical Task
Build a Fault-Tolerant Image Processor
You are building a system that processes high-resolution images. Some images are small and fast, while others are massive and might hang your processor. Your goal is to ensure that no single image processing task takes longer than 3 seconds.
Requirements:
- Create a function
processImage(ctx context.Context, imageName string, duration time.Duration). - Inside
processImage, use aselectblock to either simulate the work (usingtime.Afterwith the provided duration) or stop if the context is cancelled. - In your
mainfunction, trigger two separate image processes:- "image_small.jpg" which takes 1 second to process.
- "image_huge.jpg" which takes 10 seconds to process.
- Use
context.WithTimeoutto enforce the 3-second limit for both. - Print a message indicating whether each image was "Successfully Processed" or "Timed Out".
There are no comments for now.