Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled
70 lines
1.5 KiB
Go
70 lines
1.5 KiB
Go
package singleton
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
|
|
"github.com/navidrome/navidrome/log"
|
|
)
|
|
|
|
var (
|
|
instances = map[string]interface{}{}
|
|
pending = map[string]chan struct{}{}
|
|
lock sync.RWMutex
|
|
)
|
|
|
|
func GetInstance[T any](constructor func() T) T {
|
|
var v T
|
|
name := reflect.TypeOf(v).String()
|
|
|
|
// First check with read lock
|
|
lock.RLock()
|
|
if instance, ok := instances[name]; ok {
|
|
defer lock.RUnlock()
|
|
return instance.(T)
|
|
}
|
|
lock.RUnlock()
|
|
|
|
// Now check if someone is already creating this type
|
|
lock.Lock()
|
|
|
|
// Check again with the write lock - someone might have created it
|
|
if instance, ok := instances[name]; ok {
|
|
lock.Unlock()
|
|
return instance.(T)
|
|
}
|
|
|
|
// Check if creation is pending
|
|
wait, isPending := pending[name]
|
|
if !isPending {
|
|
// We'll be the one creating it
|
|
pending[name] = make(chan struct{})
|
|
wait = pending[name]
|
|
}
|
|
lock.Unlock()
|
|
|
|
// If someone else is creating it, wait for them
|
|
if isPending {
|
|
<-wait // Wait for creation to complete
|
|
|
|
// Now it should be in the instances map
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
return instances[name].(T)
|
|
}
|
|
|
|
// We're responsible for creating the instance
|
|
newInstance := constructor()
|
|
|
|
// Store it and signal other goroutines
|
|
lock.Lock()
|
|
instances[name] = newInstance
|
|
close(wait) // Signal that creation is complete
|
|
delete(pending, name) // Clean up
|
|
log.Trace("Created new singleton", "type", name, "instance", fmt.Sprintf("%+v", newInstance))
|
|
lock.Unlock()
|
|
|
|
return newInstance
|
|
}
|