Keep control of goroutines with a Context construct
Simpler with Context
To wrap this common synchronization pattern into an easily deployed package, the Go standard library provides Context
structs [1]. They are used in Google's data centers by servers, which often have to call many goroutines to compile the results for incoming user requests. If this takes too long, the main function handling the request must be able to contact all the goroutines that are still running, forcing them to immediately abandon their current work, release any allocated resources, and terminate their program flow.
The context's interface uses Done()
to return an open channel from which the worker bees try to read. However, no message ever reaches the channel (like in the previous example). Instead, the main program calls close()
to close the channel abstracted in the struct when it's time for the final whistle, using the context's exported Cancel()
function, which suddenly gives the reading workers an error value. They field this and interpret it as a signal to close up shop.
Listing 2 imports context
to introduce the standard library package of the same name in line 4. The context.WithCancel()
function extends a standard context created by context.Background()
and returns two things: a context object in ctx
and a cancel()
function that the programmer calls later on (in line 17 in Listing 2) to send the signal to end the party and turn off the lights.
Listing 2
context.go
01 package main 02 03 import ( 04 "context" 05 "fmt" 06 "time" 07 ) 08 09 func main() { 10 ctx, cancel := context.WithCancel(context.Background()) 11 12 work(ctx) 13 work(ctx) 14 work(ctx) 15 16 time.Sleep(3 * time.Second) 17 cancel() 18 time.Sleep(3 * time.Second) 19 } 20 21 func work(ctx context.Context) { 22 go func() { 23 for i := 0; i < 10; i++ { 24 fmt.Printf("%d\n", i) 25 26 select { 27 case <-ctx.Done(): 28 fmt.Printf("Ok. I quit.\n") 29 return 30 case <-time.After(time.Second): 31 } 32 } 33 }() 34 }
Worker bees use ctx.Done()
to extract the channel to be monitored from the context and insert a case
statement with a read operation into their select loops; they then use this mechanism to receive control commands from their caller. After compilation, the output from Listing 2 looks exactly like Figure 1 and exhibits exactly the same behavior. This is not surprising, since the context implementation uses the same internal infrastructure.
Inside Google's Brain
In Google's data centers, all worker functions use a context variable as their first parameter; this controls any premature termination that may be necessary. However, it also helps pass down payloads of received requests, such as the name of the authenticated user or credentials for subsystems. In this way, all subsystems across all API boundaries support certain standard functions such as timeouts, cleanup signals due to unsolvable problems, or simply convenient access to global key/value values.
Brake and Lose
Listing 3 shows what this kind of server function could look like in a practical use case. It retrieves four URLs: the Google, Facebook, and Amazon splash pages, along with the artificially slowed down website deelay.me
. Line 23 shows that it calls the AOL website with a delay of 5,000 milliseconds to make the client think it has a lame Internet connection.
Listing 3
delay.go
01 package main 02 03 import ( 04 "context" 05 "fmt" 06 "net/http" 07 "time" 08 ) 09 10 type Resp struct { 11 rcode int 12 url string 13 } 14 15 func main() { 16 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 17 defer cancel() 18 19 urls := []string{ 20 "https://www.google.com", 21 "https://www.facebook.com", 22 "https://www.amazon.com", 23 "https://deelay.me/5000/www.aol.com", 24 } 25 26 results := make(chan Resp) 27 28 for _, url := range urls { 29 chkurl(ctx, url, results) 30 } 31 32 for _ = range urls { 33 resp := <-results 34 fmt.Printf("Received: %d %s\n", resp.rcode, resp.url) 35 } 36 } 37 38 func chkurl(ctx context.Context, url string, results chan Resp) { 39 fmt.Printf("Fetching %s\n", url) 40 httpch := make(chan int) 41 42 go func() { 43 // async url fetch 44 go func() { 45 resp, err := http.Get(url) 46 if err != nil { 47 httpch <- 500 48 } else { 49 httpch <- resp.StatusCode 50 } 51 }() 52 53 select { 54 case result := <-httpch: 55 results <- Resp{ 56 rcode: result, url: url} 57 case <-ctx.Done(): 58 fmt.Printf("Timeout!!\n") 59 results <- Resp{ 60 rcode: 501, url: url} 61 } 62 }() 63 }
The main
program now goes through these URLs in the for
loop starting in line 28 and passes each one to the chkurl()
function, along with a context variable and a results
channel. The latter returns the workers' results to the main program in the form of Resp
structures. This data type, defined starting in line 10, stores the URL obtained along with the request's HTTP return code.
In the process, chkurl()
processes the requests asynchronously. It starts a goroutine from line 42 for time-consuming retrieval over the web and therefore quickly returns to the main program. Results bubble up later on via the results
channel, where the for
loop collects them starting in line 32, outputting the URLs along with their numeric result codes.
« Previous 1 2 3 Next »
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
So Long Neofetch and Thanks for the Info
Today is a day that every Linux user who enjoys bragging about their system(s) will mourn, as Neofetch has come to an end.
-
Ubuntu 24.04 Comes with a “Flaw"
If you're thinking you might want to upgrade from your current Ubuntu release to the latest, there's something you might want to consider before doing so.
-
Canonical Releases Ubuntu 24.04
After a brief pause because of the XZ vulnerability, Ubuntu 24.04 is now available for install.
-
Linux Servers Targeted by Akira Ransomware
A group of bad actors who have already extorted $42 million have their sights set on the Linux platform.
-
TUXEDO Computers Unveils Linux Laptop Featuring AMD Ryzen CPU
This latest release is the first laptop to include the new CPU from Ryzen and Linux preinstalled.
-
XZ Gets the All-Clear
The back door xz vulnerability has been officially reverted for Fedora 40 and versions 38 and 39 were never affected.
-
Canonical Collaborates with Qualcomm on New Venture
This new joint effort is geared toward bringing Ubuntu and Ubuntu Core to Qualcomm-powered devices.
-
Kodi 21.0 Open-Source Entertainment Hub Released
After a year of development, the award-winning Kodi cross-platform, media center software is now available with many new additions and improvements.
-
Linux Usage Increases in Two Key Areas
If market share is your thing, you'll be happy to know that Linux is on the rise in two areas that, if they keep climbing, could have serious meaning for Linux's future.
-
Vulnerability Discovered in xz Libraries
An urgent alert for Fedora 40 has been posted and users should pay attention.