diff --git a/internal/component/component.go b/internal/component/component.go index bc1aa86..607a08e 100644 --- a/internal/component/component.go +++ b/internal/component/component.go @@ -3,15 +3,12 @@ package component // Component represents a logical subsystem of the distillery. // Every component must embed [ComponentBase] and should be initialized using [Initialize]. +// A Component should be implemented as a pointer to a struct. // // By convention these are defined within their corresponding subpackage. // This subpackage also contains all required resources. -// Furthermore, a component is typically instantiated using a call on the ["distillery.Distillery"] struct. // -// For example, the web.Web component lives in the web package and can be created like: -// -// var dis Distillery -// web := dis.Web() +// Components are initialized using a [Pool]. type Component interface { // Name returns the name of this component. // It should correspond to the appropriate subpackage. diff --git a/internal/component/pool.go b/internal/component/pool.go index 46500ee..419ed06 100644 --- a/internal/component/pool.go +++ b/internal/component/pool.go @@ -2,123 +2,146 @@ package component import ( "reflect" - "sync" - "sync/atomic" - "github.com/FAU-CDI/wisski-distillery/pkg/rlock" - "github.com/tkw1536/goprogram/lib/reflectx" + "github.com/FAU-CDI/wisski-distillery/pkg/lazy" ) -// Pool represents a pool of components +// Pool holds a pool of components and provides factilities to create and access them. +// See [Pool.All], [ExportComponents] and [ExportComponent]. type Pool struct { - rLock rlock.RLock - - // the actual queue of initi functions! - nested uint64 // is the q active? - queue []func(thread int32) - - // global initalization! - initOnce sync.Once - - // components and lock! - cLock sync.Mutex - components map[string]Component + all lazy.Lazy[[]Component] // all components } -func (p *Pool) init() { - p.initOnce.Do(func() { - p.components = make(map[string]Component) +// All initializes or returns all components stored in this pool. +// +// The All function should return an array of calls to [Make] with the provided context. +// Multiple calls to the this method return the same return value. +func (p *Pool) All(All func(context *PoolContext) []Component) []Component { + return p.all.Get(func() []Component { + // create a new context + context := &PoolContext{ + all: All, + cache: make(map[string]Component), + } + + // and process them all + all := context.all(context) + context.process(all) + return all }) } -// InitComponent initializes a specific component and caches it within the given pool. -// -// Concurrent calls of InitComponent must use a distinct thread parameter. -// Nested calls of InitComponent should use the same thread parameter. -// -// Init may initialize components, but not call functions on them! -func InitComponent[C Component](p *Pool, thread int32, core Core, init func(component C, thread int32)) C { - p.init() +// PoolContext is a context used during [Make]. +// It should not be initialized by a user. +type PoolContext struct { + all func(context *PoolContext) []Component // function to return all components - p.rLock.Lock(int(thread)) - defer p.rLock.Unlock() + cache map[string]Component // cached components + queue []delayedInit // init queue +} - // get a description of the type - cd := GetMeta[C]() +type delayedInit struct { + meta meta + value reflect.Value +} - // find a field to put the component into - instance, created := func() (C, bool) { - p.cLock.Lock() - defer p.cLock.Unlock() +func (di delayedInit) Do(all []Component) { + di.meta.InitComponent(di.value, all) +} - // create the component - field, ok := p.components[cd.Name] - if ok { - return field.(C), false - } - - // create a new component - p.components[cd.Name] = cd.New().(Component) - return p.components[cd.Name].(C), true - }() - - // if we already created the instance, then there is nothing to do - // as someone else will init it! - if !created { - return instance - } - - // setup the core initialization now! - instance.getBase().Core = core - - if init == nil { - return instance - } - - // if we are in nested mode, then delay the init! - if !atomic.CompareAndSwapUint64(&p.nested, 0, 1) { - func() { - p.queue = append(p.queue, func(thread int32) { - init(instance, thread) - }) - }() - return instance - } - defer atomic.StoreUint64(&p.nested, 0) - - // init ourselves first (everything below will be nested) - init(instance, thread) - - // do all the delayed initializations +// process processes the queue in this process +func (p *PoolContext) process(all []Component) { index := 0 for len(p.queue) > index { - p.queue[index](thread) + p.queue[index].Do(all) index++ } p.queue = nil +} + +// Make creates or returns a cached component of the given Context. +// +// Components are initialized by first calling the init function. +// Then all component-like fields of fields are filled with their appropriate components. +// +// A component-like field has one of the following types: +// +// - A pointer to a struct type that implements component +// - A slice type of an interface type that implements component +// +// These fields are initialized in an undefined order during initialization. +// The init function may not rely on these existing. +// Furthermore, the init function may not cause other components to be initialized. +// +// The init function may be nil, indicating that no additional initialization is required. +func Make[C Component](context *PoolContext, core Core, init func(component C)) C { + // get a description of the type + cd := getMeta[C]() + + // if an instance already exists, return it! + if instance, ok := context.cache[cd.Name]; ok { + return instance.(C) + } + + // make sure that we have an array of components + if context.cache == nil { + context.cache = make(map[string]Component) + } + + // create a fresh (empty) instance + context.cache[cd.Name] = cd.New() + instance := context.cache[cd.Name].(C) + + // do the core and self-initialization + instance.getBase().Core = core + + if init != nil { + init(instance) + } + + if cd.NeedsInitComponent() { + context.queue = append(context.queue, delayedInit{ + meta: cd, + value: reflect.ValueOf(instance), + }) + } - // and return the instance return instance } -// GetMeta gets the component belonging to a component type -func GetMeta[C Component]() (meta Meta) { - tp := reflectx.TypeOf[C]() - if tp.Kind() != reflect.Pointer { - panic("GetMeta: C must be backed by a pointer (" + tp.String() + ")") +// +// PUBLIC FUNCTIONS +// + +// ExportComponents exports all components that are a C from the pool. +// +// All should be the function of the core that initializes all components. +// All should only make calls to [InitComponent]. +func ExportComponents[C Component](p *Pool, All func(context *PoolContext) []Component) []C { + components := p.All(All) + + results := make([]C, 0, len(components)) + for _, comp := range components { + if cc, ok := comp.(C); ok { + results = append(results, cc) + } } - meta.Elem = tp.Elem() - meta.Name = meta.Elem.PkgPath() + "." + meta.Elem.Name() - return + return results } -// Meta represents meta information about a component -type Meta struct { - Elem reflect.Type // the element type of the component - Name string // the name of the component -} +// ExportComponent exports the first component that is a C from the pool. +// +// All should be the function of the core that initializes all components. +// All should only make calls to [InitComponent]. +func ExportComponent[C Component](p *Pool, All func(context *PoolContext) []Component) C { + components := p.All(All) -// New creates a new ComponentDescription -func (cd Meta) New() any { - return reflect.New(cd.Elem).Interface() + for _, comp := range components { + if cc, ok := comp.(C); ok { + return cc + } + } + + var c C + return c } diff --git a/internal/component/pool_meta.go b/internal/component/pool_meta.go new file mode 100644 index 0000000..82330c7 --- /dev/null +++ b/internal/component/pool_meta.go @@ -0,0 +1,126 @@ +package component + +import ( + "reflect" + "sync" + + "github.com/FAU-CDI/wisski-distillery/pkg/slicesx" + "github.com/tkw1536/goprogram/lib/reflectx" +) + +// +// META +// + +var metaCache sync.Map + +// getMeta gets the component belonging to a component type +func getMeta[C Component]() meta { + tp := reflectx.TypeOf[C]() + + // we already have a m => return it + if m, ok := metaCache.Load(tp); ok { + return m.(meta) + } + + // create a new m + var m meta + m.init(tp) + + // store it in the cache + metaCache.Store(tp, m) + return m +} + +// meta stores meta-information about a specific component +type meta struct { + Name string // the type name of this component + Elem reflect.Type // the element type of the component + + CFields map[string]reflect.Type // fields with type C for which C implements component + IFields map[string]reflect.Type // fields []I where I is an interface that implements component +} + +// componentType is the type of components +var componentType = reflectx.TypeOf[Component]() + +// init initializes this refklecttype +func (m *meta) init(tp reflect.Type) { + if tp.Kind() != reflect.Pointer && tp.Elem().Kind() != reflect.Struct { + panic("GetMeta: Type (" + tp.String() + ") must be backed by a pointer to slice") + } + + m.Name = tp.Elem().PkgPath() + "." + tp.Elem().Name() + m.Elem = tp.Elem() + + m.CFields = make(map[string]reflect.Type) + m.IFields = make(map[string]reflect.Type) + + // fill the above variables, with a mapping of field name to struct + count := m.Elem.NumField() + for i := 0; i < count; i++ { + field := m.Elem.Field(i) + + name := field.Name + tp := field.Type + + switch { + // field is a pointer to struct that implements a component + case tp.Implements(componentType) && tp.Kind() == reflect.Pointer && tp.Elem().Kind() == reflect.Struct: + m.CFields[name] = tp + + // field is []I, where I is an interface that implements component + case tp.Kind() == reflect.Slice && tp.Elem().Kind() == reflect.Interface && tp.Elem().Implements(componentType): + m.IFields[name] = tp.Elem() + } + } +} + +// New creates a new ComponentDescription +func (m meta) New() Component { + return reflect.New(m.Elem).Interface().(Component) +} + +// NeedsInitComponent +func (m meta) NeedsInitComponent() bool { + return len(m.CFields) > 0 || len(m.IFields) > 0 +} + +// InitComponent sets up the fields of the given instance of a component. +func (m meta) InitComponent(instance reflect.Value, all []Component) { + elem := instance.Elem() + + // assign the component fields + for field, eType := range m.CFields { + c := slicesx.First(all, func(c Component) bool { + return reflect.TypeOf(c).AssignableTo(eType) + }) + + field := elem.FieldByName(field) + field.Set(reflect.ValueOf(c)) + } + + // assign the multi subtypes + registryR := reflect.ValueOf(all) + for field, eType := range m.IFields { + cs := filterSubtype(registryR, eType) + field := elem.FieldByName(field) + field.Set(cs) + } +} + +// filterSubtype filters the slice of type []S into a slice of type []iface. +// S and I must be interface types. +func filterSubtype(sliceS reflect.Value, iface reflect.Type) reflect.Value { + len := sliceS.Len() + + // convert each element + result := reflect.MakeSlice(reflect.SliceOf(iface), 0, len) + for i := 0; i < len; i++ { + element := sliceS.Index(i) + if element.Type().Implements(iface) { + result = reflect.Append(result, element.Elem().Convert(iface)) + } + } + return result +} diff --git a/internal/dis/component.go b/internal/dis/component.go index e245b00..1762762 100644 --- a/internal/dis/component.go +++ b/internal/dis/component.go @@ -1,8 +1,6 @@ package dis import ( - "reflect" - "sync/atomic" "time" "github.com/FAU-CDI/wisski-distillery/internal/component" @@ -15,173 +13,53 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/component/triplestore" "github.com/FAU-CDI/wisski-distillery/internal/component/web" "github.com/FAU-CDI/wisski-distillery/internal/core" - "github.com/FAU-CDI/wisski-distillery/pkg/slicesx" - "github.com/tkw1536/goprogram/lib/reflectx" ) -// components holds the various components of the distillery -// It is inlined into the [Distillery] struct, and initialized using [makeComponent]. -// -// The caller is responsible for syncronizing access across multiple goroutines. -type components struct { - t int32 // t is the previously used thread id! - pool component.Pool -} - // register returns all components of the distillery -func register(dis *Distillery, thread int32) []component.Component { +func (dis *Distillery) register(context *component.PoolContext) []component.Component { return []component.Component{ - ra[*web.Web](dis, thread), + ra[*web.Web](dis, context), - ra[*ssh.SSH](dis, thread), + ra[*ssh.SSH](dis, context), - r(dis, thread, func(ts *triplestore.Triplestore) { + r(dis, context, func(ts *triplestore.Triplestore) { ts.BaseURL = "http://" + dis.Upstream.Triplestore ts.PollContext = dis.Context() ts.PollInterval = time.Second }), - r(dis, thread, func(sql *sql.SQL) { + r(dis, context, func(sql *sql.SQL) { sql.ServerURL = dis.Upstream.SQL sql.PollContext = dis.Context() sql.PollInterval = time.Second }), - ra[*instances.Instances](dis, thread), + ra[*instances.Instances](dis, context), // Snapshots - ra[*snapshots.Manager](dis, thread), - ra[*snapshots.Config](dis, thread), - ra[*snapshots.Bookkeeping](dis, thread), - ra[*snapshots.Filesystem](dis, thread), - ra[*snapshots.Pathbuilders](dis, thread), + ra[*snapshots.Manager](dis, context), + ra[*snapshots.Config](dis, context), + ra[*snapshots.Bookkeeping](dis, context), + ra[*snapshots.Filesystem](dis, context), + ra[*snapshots.Pathbuilders](dis, context), // Control server - r(dis, thread, func(control *control.Control) { + r(dis, context, func(control *control.Control) { control.ResolverFile = core.PrefixConfig }), - ra[*control.SelfHandler](dis, thread), - r(dis, thread, func(resolver *resolver.Resolver) { + ra[*control.SelfHandler](dis, context), + r(dis, context, func(resolver *resolver.Resolver) { resolver.ResolverFile = core.PrefixConfig }), - ra[*control.Info](dis, thread), + ra[*control.Info](dis, context), } } // r initializes a component from the provided distillery. -func r[C component.Component](dis *Distillery, thread int32, init func(component C)) C { - return component.InitComponent(&dis.pool, thread, dis.Core, makeInitFunction(dis, init)) +func r[C component.Component](dis *Distillery, context *component.PoolContext, init func(component C)) C { + return component.Make(context, dis.Core, init) } // ra is like r, but does not provided additional initialization -func ra[C component.Component](dis *Distillery, thread int32) C { - return r[C](dis, thread, nil) -} - -var componentType = reflectx.TypeOf[component.Component]() - -// makeInitFunction generate an init function for a specific component. -// The function should be called at most once. -func makeInitFunction[C component.Component](dis *Distillery, rest func(instance C)) func(instance C, thread int32) { - return func(instance C, thread int32) { - meta := component.GetMeta[C]() - - // this function automatically initializes component.Component-like fields of the instance. - // for this we first store two types of fields: - - singles := make(map[string]reflect.Type) // fields of type C where C is a component.Type - multis := make(map[string]reflect.Type) // fields of type []C where C is an interface that implements component. - - // fill the above variables, with a mapping of field name to struct - count := meta.Elem.NumField() - for i := 0; i < count; i++ { - field := meta.Elem.Field(i) - - name := field.Name - tp := field.Type - - switch { - // field is a `Component`` - case tp.Implements(componentType): - singles[name] = tp - // field is a `[]Component`` - case tp.Kind() == reflect.Slice && tp.Elem().Kind() == reflect.Interface && tp.Elem().Implements(componentType): - multis[name] = tp.Elem() - } - } - - // do the rest of the initialization - if rest != nil { - defer rest(instance) - } - - if len(singles) == 0 && len(multis) == 0 { - // no fields to assign, bail out immediatly - return - } - - registry := register(dis, thread) - - instanceV := reflect.ValueOf(instance).Elem() - - // assign the component fields - for field, eType := range singles { - c := slicesx.First(registry, func(c component.Component) bool { - return reflect.TypeOf(c).AssignableTo(eType) - }) - - field := instanceV.FieldByName(field) - field.Set(reflect.ValueOf(c)) - } - - // assign the multi subtypes - registryR := reflect.ValueOf(registry) - for field, eType := range multis { - cs := filterSubtype(registryR, eType) - field := instanceV.FieldByName(field) - field.Set(cs) - } - } -} - -// filterSubtype filters the slice of type []S into a slice of type []iface. -// S and I must be interface types. -func filterSubtype(sliceS reflect.Value, iface reflect.Type) reflect.Value { - len := sliceS.Len() - - // convert each element - result := reflect.MakeSlice(reflect.SliceOf(iface), 0, len) - for i := 0; i < len; i++ { - element := sliceS.Index(i) - if element.Type().Implements(iface) { - result = reflect.Append(result, element.Elem().Convert(iface)) - } - } - return result -} - -// -// Export Components -// - -// ea exports all components of the given subtype -func ea[C component.Component](dis *Distillery) []C { - registry := register(dis, atomic.AddInt32(&dis.t, 1)) - - results := make([]C, 0, len(registry)) - for _, comp := range registry { - if cc, ok := comp.(C); ok { - results = append(results, cc) - } - } - return results -} - -// e exports a single component of the given subtype -func e[C component.Component](dis *Distillery) C { - for _, comp := range register(dis, atomic.AddInt32(&dis.t, 1)) { - if cc, ok := comp.(C); ok { - return cc - } - } - panic("e: component is missing") +func ra[C component.Component](dis *Distillery, context *component.PoolContext) C { + return r[C](dis, context, nil) } diff --git a/internal/dis/distillery.go b/internal/dis/distillery.go index eb816ce..91d84e6 100644 --- a/internal/dis/distillery.go +++ b/internal/dis/distillery.go @@ -28,8 +28,8 @@ type Distillery struct { // But for now this will just hold upstream configuration. Upstream Upstream - // components hold references to the various components of the distillery. - components + // Pool holds all the components in this pool + pool component.Pool } // Upstream contains the configuration for accessing remote configuration. @@ -47,6 +47,21 @@ func (dis *Distillery) Context() context.Context { // PUBLIC COMPONENT GETTERS // +// e is a convenience function to export a single component +func e[C component.Component](dis *Distillery) C { + return component.ExportComponent[C]( + &dis.pool, + dis.register, + ) +} + +func ea[C component.Component](dis *Distillery) []C { + return component.ExportComponents[C]( + &dis.pool, + dis.register, + ) +} + func (dis *Distillery) Control() *control.Control { return e[*control.Control](dis) } diff --git a/pkg/rlock/rlock.go b/pkg/rlock/rlock.go deleted file mode 100644 index 49c4ea6..0000000 --- a/pkg/rlock/rlock.go +++ /dev/null @@ -1,53 +0,0 @@ -package rlock - -import ( - "sync" - "time" -) - -// RLock is like [sync.Mutex], but permits recursive locking. -type RLock struct { - m sync.Mutex // m is held internally - - held bool - holder int - counter uint64 -} - -// Lock acquires this lock with the given id, and blocks until it can be aquired. -// Concurrent locks with the same ids do not block; however each should be unlocked with a call to unlock. -func (rm *RLock) Lock(id int) { -loop: - for { - rm.m.Lock() - switch { - case !rm.held: - rm.held = true - rm.holder = id - break loop - case rm.held && rm.holder == id: - break loop - } - rm.m.Unlock() - time.Sleep(time.Millisecond) // spinning! - } - - rm.counter++ - rm.m.Unlock() -} - -// Unlock releases the lock -func (rm *RLock) Unlock() { - rm.m.Lock() - defer rm.m.Unlock() - - if !rm.held || rm.counter <= 0 { - panic("RLock: Unlock() without Lock()") - } - - rm.counter-- - if rm.counter == 0 { - rm.held = false - rm.holder = 0 - } -}