Skip to content

Commit

Permalink
rely on atomic counter per context instead of time.Now()
Browse files Browse the repository at this point in the history
  • Loading branch information
Firas Darwish committed Nov 10, 2024
1 parent ce06b0c commit bc181cc
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 29 deletions.
10 changes: 4 additions & 6 deletions concrete.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package ore

import "time"

// concrete holds the resolved instance value and other metadata
type concrete struct {
//the value implementation
value any

//invocationTime is the time when the resolver had been invoked, it is different from the "creationTime"
//of the concrete. Eg: A calls B, then the invocationTime of A is before B, but the creationTime of A is after B
//invocationOrder is the order when the resolver had been invoked, it is the opposite of the "creationTime"
//of the concrete. Eg: A calls B, then the invocationOrder is [A -> B], but the creationTime/order is [B -> A]
//(because B was created before A)
invocationTime time.Time
invocationOrder uint32

//the lifetime of this concrete
lifetime Lifetime

//the invocation deep level, the bigger the value, the deeper it was resolved in the dependency chain
//the invocation depth level, the bigger the value, the deeper it was resolved in the dependency chain
//for example: A depends on B, B depends on C, C depends on D
//A will have invocationLevel = 1, B = 2, C = 3, D = 4
invocationLevel int
Expand Down
2 changes: 1 addition & 1 deletion get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ func TestGetResolvedScopedInstances(t *testing.T) {
_, ctx = Get[*m.DisposableService2](ctx)

//Act
disposables := GetResolvedScopedInstances[m.Disposer](ctx) //B, A
disposables := GetResolvedScopedInstances[m.Disposer](ctx) //B, C, A

//Assert that the order is [B,C,A], the most recent invocation would be returned first
assert.Equal(t, 3, len(disposables))
Expand Down
11 changes: 5 additions & 6 deletions getters.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func GetList[T any](ctx context.Context, key ...KeyStringer) ([]T, context.Conte
pointerTypeNames = []pointerTypeName{inputPointerTypeName}
}

servicesArray := []T{}
var servicesArray []T

for i := 0; i < len(pointerTypeNames); i++ {
pointerTypeName := pointerTypeNames[i]
Expand Down Expand Up @@ -117,7 +117,7 @@ func GetResolvedSingletons[TInterface any]() []TInterface {
lock.RLock()
defer lock.RUnlock()

list := []*concrete{}
var list []*concrete

//filtering
for _, resolvers := range container {
Expand Down Expand Up @@ -152,7 +152,7 @@ func GetResolvedScopedInstances[TInterface any](ctx context.Context) []TInterfac
return []TInterface{}
}

list := []*concrete{}
var list []*concrete

//filtering
for _, contextKey := range contextKeyRepository {
Expand All @@ -169,9 +169,8 @@ func GetResolvedScopedInstances[TInterface any](ctx context.Context) []TInterfac
func sortAndSelect[TInterface any](list []*concrete) []TInterface {
//sorting
sort.Slice(list, func(i, j int) bool {
return list[i].invocationTime.After(list[j].invocationTime) ||
(list[i].invocationTime == list[j].invocationTime &&
list[i].invocationLevel > list[j].invocationLevel)
return list[i].invocationOrder > list[j].invocationOrder ||
(list[i].invocationOrder == list[j].invocationOrder && list[i].invocationLevel > list[j].invocationLevel)
})

//selecting
Expand Down
15 changes: 8 additions & 7 deletions ore.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ var (
// - no lifetime misalignment (a longer lifetime service depends on a shorter one).
//
// You don't need Ore to validate over and over again each time it creates a new concrete.
// It's a waste of resource especially when you will need Ore to create milion of transient concretes
// It's a waste of resource especially when you will need Ore to create a million transient concretes
// and any "pico" seconds or memory allocation matter for you.
//
// In this case, you can set DisableValidation = true.
//
// This config would impact also the the [GetResolvedSingletons] and the [GetResolvedScopedInstances] functions,
// This config would impact also the [GetResolvedSingletons] and the [GetResolvedScopedInstances] functions,
// the returning order would be no longer guaranteed.
DisableValidation = false
lock = &sync.RWMutex{}
Expand All @@ -31,9 +31,10 @@ var (
aliases = map[pointerTypeName][]pointerTypeName{}

//contextKeysRepositoryID is a special context key. The value of this key is the collection of other context keys stored in the context.
contextKeysRepositoryID specialContextKey = "The context keys repository"
contextKeysRepositoryID specialContextKey = "__ORE_CONTEXT_KEY_REPO"
//contextKeyResolversStack is a special context key. The value of this key is the [ResolversStack].
contextKeyResolversStack specialContextKey = "Dependencies stack"
contextKeyResolversStack specialContextKey = "__ORE_DEPENDENCIES_STACK"
contextCounterKey = "__ORE_COUNTER"
)

type contextKeysRepository = []contextKey
Expand Down Expand Up @@ -84,13 +85,13 @@ func appendToAliases[TInterface, TImpl any]() {
return
}
lock.Lock()
defer lock.Unlock()
for _, ot := range aliases[aliasType] {
if ot == originalType {
return //already registered
}
}
aliases[aliasType] = append(aliases[aliasType], originalType)
lock.Unlock()
}

func Build() {
Expand All @@ -103,9 +104,9 @@ func Build() {

// Validate invokes all registered resolvers. It panics if any of them fails.
// It is recommended to call this function on application start, or in the CI/CD test pipeline
// The objectif is to panic early when the container is bad configured. For eg:
// The objective is to panic early when the container is bad configured. For eg:
//
// - (1) Missing depedency (forget to register certain resolvers)
// - (1) Missing dependency (forget to register certain resolvers)
// - (2) cyclic dependency
// - (3) lifetime misalignment (a longer lifetime service depends on a shorter one).
func Validate() {
Expand Down
7 changes: 3 additions & 4 deletions registrars.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package ore
import (
"fmt"
"reflect"
"time"
)

// RegisterLazyCreator Registers a lazily initialized value using a `Creator[T]` interface
Expand Down Expand Up @@ -32,9 +31,9 @@ func RegisterEagerSingleton[T comparable](impl T, key ...KeyStringer) {
lifetime: Singleton,
},
singletonConcrete: &concrete{
value: impl,
lifetime: Singleton,
invocationTime: time.Now(),
value: impl,
lifetime: Singleton,
invocationOrder: 0,
},
}
appendToContainer[T](e, key)
Expand Down
18 changes: 13 additions & 5 deletions serviceResolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"container/list"
"context"
"fmt"
"time"
"sync/atomic"
)

type (
Expand Down Expand Up @@ -88,10 +88,18 @@ func (this serviceResolverImpl[T]) resolveService(ctx context.Context) (*concret
// push the current resolver to the resolversStack
marker = pushToStack(currentStack, this.resolverMetadata)
}

ctxCounter, ok := ctx.Value(contextCounterKey).(*atomic.Uint32)
if !ok {
ctxCounter = &atomic.Uint32{}
ctx = context.WithValue(ctx, contextCounterKey, ctxCounter)
}

invocationOrder := ctxCounter.Add(1)

var concreteValue T
invocationTime := time.Now()

// first, try make concrete implementation from `anonymousInitializer`
// first, try to make concrete implementation from `anonymousInitializer`
// if nil, try the concrete implementation `Creator`
if this.anonymousInitializer != nil {
concreteValue, ctx = (*this.anonymousInitializer)(ctx)
Expand All @@ -103,15 +111,15 @@ func (this serviceResolverImpl[T]) resolveService(ctx context.Context) (*concret
if !DisableValidation {
invocationLevel = currentStack.Len()

//the concreteValue is created, we must to pop the current resolvers from the stack
//the concreteValue is created, we must pop the current resolvers from the stack
//so that future resolvers won't link to it
currentStack.Remove(marker)
}

con := &concrete{
value: concreteValue,
lifetime: this.lifetime,
invocationTime: invocationTime,
invocationOrder: invocationOrder,
invocationLevel: invocationLevel,
}

Expand Down

0 comments on commit bc181cc

Please sign in to comment.