samber / do

⚙️ A dependency injection toolkit based on Go 1.18+ Generics.
https://pkg.go.dev/github.com/samber/do
MIT License
1.71k stars 67 forks source link

Data race when invoking services after shutdown #34

Closed ivanduka closed 2 months ago

ivanduka commented 1 year ago

I am not sure if this is a problem or a feature, but invoking dependencies after shutdown creates a race condition (runnable code at https://goplay.tools/snippet/vIQ3Pwu4lw1):

package main

import (
    "log"
    "sync"
    "time"

    "github.com/samber/do"
)

type Service struct{}

func main() {
    var wg sync.WaitGroup

    i := do.New()

    do.Provide(i, func(injector *do.Injector) (*Service, error) {
        return &Service{}, nil
    })

    // uncommenting the line below gives an error: `Invoking after shutdown error: DI: could not find service `*main.Service`, available services:`
    //_ = do.MustInvoke[*Service](i)

    wg.Add(1)

    go func() {
        defer wg.Done()

        time.Sleep(time.Second * 1)

        _, err := do.Invoke[*Service](i)
        if err != nil {
            log.Printf("Invoking after shutdown error: %v\n", err)
            return
        }
    }()

    err := i.Shutdown()
    if err != nil {
        log.Printf("Shutdown error: %v\n", err)
    }

    wg.Wait()

    log.Println("Finished")
}

When running with go run -race . it detects a race condition:

==================
WARNING: DATA RACE
Write at 0x00c0000b60c8 by goroutine 6:
  github.com/samber/do.(*Injector).onServiceInvoke()
      /Users/ivand/go/pkg/mod/github.com/samber/do@v1.6.0/injector.go:278 +0x120
  github.com/samber/do.InvokeNamed[...]()
      /Users/ivand/go/pkg/mod/github.com/samber/do@v1.6.0/di.go:128 +0xc8
  github.com/samber/do.Invoke[...]()
      /Users/ivand/go/pkg/mod/github.com/samber/do@v1.6.0/di.go:101 +0x6c
  main.main.func2()
      /Users/ivand/IdeaProjects/untitled1/main.go:32 +0x7c

Previous read at 0x00c0000b60c8 by main goroutine:
  github.com/samber/do.(*Injector).Shutdown()
      /Users/ivand/go/pkg/mod/github.com/samber/do@v1.6.0/injector.go:124 +0x1b0
  main.main()
      /Users/ivand/IdeaProjects/untitled1/main.go:39 +0x130

Goroutine 6 (running) created at:
  main.main()
      /Users/ivand/IdeaProjects/untitled1/main.go:27 +0x128
==================
2023/04/28 12:55:27 Finished
Found 1 data race(s)
exit status 66

Is this the intended way it supposed to work? Is the shutdown procedure expected to be a final call with no invoking of services after it?

I haven't dug deeply in the source code, but apparently the shutdown procedure does not operate under the write lock. Is there a particular reason for that?

samber commented 2 months ago

It has been fixed in https://github.com/samber/do/commit/58b08d463297ae40bda36a7de468f44b712f1019

We added more thread safety in v2 (currently in RC)