Best way to pass context - go

I've done a lot of research regarding context, but I can't seem to find a generally accepted answer, plus I'm new to Go.
In my current code I've var ctx = context.Background(),which is used in various places.
My concern is, aren't all my code modifying the same context since it's a global variable? .
Yes, I know context is request scoped.
This is part of my code for context.
var ctx = context.Background()
var db *firestore.Client
var auth *aut.Client
func init() {
app, err := firebase.NewApp(ctx, nil)
if err != nil {
log.Fatal(err)
}
db, err = app.Firestore(ctx)
if err != nil {
log.Fatal(err)
}
auth, err = app.Auth(ctx)
if err != nil {
log.Fatal(err)
}
}
func SetRate(r int) (err error) {
//TODO: create last updated field
_, err = db.Collection("Rate").Doc("rate").Set(ctx, map[string]int{"USDT": r})
if err != nil {
log.Println(err)
return err
}
return nil
}
Please try not to use overly complicated words to describe a term.

Its an accepted practice in go to pass context from function to function. Normally, the first parameter of every function if context type. I have seen that whenever a context is passed down and has some use-case with in the method scope, a new context is created from parent context.

It is best practice to create a context inside of a function and pass it between functions as needed, rather than having the one context shared across the package. For something like a HTTP server, you will typically see a unique context for each incoming API call.

Related

Cannot identify an error in sync.Once usage

I'm doing an online course on Golang. The following piece of code is presented in the course material as an example of misuse of sync.Once:
var (
once sync.Once
db *sql.DB
)
func DbOnce() (*sql.DB, error) {
var err error
once.Do(func() {
fmt.Println("Am called")
db, err = sql.Open("mysql", "root:test#tcp(127.0.0.1:3306)/test")
if err != nil {
return
}
err = db.Ping()
})
if err != nil {
return nil, err
}
return db, nil
}
Supposedly, the above is a faulty implementation of an SQL connection manager. We, the students, are to find the error ourselves, which I struggle with. The code runs fine even in parallel. This is how I used it:
func main() {
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go (func() {
db, err := DbOnce()
if err != nil {
panic(err)
}
var v int
r := db.QueryRow("SELECT 1")
err = r.Scan(&v)
fmt.Println(v, err)
wg.Done()
})()
}
wg.Wait()
}
I understand that homework questions are discouraged here, so I'm not asking for a complete solution, just a hint would be fine. Is the error related to concurrency (i.e. I need to run it in a specific concurrent context)? Is it usage of sql.Open specifically?
Initialization of the db variable is OK. The problem is with the returned error.
If you call DbOnce() for the first time and opening a DB connection fails, that error will be returned properly. But what about subsequent calls? The db initialization code will not be run again, so nil db may be returned, and since the initialization code is not run, the default value of the err variable is returned, which will be nil. To sum it up, the initialization error is lost and will not be reported anymore.
One solution is to stop the app if connection fails (at the first call). Another option is to store the initialization error too in a package level variable along with db, and return that from DbOnce() (and not use a local variable for that). The former has the advantage that you don't have to handle errors returned from DbOnce(), as it doesn't even have to return an error (if there's an error, your app will terminate).
The latter could look like this:
var (
once sync.Once
db *sql.DB
dbErr error
)
func DbOnce() (*sql.DB, error) {
once.Do(func() {
fmt.Println("Am called")
db, dbErr = sql.Open("mysql", "root:test#tcp(127.0.0.1:3306)/test")
if dbErr != nil {
return
}
dbErr = db.Ping()
})
return db, dbErr
}

Where to get the Context for DB query?

I am using https://entgo.io/ as entity framework and https://echo.labstack.com/ as web framework.
Consider the following code snippet:
func main() {
client, err := Open("postgresql://pgadmin:pgpw#127.0.0.1/nfeed")
if err != nil {
log.Fatal(err.Error())
return
}
ctx := context.Background()
if err := client.Schema.Create(ctx); err != nil {
log.Fatal(err)
return
}
e := echo.New()
e.GET("/hashtags", func(c echo.Context) error {
pattern := c.QueryParam("pattern")
if len(pattern) == 0 {
return c.JSON(http.StatusNotFound, err)
}
hs, err := client.Hashtag.Query().All(????)
return c.JSON(http.StatusOK, "Hello")
})
e.Logger.Fatal(e.Start(":3000"))
}
The function call client.Hashtag.Query().All(context) expects https://pkg.go.dev/context as parameter.
The question is, how or where can I get the context?
In my opinion, the Echo framework should provide me a Context. Unfortunately, I could not found within the Echo module.
You define a context variable on the line ctx := context.Background(), which is a valid context type, there's good documentation on this in your own link https://pkg.go.dev/context.
There's no reason that wouldn't work, if you need more then you can check the documentation on how to make a more in depth context variable.

What is the proper style and usage of golang Context?

I am new to golang and trying to get a better understanding of context.
In the below snippet, it appears to me that I've instantiated my computeService with a context. why do I have to pass it again to the .Context() function when calling Stop()?
package main
func stopTaggedMachines(ctx context.Context, svc *compute.Service, project, zone, tag string) ([]string, error) {
var instances []string
f := func(page *compute.InstanceList) error {
for _, v := range page.Items {
if v.Labels["gcp-idler-managed"] == "true" {
result, err := svc.Instances.Stop(project, zone, v.Name).Context(ctx).Do()
if err != nil {
log.Fatal(err)
}
fmt.Printf("[INFO] gcp-machine-idler: Instance in state %v, Stopping %v... Response: %v \n", v.Status, v.Name, result.HTTPStatusCode)
}
}
return nil
}
call := svc.Instances.List("my-project", "us-west1-b")
if err := call.Pages(oauth2.NoContext, f); err != nil {
return instances, nil
}
return instances, nil
}
func main() {
// Use oauth2.NoContext if there isn't a good context to pass in.
ctx := context.Background()
computeService, err := compute.NewService(ctx)
if err != nil {
log.Fatal(err)
}
stopTaggedMachines(ctx, computeService, "my-project", "us-west1-b", "gcp-idler-managed")
return
}
It seems redundant to me that I pass ctx into compute.NewService(), then again into stopTaggedMachines()
Is this really the correct convention or usage of context? Why does my call to svc.Instances.Stop(project, zone, v.Name).Context(ctx).Do() need to be passed ctx yet again as a parameter?
svc.Instances.Stop(project, zone, v.Name) returns InstanceStopCall
By calling Context(ctx) you are setting the context to be used in this call's Do method. This allows the HTTP request to be aborted if the context is canceled.
The Stop method can take a long time (as in minutes). This allows a user to cancel waiting for a VM to shutdown.

Split function into 2 function for test coverage

How can I test the error for ioutil.ReadAll(rep.Body)? Do I need to split my function in two, one which will make the request, and another one which will read the body and return the bytes and error?
func fetchUrl(URL string) ([]bytes, error) {
resp, err := http.Get(URL)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
return body, nil
}
Do I need to split my function in two, one which will make the request, and another one which will read the body and return the bytes and error?
The first one is called http.Get and the other one ioutil.ReadAll, so I don't think there's anything to split. You just created a function that uses two other functions together which you should assume are working correctly. You could even simplify your function to make it more obvious:
func fetchURL(URL string) ([]byte, error) {
resp, err := http.Get(URL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
If you want to test anything is your fetchURL function using http.Get and ioutil.ReadAll together. I wouldn't personally bother to test it directly, but if you insist on it, you can overwrite http.DefaultTransport for a single test and provide your own, which returns http.Response with body implementing some error scenarios (e.g. and error during body read).
Here is the sketch idea:
type BrokenTransport struct {
}
func (*BrokenTransport) RoundTrip(*http.Request) (*http.Response, error) {
// Return Response with Body implementing specific error behaviour
}
http.DefaultTransport = &BrokenTransport{}
// http.Get will now use your RoundTripper.
// You should probably restore http.DefaultTransport after the test.
Basically yes, unless you're using net/http/httptest or a similar way to mock your HTTP server when testing.
But the question is: what would you really be testing? That ioutil.ReadAll() detects errors? But I'm sure this was already covered by the test suite of the Go's stdlib.
Hence I'd say that in this particular case you're about to test for the testing's sake. IMO for such trivial cases it's better to concentrate on how the fetched result is further processed.

Organize Go code for CRUD operations on MongoDB

I'm writing a web application in Go but I have some troubles getting my code organized.
For basic CRUD operations on MongoDB I always have to do something like this in the beginning of my code:
session, err := mgo.Dial("localhost")
if err != nil {
return err
}
defer session.Close()
But I don't like the fact that I always have to repeat the same code.
Is there a way to make it shorter or to avoid a lot of this in my code:
if err != nil {
return err
}
I'm new to Go so maybe I'm missing something obvious.
First for the actual question, no, that's the Go away of checking for errors.
Second, the proper way to use mgo is to have one sesson and clone it every time you need to do something, for example:
var (
mgoSession *mgo.Session
)
func init() {
sess, err := mgo.Dial("localhost")
if err != nil {
panic(err) // no, not really
}
mgoSession = sess
}
func do_stuff_with_mgo() {
sess := mgoSession.Clone()
defer sess.Close()
//do stuff with sess
}
func main() {
go do_stuff_with_mgo()
go do_stuff_with_mgo()
do_stuff_with_mgo()
}
Also check this article about mgo (I'm not the author, but it helped me with learning mgo, it might be a bit outdated though.)

Resources