# Golang Context

## What is golang context
Context is used to pass request-scope values, and control timeout, deadline and cancel of sub goroutines in a request.

## Common use cases.
* Pass value through context.
* Set timeout or deadline to context
* Cancel context, and stop run sub goroutines.

```go
import (
	"context"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/parnurzeal/gorequest"
)

type ctxKeyTp struct{}

var ctxKey = ctxKeyTp{}

type result struct {
	content string
	err     error
}

func CrawlWeb(ctx context.Context, url string) (*result, error) {
	ctxValue, ok := ctx.Value(ctxKey).(string)
	log.Printf("get value from context(%v): %s, %t\n", ctxKey, ctxValue, ok)

	var c = make(chan result, 1)
	go func() {
		now := time.Now()
		request := gorequest.New()
		_, body, errs := request.Get(url).End()
		if len(errs) > 0 {
			var errStr []string
			for _, e := range errs {
				errStr = append(errStr, e.Error())
			}

			log.Printf("get error: %v, at: %v\n", errStr, time.Since(now))
			c <- result{err: fmt.Errorf(strings.Join(errStr, ","))}
			return
		}
		log.Printf("get result: %s, at: %v\n", body, time.Since(now))
		c <- result{content: body}
	}()

	select {
	// when context canceled, timeout or deadline, then done channel will be closed.
	// returns a channel that's closed when work done
	case <-ctx.Done():
		// Err() if Done is closed, then Err returns non nil error of the reason(canceled, deadlined)
		ctxErr := ctx.Err()
		log.Printf("ctx done, error: %v\n", ctxErr)
		return nil, ctxErr
	case res := <-c:
		return &res, nil
	}
}
``` 


## Context timeout
WithTimeout arranges for Done to be closed when the timeout elapses.


```go
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}
// get value from context({}): , false
// ctx done, error: context deadline exceeded
// since:  502.230142ms
func TestCrawlWebTimeout(t *testing.T) {
	timeOut := 500 * time.Millisecond
	// context.WithTimeout returns a copy of parent context with timout
	ctx, cancel := context.WithTimeout(context.Background(), timeOut)
	defer cancel()

	start := time.Now()
	res, err := CrawlWeb(ctx, "https://blog.golang.org/context")
	fmt.Println("since: ", time.Since(start))
	assert.NotNil(t, err)

	// After 500ms, CrawlWeb don't finish, So returns ctx.Err
	assert.Equal(t, context.DeadlineExceeded, err)
	assert.Nil(t, res)
}
``` 

## Context Deadline
WithDeadline arranges for Done to be closed when the deadline expires.
returns copy of parent context with the deadline adjusted to be no later then d.


```go
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
...
}
``` 

Deadline Context Also returns context.DeadlineExceeded error same with WithTimeout


```go
// ctx done, error: context deadline exceeded
// res:  <nil>
// err:  context deadline exceeded
func TestCrawlWebWithDeadline(t *testing.T) {
	timeOut := 500 * time.Millisecond
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeOut))
	defer cancel()

	res, err := CrawlWeb(ctx, "https://blog.golang.org/context")
	log.Println("res: ", res)
	log.Println("err: ", err)
	assert.NotNil(t, err)
	assert.Equal(t, context.DeadlineExceeded, err)
	assert.Nil(t, res)
}
``` 

## Cancel context
* WithCancel returns a copy of parent with a new Done channel.
* When call returned cancel func, then the Done channel will be closed. And context.Err() returns context.Canceled error.


```go
// 500ms reached, cancel this request
// ctx done, error: context canceled
// res:  <nil> , error:  context canceled
func TestCrawlWebCancel(t *testing.T) {
	type crawlResult struct {
		res *result
		err error
	}
	var c = make(chan crawlResult, 1)
	// if the result not returned in 1 second will cancel this.
	timer := time.NewTimer(500 * time.Millisecond)
	defer timer.Stop()

	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		res, err := CrawlWeb(ctx, "https://blog.golang.org/context")
		log.Println("res: ", res, ", error: ", err)
		assert.Equal(t, context.Canceled, err)
		c <- crawlResult{
			res: res,
			err: err,
		}
	}()
	select {
	case <-timer.C: // reached 500ms, but not get result. cancel this request.
		log.Println("500ms reached, cancel this request")
		cancel()
	case res := <-c:
		log.Println("get result in 500ms, great!")
		assert.Nil(t, res.err)
		assert.NotNil(t, res.res)
	}
}
``` 

## Pass value through context.

```go
// get value from context({}): Hello,, true
func TestPassValueThroughContext(t *testing.T) {
	ctx := context.WithValue(context.Background(), ctxKey, "Hello,")
	res, err := CrawlWeb(ctx, "https://blog.golang.org/context")
	assert.Nil(t, err)
	assert.NotNil(t, res)
}
``` 

## Receive context Value from context.


```go
ctxValue, ok := ctx.Value(ctxKey).(string)
log.Printf("get value from context(%v): %s, %t\n", ctxKey, ctxValue, ok)
``` 


