Golang cli application - how to use context properly? - go

I'm new to golang and somewhat confused about context and how to use the context in golang applications.
Specifically im working on the cli application and just need to access mongo, for example.
Like - is this correct that I just create single shared ctx context variable, then use it for any operations that need context?
Would any operation that needs context restart the 5-second timer? or is this a shared timer?
package main
import (
"context"
"log"
"os"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
func main() {
log.SetOutput(os.Stdout)
// Create a context with a timeout of 5 seconds
//This defines a timeout context that will be canceled after 5 seconds.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// always defer in case func returns early
defer cancel()
//Creates a new ClientOptions instance.
clientOptions := options.Client()
clientOptions = clientOptions.ApplyURI("mongodb+srv://127.0.0.1?retryWrites=true&w=majority")
//Connect to mongo
client, err := mongo.Connect(ctx, clientOptions)
defer client.Disconnect(ctx)
if err != nil {
log.Fatal(err)
}
//Test connection to the database
log.Println("I: test mongo connection using ping")
err = client.Ping(ctx, readpref.Primary())
if err != nil {
log.Fatal(err)
}
log.Println("I: Fin")
}

If you think about it, it makes no sense that a context.Context could be shared "horizontally" (meaning between operations not part of the same call stack). A golang Context provides the context within which an operation (including any nested operations below it in the call stack) are to be performed - such as "within X seconds," to protect against hanging due to communications delays, etc. So if you issue 10 requests in parallel, you should give each one its own context - you probably don't want the tenth one to fail because the first one did. If you are just using a context.Background() or context.TODO(), without further decoration, you probably don't need to store the Context in a variable the first time you create it - you can just create when you pass it to the first function in the call stack, and properly constructed code will pass it down the stack as appropriate, applying necessary decorations along the way:
func Execute() {
DoThing(context.Background())
// Other stuff
}
func DoThing(pctx context.Context) {
ctx, cancel := context.WithTimeout(pctx, 10 * time.Second) // Timeout after 10 seconds
defer cancel()
DoThingThatMayTakeAWhile(ctx)
select {
// You may want other stuff here
case <-ctx.Done():
// Context timed out, maybe log an error?
}
}
func DoThingThatMayTakeAWhile(pctx context.Context) {
DoThingNeedingInfoInContext(context.WithValue(pctx, "thisisakey", "value"))
}
func DoThingNeedingInfoInContext(ctx context.Context) {
val := ctx.Value("thisisakey")
// Do something with val, check that it isn't nil, etc.
}
If I were to make multiple calls to DoThingThatMayTakeAWhile(), I'd want to give each one a separate child context - I would not want to share ctx with each of them.
So in your code, every call to mongo.Connect() should receive a freshly created context.Context instance.

You create a new context variable for each operations that need context.
The timer of a context will never restart.
In your example, try to add time.Sleep(6*time.Second) after context.WithTimeout, you will see all operations return error context deadline exceeded.

Related

In Go, what is the proper way to use context with pgx within http handlers?

Update 1: it seems that using a context tied to the HTTP request may lead to the 'context canceled' error. However, using the context.Background() as the parent seems to work fine.
// This works, no 'context canceled' errors
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
// However, this creates 'context canceled' errors under mild load
// ctx, cancel := context.WithTimeout(r.Context(), 100*time.Second)
defer cancel()
app.Insert(ctx, record)
(updated code sample below to produce a self-contained example for repro)
In go, I have an http handler like the following code. On the first HTTP request to this endpoint I get a context cancelled error. However, the data is actually inserted into the database. On subsequent requests to this endpoint, no such error is given and data is also successfully inserted into the database.
Question: Am I setting up and passing the context correctly between the http handler and pgx QueryRow method? (if not is there a better way?)
If you copy this code into main.go and run go run main.go, go to localhost:4444/create and hold ctrl-R to produce a mild load, you should see some context canceled errors produced.
package main
import (
"context"
"fmt"
"log"
"math/rand"
"net/http"
"time"
"github.com/jackc/pgx/v4/pgxpool"
)
type application struct {
DB *pgxpool.Pool
}
type Task struct {
ID string
Name string
Status string
}
//HTTP GET /create
func (app *application) create(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.Path, time.Now())
task := &Task{Name: fmt.Sprintf("Task #%d", rand.Int()%1000), Status: "pending"}
// -------- problem code here ----
// This line works and does not generate any 'context canceled' errors
//ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
// However, this linegenerates 'context canceled' errors under mild load
ctx, cancel := context.WithTimeout(r.Context(), 100*time.Second)
// -------- end -------
defer cancel()
err := app.insertTask(ctx, task)
if err != nil {
fmt.Println("insert error:", err)
return
}
fmt.Fprintf(w, "%+v", task)
}
func (app *application) insertTask(ctx context.Context, t *Task) error {
stmt := `INSERT INTO task (name, status) VALUES ($1, $2) RETURNING ID`
row := app.DB.QueryRow(ctx, stmt, t.Name, t.Status)
err := row.Scan(&t.ID)
if err != nil {
return err
}
return nil
}
func main() {
rand.Seed(time.Now().UnixNano())
db, err := pgxpool.Connect(context.Background(), "postgres://test:test123#localhost:5432/test")
if err != nil {
log.Fatal(err)
}
log.Println("db conn pool created")
stmt := `CREATE TABLE IF NOT EXISTS public.task (
id uuid NOT NULL DEFAULT gen_random_uuid(),
name text NULL,
status text NULL,
PRIMARY KEY (id)
); `
_, err = db.Exec(context.Background(), stmt)
if err != nil {
log.Fatal(err)
}
log.Println("task table created")
defer db.Close()
app := &application{
DB: db,
}
mux := http.NewServeMux()
mux.HandleFunc("/create", app.create)
log.Println("http server up at localhost:4444")
err = http.ListenAndServe(":4444", mux)
if err != nil {
log.Fatal(err)
}
}
TLDR: Using r.Context() works fine in production, testing using Browser is a problem.
An HTTP request gets its own context that is cancelled when the request is finished. That is a feature, not a bug. Developers are expected to use it and gracefully shutdown execution when the request is interrupted by client or timeout. For example, a cancelled request can mean that client never see the response (transaction result) and developer can decide to roll back that transaction.
In production, request cancelation does not happen very often for normally design/build APIs. Typically, flow is controlled by the server and the server returns the result before the request is cancelled.
Multiple Client requests does not affect each other because they get independent go-routine and context. Again, we are talking about happy path for normally designed/build applications. Your sample app looks good and should work fine.
The problem is how we test the app. Instead of creating multiple independent requests, we use Browser and refresh a single browser session. I did not check what exactly is going on, but assume that the Browser terminates the existing request in order to run a new one when you click ctrl-R. The server sees that request termination and communicates it to your code as context cancelation.
Try to test your code using curl or some other script/utility that creates independent requests. I am sure you will not see cancelations in that case.

Using application context as queries' parent context

I am currently passing application/main context from main.go to repository.go so that I can use it as "parent" context to go with query context. Is this valid/idiomatic usage or not? Also, shall I ditch the idea and just use context.Background() as "parent" context to go with query context instead?
main.go
package main
import (
"context"
"internal/user"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
repo := user.NewRepository(ctx, db)
// HTTP server is running.
}
internal/user/repository.go
package user
import (
"context"
"database/sql"
"time"
)
type Repository struct {
*sql.DB
}
var appCTX context.Context
func NewRepository(ctx context.Context, db *sql.DB) Repository {
appCTX = ctx
return Repository{db}
}
func (r Repository) Insert(args ...interface{}) error {
ctx, cancel := context.WithTimeout(appCTX, 5 * time.Millisecond)
defer cancel()
// Run query etc.
res, err := r.ExecContext(ctx, `INSERT INTO .....`, args...)
}
The idiomatic use of context is to pass it as the first function argument, and not store in structs. This is from the context doc:
Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx
So, even if you pass the main context down to your implementation, you should do that by passing the context to each operation.
Each self-contained operation (such as an HTTP request) should create a new context. If your main performs one such self-contained operation, you can pass the context down like this. However, if this is a server application, you should create a separate context for each request handler.

different about withcancel and withtimeout in golang's context

I'm new in Golang and I have some confused about WithCancel and WithTimeout when I learn golang's context part.
Show the code.
package main
import (
"context"
"fmt"
"time"
)
func someHandler() {
//ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
go doSth(ctx)
time.Sleep(3 * time.Second)
cancel()
}
func doSth(ctx context.Context) {
var i = 1
for {
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
fmt.Println("done")
return
default:
fmt.Printf("work %d seconds: \n", i)
}
i++
}
}
func main() {
fmt.Println("start...")
someHandler()
fmt.Println("end.")
}
Result:
// when use WithCancel
//
start...
work 1 seconds:
work 2 seconds:
end.
// when use WithTimeout
start...
work 1 seconds:
done
end.
My question is: why doesn't it print 'done' when I use withCancel but withTimeout does print it?
"Understanding the context package in golang" from Parikshit Agnihotry
mentions:
context.WithCancel(parent Context) (ctx Context, cancel CancelFunc)
This function creates a new context derived from the parent context that is passed in.
The parent can be a background context or a context that was passed into the function.
This returns a derived context and the cancel function.
Only the function that creates this should call the cancel function to cancel this context.
You can pass around the cancel function if you wanted to, but, that is highly not recommended. This can lead to the invoker of cancel not realizing what the downstream impact of canceling the context may be. There may be other contexts that are derived from this which may cause the program to behave in an unexpected fashion. In short, NEVER pass around the cancel function.
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2 * time.Second))
context.WithDeadline(parent Context, d time.Time) (ctx Context, cancel CancelFunc)
This function returns a derived context from its parent that gets cancelled when the deadline exceeds or cancel function is called.
For example, you can create a context that will automatically get canceled at a certain time in future and pass that around in child functions.
When that context gets canceled because of deadline running out, all the functions that got the context get notified to stop work and return.
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2 * time.Second))
context.WithTimeout(parent Context, timeout time.Duration) (ctx Context, cancel CancelFunc)
This function is similar to context.WithDeadline.
The difference is that it takes in time duration as an input instead of the time object.
This function returns a derived context that gets canceled if the cancel function is called or the timeout duration is exceeded.
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2 * time.Second))
why dones't print 'done' when use withCancel but withTimeout does
Probably because the Go program already exited before the goroutine has time to acknowledge the "Done" part.
We can find the relationship of cancelContext, timeOutContext and deadlineContext in the source code as below:
// cancelCtx declaration
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
// timerCtx declaration
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
cancelCtx is embeded inside the timerCtx, which realize inheritance in golang.

How to access a variable across all connected tcp clients in go?

I'm setting up a tcp server in a pet project I'm writing in go. I want to be able to maintain a slice of all connected clients, and then modify it whenever a new client connects or disconnects from my server.
My main mental obstacle right now is whether I should be declaring a package level slice, or just passing a slice into my handler.
My first thought was to declare my ClientList slice (I'm aware that a slice might not be my best option here, but I've decided to leave it as is for now) as a package level variable. While I think this would work, I've seen a number of posts discouraging the use of them.
My other thought was to declare ClientList as a slice in my main function, and then I pass ClientList to my HandleClient function, so whenever a client connects/disconnects I can call AddClient or RemoveClient and pass this slice in and add/remove the appropriate client.
This implementation is seen below. There are definitely other issues with the code, but I'm stuck trying to wrap my head around something that seems like it should be very simple.
type Client struct {
Name string
Conn net.Conn
}
type ClientList []*Client
// Identify is used to set the name of the client
func (cl *Client) Identify() error {
// code here to set the client's name in the based on input from client
}
// This is not a threadsafe way to do this - need to use mutex/channels
func (cList *ClientList) AddClient(cl *Client) {
*cList = append(*cList, cl)
}
func (cl *Client) HandleClient(cList *ClientList) {
defer cl.Conn.Close()
cList.AddClient(cl)
err := cl.Identify()
if err != nil {
log.Println(err)
return
}
for {
err := cl.Conn.SetDeadline(time.Now().Add(20 * time.Second))
if err != nil {
log.Println(err)
return
}
cl.Conn.Write([]byte("What command would you like to perform?\n"))
netData, err := bufio.NewReader(cl.Conn).ReadString('\n')
if err != nil {
log.Println(err)
return
}
cmd := strings.TrimSpace(string(netData))
if cmd == "Ping" {
cl.Ping() //sends a pong msg back to client
} else {
cl.Conn.Write([]byte("Unsupported command at this time\n"))
}
}
}
func main() {
arguments := os.Args
PORT := ":" + arguments[1]
l, err := net.Listen("tcp4", PORT)
if err != nil {
fmt.Println(err)
return
}
defer l.Close()
fmt.Println("Listening...")
// Create a new slice to store pointers to clients
var cList ClientList
for {
c, err := l.Accept()
if err != nil {
log.Println(err)
return
}
// Create client cl1
cl1 := Client{Conn: c}
// Go and handle the client
go cl1.HandleClient(&cList)
}
}
From my initial testing, this appears to work. I am able to print out my client list and I can see that new clients are being added, and their name is being added after Identify() is called as well.
When I run it with the -race flag, I do get data race warnings, so I know I will need a threadsafe way to handle adding clients. The same goes for removing clients when I add that in.
Are there any other issues I might be missing by passing my ClientList into HandleClient, or any benefits I would gain from declaring ClientList as a package level variable instead?
Several problems with this approach.
First, your code contains a data race: each TCP connection is served by a separate goroutine, and they all attempt to modify the slice concurrently.
You might try building your code with go build -race (or go install -race — whatever you're using), and see it crash by the enabled runtime checks.
This one is easy to fix. The most straightforward approach is to add a mutex variable into the ClientList type:
type ClientList struct {
mu sync.Mutex
clients []*Client
}
…and make the type's methods hold the mutex while they're mutating the clients field, like this:
func (cList *ClientList) AddClient(cl *Client) {
cList.mu.Lock()
defer cList.mu.Unlock()
cList.clients = append(cList.clients, o)
}
(If you will ever encounter the typical usage pattern of your ClientList type is to frequently call methods which only read the contained list, you may start using the sync.RWLock type instead, which allows multiple concurrent readers.)
Second, I'd split the part which "identifies" a client out of the handler function.
As of now, in the handler, if the identification fails, the handler exits but the client is not delisted.
I'd say it would be better to identify it up front and only run the handler once the client is beleived to be okay.
Also it supposedly worth adding a deferred call to something like RemoveClient at the top of the handler's body so that the client is properly delisted when the handler is done with it.
IOW, I'd expect to see something like this:
func (cl *Client) HandleClient(cList *ClientList) {
defer cl.Conn.Close()
err := cl.Identify()
if err != nil {
log.Println(err)
return
}
cList.AddClient(cl)
defer cList.RemoveClient(cl)
// ... the rest of the code
}

Rate limit function 40/second with "golang.org/x/time/rate"

I'm trying to use "golang.org/x/time/rate" to build a function which blocks until a token is free. Is this the correct way to use the library to rate limit blocks of code to 40 requests per second, with a bucket size of 2.
type Client struct {
limiter *rate.Limiter
ctx context.Context
}
func NewClient() *Client {
c :=Client{}
c.limiter = rate.NewLimiter(40, 2)
c.ctx = context.Background()
return &c
}
func (client *Client) RateLimitFunc() {
err := client.limiter.Wait(client.ctx)
if err != nil {
fmt.Printf("rate limit error: %v", err)
}
}
To rate limit a block of code I call
RateLimitFunc()
I don't want to use a ticker as I want the rate limiter to take into account the length of time the calling code runs for.
Reading the documentation here; link
You can see that the first parameter to NewLimiter is of type rate.Limit.
If you want 40 requests / second then that translates into a rate of 1 request every 25 ms.
You can create that by doing:
limiter := rate.NewLimiter(rate.Every(25 * time.Millisecond), 2)
Side note:
In generate, a context, ctx, should not be stored on a struct and should be per request. It would appear that Client will be reused, thus you could pass a context to the RateLimitFunc() or wherever appropriate instead of storing a single context on the client struct.
func RateLimit(ctx context.Context) {
limiter := rate.NewLimiter(40, 10)
err := limiter.Wait(ctx)
if err != nil {
// Log the error and return
}
// Do the actual work here
}
As Zak said, do not store Context inside a struct type according to the Go documentation context.

Resources