Race condition on Echo context with GraphQL in Go

Given an API setup with GraphQL and Echo, a colleague ran into a race condition situation. There was a concurrent read/write issue on Echo’s context. GraphQL runs its resolvers in parallel if set so, and when context is shared between resolvers, things can go wrong.

I took a look into Echo’s context implementation and I saw a simple map is used for Get/Set.

For every API call, a handle functions is given an Echo context and executes the GraphQL schema with the specified context.

func handle(c echo.Context) error {
 schema, err := gqlgo.ParseSchema(
  ...
  gqlgo.MaxParallelism(10),
 )

 schema.Exec(
  c,
  ...
 )
}

My solution was to use a custom context which embeds the original one and uses a concurrent map instead of Echo’s.

type Context struct {
 echo.Context
 store sync.Map
}

func (c *Context) Set(key string, val interface{}) {
 c.store.Store(key, val)
}

func (c *Context) Get(key string) interface{} {
 val, _ := c.store.Load(key)
 return val
}

func (c *Context) Deadline() (deadline time.Time, ok bool) {
 return c.Request().Context().Deadline()
}

func (c *Context) Done() <-chan struct{} {
 return c.Request().Context().Done()
}

func (c *Context) Err() error {
 return c.Request().Context().Err()
}

func (c *Context) Value(key interface{}) interface{} {
 return c.Get(fmt.Sprintf("%v", key))
}

func handle(c echo.Context) error {
 ctx := &Context{Context: c}

 schema.Exec(
  ctx,
  ...
 )
}

While this fixes the issue, a colleague suggested another approach. Using a new map like I did will ignore any possible values already existing on the original context. So if you want to preserve the original context you can use a Mutex to prevent concurrent read/write issues.

type Context struct {
 echo.Context
 sync.RWMutex
}

func (c *Context) Set(key string, val interface{}) {
 c.Lock()
 c.Context.Set(key, val)
 c.Unlock()
}

func (c *Context) Get(key string) interface{} {
 c.RLock()
 val := c.Context.Get(key)
 c.RUnlock()
 return val
}

3 thoughts on “Race condition on Echo context with GraphQL in Go”

  1. You should in fact try to fix this in Echo. I think they welcome contributions. I do not see why they made that context non concurrency safe.
    On a sidenote, cool that there is a graphql implementation in go. I was not aware of that.

    1. I think that in a normal server situation you don’t get to this issue, while GraphQL is based on concurrent handling. And it also depends on your GraphQL resolvers design.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.