Home > Software engineering >  context.TODO() or context.Background(), which one should I prefer?
context.TODO() or context.Background(), which one should I prefer?

Time:10-29

I'm currently working on migrating our code from global sign package to go mongo-driver, not sure where should I use context.TODO() and context.Background(), it’s really confusing, I know both it returns non-nil empty, so should I use context.Background() in the main function & init() functions? And use context.TODO() in other places? Can anyone help with this?

Trying to check to see which param should I use context.TODO() or context.Background().

CodePudding user response:

Check their documentation:

context.Background():

func Background() Context

Background returns a non-nil, empty Context. It is never canceled, has no values, and has no deadline. It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests.

context.TODO():

func TODO() Context

TODO returns a non-nil, empty Context. Code should use context.TODO when it's unclear which Context to use or it is not yet available (because the surrounding function has not yet been extended to accept a Context parameter).

According to the doc, when you need a context but you don't have one (yet) and don't know what to use, use context.TODO(). This is exactly your case, using context.TODO() properly documents this. Also, static analysis tools and IDEs may give support discovering these contexts and give you warning later to address the issue.

Also note that if you do have a context when you have to use the mongo driver, then consider using that context, or deriving a new one from it.

For example, if you are writing an HTTP handler where you need to query some documents, the HTTP request http.Request already has a context which you can access using Request.Context(). This is a prime candidate to use when you call other API functions that require a context.

Why, you may ask? Because the request's context is cancelled when the HTTP client abandons / aborts the request, which means whatever you do, the client will not receive it, so for example if the HTTP handler just serves information, you may as well abort generating it, saving some resources. So if the request context is cancelled while you're executing a MongoDB query, you may as well cancel the query too, because you won't need (won't process) the result anyway. Cancelling the context passed to the query execution (Collection.Find() for example) is the way to tell the MongoDB server to cancel the query if possible (because you won't need the result).

So using the request context in this case can save you some CPU time and memory both on the HTTP server and on the MongoDB server as well.

Another example: let's say you have to produce the HTTP response within 10 seconds (may be a platform limitation), during which you have to execute a MongoDB operation, and you have to post process the results which takes 4 seconds. In this scenario if the MongoDB operation takes more than 6 seconds, you would not be able to complete the post processing to fit in the 10-second limit. So you might as well cancel the MongoDB operation if it doesn't complete in 6 seconds.

An easy way to solve this is to derive a context with 6 seconds timeout:

ctx, cancel := context.WithTimeout(r.Context(), 6 * time.Second)
defer cancel()

// ctx automatically times out after 6 seconds
curs, err := c.Find(ctx, bson.M{"some": "filter"})

In this example ctx will time out after 6 seconds, so if the c.Find() operation doesn't get finished by that, the passed ctx will signal that it can be cancelled (in which case an error will be returned). Since ctx is derived from r.Context(), if the request's context get's cancelled earlier (e.g. the HTTP client aborts), ctx will also be cancelled.

Now let's say you're not writing an HTTP handler but some other code (standalone app, background worker etc.), where you may not have a context, but you have your standards that you don't want to wait more than 30 seconds for a query. This is a prime example where you may derive a context from context.Background() using a 30-sec timeout:

ctx, cancel := context.WithTimeout(context.Background(), 30 * time.Second)
defer cancel()

// ctx automatically times out after 30 seconds
curs, err := c.Find(ctx, bson.M{"some": "filter"})

If the query finishes within 30 seconds, all good, you may use the results. If not, the context gets cancelled after 30 sec, and so c.Find() will also (likely) return an error.

CodePudding user response:

context.TODO:

Code should use context.TODO when it's unclear which Context to use or it is not yet available (because the surrounding function has not yet been extended to accept a Context parameter).

context.Background:

Background returns a non-nil, empty Context. It is never canceled, has no values, and has no deadline. It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests.

TODO is a placeholder to make it clear that the code should be updated with a more appropriate context in the future, but none is available yet. Background is used when such an indication isn't necessary and you simply need an empty root context with no values or cancellation. The values they return are identical; the only difference is semantic, in that one might scan code for TODO contexts (or use a linter to do so) as places that need to be updated/addressed.

CodePudding user response:

They are both non-nil empty contexts that don't cancel or timeout. Use TODO() in functions that do not accept a context, or it is not clear what context to use. Use Background() as the first context in a chain of contexts. Based on your description, if your MongoDB using functions do not accept a context yet, TODO() would be the better choice. They are functionally equivalent though.

  • Related