Skip to content

Commit b2c24b6

Browse files
committed
add example context/cancel3
1 parent ffdad4e commit b2c24b6

File tree

7 files changed

+141
-11
lines changed

7 files changed

+141
-11
lines changed

README.md

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
# Notes: Golang Advanced Concurrency Concepts
22

33
The tutorial consist of the following modules (in sense of Go Modules):
4-
* `context/server`
5-
* `context/cancellation`
4+
* `context/cancel1`
5+
* `context/cancel2`
6+
* `context/cancel3`
7+
* `context/emit`
8+
* `context/listen`
9+
* `context/timeout`
10+
* `context/value`
611
* `error_group`
712
* `errors_in_waitgroup`
813
* `gosched`
@@ -23,6 +28,34 @@ There are two sides to the context cancellation, that can be implemented:
2328
* Listening for the cancellation event,
2429
* Emitting the cancellation event.
2530

31+
Things to remember about `context`:
32+
* A `context` object can be cancelled only once
33+
* Use it when you want to actually cancell an operation, not when you want to propagate errors
34+
* Wrapping a cancellable context with `WithTimeout` or other functions make the context object cancellable in too many ways.
35+
* Pass a child context to a goroutine you start or pass an independent context object. Here is an example:
36+
```
37+
func parent(rootCtx context.Context) {
38+
ctx, cancel := context.WithCancel(rootCtx)
39+
defer cancel()
40+
41+
someArg := "loremipsum"
42+
go child(context.Background(), someArg)
43+
}
44+
```
45+
Note: calling `cancel()` in the parent goroutine may cancel the child goroutine because there is no synchronization of the child - `parent()` (and thus its `defer`) does not wait for `child()`.
46+
* do not pass the `cancel()` function downstream. It will result in the invoker of the cancellation function not knowning what the upstream impact of cancelling the context may be eg. there may be other contexts that are derived from the cancelled context.
47+
48+
49+
Context factory methods:
50+
* for a parent/new context: `Background()`
51+
* for a cancellation context: `WithCancel()`
52+
* for a time-limited context: `WithTimeout()`, `WithDeadline()`
53+
* for a key-value storing context: `WithValue()`
54+
* for an empty context:`TODO()`.
55+
56+
How to accept/use `context` objects in downstream:
57+
58+
2659

2760
### `context/listen`
2861
In this example an HTTP server on port 9000 is set up. It processes a request within 2 seconds.

context/cancel1/go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module cancel1
2+
3+
go 1.14

context/cancel1/main.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
)
8+
9+
/*
10+
The program shows how to use context cancellation in a sub-task.
11+
However it contains a logical error worth considering:
12+
You cannot call `cancel()` twice,
13+
therfore you cannot use the same context object to cancel multiple goroutines.
14+
*/
15+
func main() {
16+
ctx := context.Background()
17+
ctx, cancel := context.WithCancel(ctx)
18+
go foo(ctx, "subtask-1")
19+
go foo(ctx, "subtask-2") //note: one of the goroutines will not be cancelled, the main routine will just cause it to end!
20+
time.Sleep(100 * time.Millisecond)
21+
cancel() //note: calling `cancel()` more than once does not yield any results.
22+
//cancel()
23+
fmt.Println("Back to the main routine")
24+
}
25+
26+
func foo(ctx context.Context, str string) {
27+
fmt.Printf("Entering foo() with message: [%s]\n", str)
28+
select {
29+
case <-time.After(500 * time.Millisecond):
30+
fmt.Printf("Executing foo() with message: [%s]\n", str)
31+
case <-ctx.Done():
32+
fmt.Printf("Cancelling foo() with message: [%s]\n", str)
33+
return
34+
}
35+
fmt.Printf("Finishing foo() with message: [%s]\n", str)
36+
}

context/cancel3/go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module cancel3
2+
3+
go 1.14

context/cancel3/main.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
/*
9+
See godoc: https://golang.org/pkg/context/#WithCancel
10+
*/
11+
func main() {
12+
ctx, cancel := context.WithCancel(context.Background())
13+
14+
for n := range yield(1, ctx) {
15+
fmt.Println(n)
16+
if n == 5 { //We want to consume only this number of elements from the channel
17+
// (1)
18+
//break
19+
// cancel()
20+
break
21+
}
22+
}
23+
24+
// (2)
25+
cancel() //or alternatively: defer cancel() // cancel when we are finished consuming integers
26+
}
27+
28+
/*
29+
`yield` generates integers in a separate goroutine and
30+
sends them to the returned channel.
31+
The callers of gen need to cancel the context once
32+
they are done consuming generated integers not to
33+
leak the internal goroutine started by gen.
34+
*/
35+
func yield(start int, ctx context.Context) <-chan int {
36+
dst := make(chan int)
37+
n := start
38+
go func() {
39+
for {
40+
select {
41+
case <-ctx.Done():
42+
// (3)
43+
// close(dst)
44+
return // returning not to leak the goroutine
45+
case dst <- n:
46+
n++
47+
}
48+
}
49+
}()
50+
return dst
51+
}

context/timeout/go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module timeout
2+
3+
go 1.14

context/timeout/main.go

+10-9
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,25 @@ import (
88
)
99

1010
func main() {
11-
// Create a new context
12-
// With a deadline of 100 milliseconds
11+
/*
12+
Create a new context with a deadline of 100 milliseconds.
13+
If 100 ms -> most likely you will get a timeout error passed through the context.
14+
If eg 500 ms -> most likely you will get 200 OK and no context cancellation.
15+
*/
1316
ctx := context.Background()
1417
ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond)
1518

16-
// Make a request, that will call the google homepage
19+
// Make a request, that will call some HTTP server
1720
req, _ := http.NewRequest(http.MethodGet, "http://google.com", nil)
1821
// Associate the cancellable context we just created to the request
1922
req = req.WithContext(ctx)
20-
2123
// Create a new HTTP client and execute the request
22-
client := &http.Client{}
23-
res, err := client.Do(req)
24-
// If the request failed, log to STDOUT
24+
res, err := (&http.Client{}).Do(req)
25+
// If the request failed, log to the standard output
2526
if err != nil {
26-
fmt.Println("Request failed:", err)
27+
fmt.Println("Request failed:", err) //`err` contains the info about time limit excess
2728
return
2829
}
2930
// Print the statuscode if the request succeeds
30-
fmt.Println("Response received, status code:", res.StatusCode)
31+
fmt.Println("Response received with status code:", res.StatusCode)
3132
}

0 commit comments

Comments
 (0)