Fragment Description:



context.Context is meant to help timing out or ending early long running operations.
A Context carries a deadline, a cancellation signal, and other values across API boundaries.
Context's methods may be called by multiple goroutines simultaneously.
A note about Context.Value:
it should inform, not control.
'Inform, not control.
This is the primary mantra that I feel should guide if you are using context.Value correctly.
The content of context.Value is for maintainers not users.
It should never be a required input for documented or expected results.
To clarify, if your function can’t behave correctly because of a value that may or may not be inside context.Value, then your API is obscuring required inputs too heavily.
said Jack Lindamood in his excellent article 'https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39#.ifkmu2vxn".


goroutineControlWithContext

Go Playground

Last update, on 2017, Tue 7 Feb, 17:39:32

/* ... <== see fragment description ... */

package main

import (
    "bufio"
    "fmt"
    "context"
    "math/rand"
    "os"
    "strings"
    "time"
)

type Transaction struct {
    Name string
    ID   int64
}

func ProcessTransaction(ctx context.Context, Transaction *Transaction) {
    // sign: func (c *valueCtx) Value(key interface{}) interface{}
    // here Value will just inform us about the status of Transaction
    completed := ctx.Value("completed").(chan struct{}) // type assertion
    for {
        select {
        case <-completed:
            fmt.Printf("\nThe Transaction of [%d] has been completed.\n", Transaction.ID)
            return
        case <-ctx.Done():
            // Done() causes: either by a Cancellation or a Deadline being exceeded
            if ctx.Err() == context.Canceled {
                fmt.Printf("\nThe Transaction [%d] has been canceled.\n", Transaction.ID)
                return
            } else if ctx.Err() == context.DeadlineExceeded {
                fmt.Printf("\nThe DELAY for Transaction has expired. The Transaction with ID [%d] is being terminated.\n", Transaction.ID)
                os.Exit(0)
            }
        default:
            time.Sleep(time.Duration(rand.Int63n(0.15 * 1e9)))
        }
    }
}
func main() {
    // let's define a signalling channel
    completed := make(chan struct{})
    var ctx context.Context
    var cancel context.CancelFunc
    // let's configure our context
    ctx = context.WithValue(context.Background(), "completed", completed)
    ctx, cancel = context.WithTimeout(ctx, 8*time.Second)
    txn := &Transaction{
        Name: "ourControlledTransaction",
        ID:   20170204}
    // start a goroutine with a context to control it
    go ProcessTransaction(ctx, txn)
    // a command line simple user interface
    fmt.Printf("-----\nThe Transaction [%d] is waiting for your input.\n", txn.ID)
    deadline, ok := ctx.Deadline()
    if ok {
        fmt.Printf("When the DELAY of %q will be passed, the Transaction will be terminated [%d].\n-----\n", deadline.Sub(time.Now()).String(), txn.ID)
    }
    // fmt.Println()
    fmt.Println("Please choose an option:")
    reader := bufio.NewReader(os.Stdin)
    for {
        fmt.Print("\n[P]rocess, [C]ancel: ")
        // capture input from Stdin
        line, err := reader.ReadString('\n')
        if err == nil {
            command := strings.TrimSuffix(line, "\n")
            switch command {
            case "P":
                fmt.Printf("\nThe Transaction [%d] is being processed!\n", txn.ID)
                // let's simulate some work is being done
                time.Sleep(200 * time.Millisecond)
                // let's signal the transaction has been processed and is completed
                completed <- struct{}{}
                return
            case "C":
                fmt.Printf("\nThe Transaction [%d] is being cancelled!\n", txn.ID)
                // let's start the txn...
                time.Sleep(200 * time.Millisecond)
                // let's cancel the txn
                cancel()
                return
            default:
                fmt.Printf("Wrong option: %q. Please try again.\n", command)
            }
        }
    }
}

/* Expected output:
-----
The Transaction [20170204] is waiting for your input.
When the DELAY of "7.999953293s" will be passed, the Transaction will be terminated [20170204].
-----
Please choose an option:
[P]rocess, [C]ancel: P
if P:  The Transaction [20170204] is being processed!
if C:  The Transaction [20170204] is being cancelled!
if other key:  Wrong option: "f". Please try again.
               [P]rocess, [C]ancel:
if nothing:    The DELAY for Transaction has expired. The Transaction with ID [20170204] is being terminated.
*/



Comments