Skip to content

Race condition in AddHook #2453

Closed
Closed
@duongcongtoai

Description

@duongcongtoai

I suspect current AddHook function will cause race condition with the background connection pool dialer

WARNING: DATA RACE
Write at 0x00c000150278 by goroutine 84:
  github.com/redis/go-redis/v9.(*hooksMixin).chain()
      /go/pkg/mod/github.com/redis/go-redis/v9@v9.0.2/redis.go:120 +0x8c
  github.com/redis/go-redis/v9.(*hooksMixin).AddHook()
      /go/pkg/mod/github.com/redis/go-redis/v9@v9.0.2/redis.go:114 +0x527
  github.com/redis/go-redis/extra/redisotel/v9.InstrumentTracing()
      /go/pkg/mod/github.com/redis/go-redis/extra/redisotel/v9@v9.0.2/tracing.go:28 +0x3e6
...
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1446 +0x216
  testing.(*T).Run.func1()
      /usr/local/go/src/testing/testing.go:1493 +0x47

Previous read at 0x00c000150278 by goroutine 85:
  github.com/redis/go-redis/v9.(*hooksMixin).dialHook()
      /go/pkg/mod/github.com/redis/go-redis/v9@v9.0.2/redis.go:169 +0x64
  github.com/redis/go-redis/v9.(*hooksMixin).dialHook-fm()
      <autogenerated>:1 +0x99
  github.com/redis/go-redis/v9.newConnPool.func1()
      /go/pkg/mod/github.com/redis/go-redis/v9@v9.0.2/options.go:489 +0xa4
  github.com/redis/go-redis/v9/internal/pool.(*ConnPool).dialConn()
      /go/pkg/mod/github.com/redis/go-redis/v9@v9.0.2/internal/pool/pool.go:194 +0x186
  github.com/redis/go-redis/v9/internal/pool.(*ConnPool).addIdleConn()
      /go/pkg/mod/github.com/redis/go-redis/v9@v9.0.2/internal/pool/pool.go:134 +0x6f
  github.com/redis/go-redis/v9/internal/pool.(*ConnPool).checkMinIdleConns.func1()
      /go/pkg/mod/github.com/redis/go-redis/v9@v9.0.2/internal/pool/pool.go:122 +0x34

Possible Solution

Allow options struct to receive a hook on init, instead of letting AddHook function modify the hooks after the pool has been created and running

Steps to Reproduce

Run with race flag:

// Package redisdb is a package for communicating with redis
package redisdb

import (
	"context"
	"time"

	"github.com/redis/go-redis/extra/redisotel/v9"
	"github.com/redis/go-redis/v9"

)

type RedisConfig struct {
	MaxRetriesWrite int
}

// New will create new redis client
func New(ctx context.Context, addr, password string, db int) (*redis.Client, error) {
	rdb := redis.NewClient(&redis.Options{
		Addr:            addr,
		Password:        password,
		DB:              db,
		MaxRetries:      3,
		MinRetryBackoff: time.Second * 3,
		// Maximum backoff between each retry.
		// Default is 512 milliseconds; -1 disables backoff.
		MaxRetryBackoff: time.Second * 3,

		// Dial timeout for establishing new connections.
		// Default is 5 seconds.
		DialTimeout: time.Second * 3,
		// Timeout for socket reads. If reached, commands will fail
		// with a timeout instead of blocking. Supported values:
		//   - `0` - default timeout (3 seconds).
		//   - `-1` - no timeout (block indefinitely).
		//   - `-2` - disables SetReadDeadline calls completely.
		ReadTimeout: time.Second * 3,
		// Timeout for socket writes. If reached, commands will fail
		// with a timeout instead of blocking.  Supported values:
		//   - `0` - default timeout (3 seconds).
		//   - `-1` - no timeout (block indefinitely).
		//   - `-2` - disables SetWriteDeadline calls completely.
		WriteTimeout: time.Second * 3,

		ContextTimeoutEnabled: true,

		// Maximum number of socket connections.
		// Default is 10 connections per every available CPU as reported by runtime.GOMAXPROCS.
		PoolSize: 10,
		// Minimum number of idle connections which is useful when establishing
		// new connection is slow.
		MinIdleConns: 1,
		// Maximum number of idle connections.
		MaxIdleConns: 1,
		// ConnMaxIdleTime is the maximum amount of time a connection may be idle.
		// Should be less than server's timeout.
		//
		// Expired connections may be closed lazily before reuse.
		// If d <= 0, connections are not closed due to a connection's idle time.
		//
		// Default is 30 minutes. -1 disables idle timeout check.
		ConnMaxIdleTime: time.Second * 30,
		// ConnMaxLifetime is the maximum amount of time a connection may be reused.
		//
		// Expired connections may be closed lazily before reuse.
		// If <= 0, connections are not closed due to a connection's age.
		//
		// Default is to not close idle connections.
		ConnMaxLifetime: time.Minute * 15,
	})

	// Check if redis not avail fail the program
	if _, err := rdb.Ping(ctx).Result(); err != nil {
		return nil, err
	}
	// Enable tracing instrumentation.
	if err := redisotel.InstrumentTracing(rdb); err != nil {
		return nil, err
	}

	return rdb, nil
}

Context (Environment)

go 1.19
	github.com/redis/go-redis/extra/redisotel/v9 v9.0.2
	github.com/redis/go-redis/v9 v9.0.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions