Home  >  Article  >  Backend Development  >  Handling cross-layer context cancellation

Handling cross-layer context cancellation

WBOY
WBOYforward
2024-02-05 23:54:07473browse

Handling cross-layer context cancellation

Question content

I am developing a layered backend API with controllers, services, repositories, etc.

Context

Each method in these layers takes context.Context as its first parameter containing the request context. This is convenient because any method can access various request-related data (such as correlationID, etc.)

Each request context has a timeout set by the following TimeoutMiddleware:

func TimeoutMiddleware(timeoutFn func(*gin.Context) time.Duration) gin.HandlerFunc {
    return func(c *gin.Context) {
        timeout := timeoutFn(c)
        ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
        defer cancel()

        c.Request = c.Request.WithContext(ctx)

        c.Next()
    }
}

func TimeoutFn(c *gin.Context) time.Duration {
    return conf.HTTPRouter.DefaultContextTimeout
}

challenge

The idea is to gracefully stop any ongoing operations when the request context times out. Based on my understanding of context and concurrency (very little), I built this helper function:

package helpers

import "context"

// Checks for context cancellation and returns ctx.Err() if canceled.
func HandleContextCancel(ctx context.Context) error {
    select {
    case <-ctx.Done(): // If the context is canceled
        return ctx.Err() // return immediately with the canceled error.

    default: 
        return nil // Continue with the normal processing.
    }
}

In theory, if I wanted to stop any operation as quickly as possible, I would need to call this function at the beginning of every method in the application, like this:

func DoSomething(ctx context.Context, ...) resterrors.RestErr {
if err := helpers.HandleContextCancel(ctx); err != nil {
    return resterrors.NewRequestTimeoutError(
        fmt.Errorf("DoSomething: %w", err),
    )
}
// ...

Also, I know that in the repository that accesses the database, most functions require a Context, such as Query, QueryRow, Exec ...,As follows:

rows, err := pgclient.GetSession().Query(ctx, query, param1, ...)

So every time an error occurs in the line above, I have to check if the error is not due to context cancellation instead of just returning internal_server_error and the error message like this:

rows, err := pgclient.GetSession().Query(ctx, query, param1, ...)
if err != nil {
   return helpers.MapRepoError("DoSomething: Query Error:", err)
}
func MapRepoError(location string, err error) resterrors.RestErr {
    if errors.Is(err, context.DeadlineExceeded) {
        return resterrors.NewRequestTimeoutError(
            fmt.Errorf("%s request_timeout", location),
        )
    }
    return resterrors.NewInternalServerError(
        fmt.Errorf("%s %w", location, err),
    )
}

Using the HandleContextCancel function looks a bit redundant, what do you think?


Correct answer


You don't need the HandleContextCancel function, you can simply do the following:

if ctx.Err()!=nil {
  // Context timed out or canceled. Return
  return ctx.Err()
}

If your other error handling functions wrap this error correctly (i.e. they implement the Unwrap() error method, then you can check at the top level whether the error contains a timeout/cancellation error and decide if you To handle what type of errors like returns. You don't have to do this for every layer.

The above is the detailed content of Handling cross-layer context cancellation. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete