Skip to content

Commit

Permalink
context: introduce dependent context
Browse files Browse the repository at this point in the history
The PR jaypipes#236 fixes a snapshot
directory leakage issue, but also highlight a context
lifecycle/ownership issue.

In general in ghw each package creates its own context, including
the cases on which a package is consumed by another, for example
`topology` and `pci`. However, in this case, it would make more sense
to reuse the context from the parent package, because the two packages
are tightly coupled.

Before the introduction of the the transparent snapshot support
(jaypipes#202), the above mechanism worked,
because each context, each time, was consuming over and over again the
same parameters (e.g. environment variables); besides some resource
waste, this had no negative effect.

When introducing snapshots in the picture, repeatedly unpacking the
same snapshot to consume the same data is much more wasteful.
So it makes sense to introduce explicitly the concept of dependent
context.

We add a functio to create a context subordinate to another.
The subordinate context will effectively borrow all the resources from
the parent one. The parent is in charge to perform any setup/teardown
(e.g. for snapshot, to learn which chroot should be used, if at all)
and so forth.
Making this dependency explicit allow the client code to manage
correctly the lifecycle of the topmost context, hence of all the
resources.

Signed-off-by: Francesco Romani <fromani@redhat.com>
  • Loading branch information
ffromani committed Apr 12, 2021
1 parent 14f2fd2 commit 0c2739a
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 12 deletions.
13 changes: 12 additions & 1 deletion pkg/baseboard/baseboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,18 @@ func (i *Info) String() string {
// New returns a pointer to an Info struct containing information about the
// host's baseboard
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return newWithContext(context.New(opts...))
}

// New returns a pointer to an Info struct containing information about the
// host's baseboard, reusing a given context.
// Use this function when you want to consume this package from another,
// ensuring the two see a coherent set of resources.
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down
13 changes: 12 additions & 1 deletion pkg/bios/bios.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,18 @@ func (i *Info) String() string {
// New returns a pointer to a Info struct containing information
// about the host's BIOS
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return newWithContext(context.New(opts...))
}

// New returns a pointer to a Info struct containing information
// about the host's BIOS, reusing a given context.
// Use this function when you want to consume this package from another,
// ensuring the two see a coherent set of resources.
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down
13 changes: 12 additions & 1 deletion pkg/block/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,18 @@ type Info struct {
// New returns a pointer to an Info struct that describes the block storage
// resources of the host system.
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return newWithContext(context.New(opts...))
}

// New returns a pointer to an Info struct that describes the block storage
// resources of the host system, reusing a given context.
// Use this function when you want to consume this package from another,
// ensuring the two see a coherent set of resources.
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down
13 changes: 12 additions & 1 deletion pkg/chassis/chassis.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,18 @@ func (i *Info) String() string {
// New returns a pointer to a Info struct containing information
// about the host's chassis
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return newWithContext(context.New(opts...))
}

// New returns a pointer to a Info struct containing information
// about the host's chassis, reusing a given context.
// Use this function when you want to consume this package from another,
// ensuring the two see a coherent set of resources.
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down
26 changes: 25 additions & 1 deletion pkg/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Context struct {
SnapshotRoot string
SnapshotExclusive bool
alert option.Alerter
parent *Context
}

// New returns a Context struct pointer that has had various options set on it
Expand Down Expand Up @@ -50,7 +51,7 @@ func New(opts ...*option.Option) *Context {
return ctx
}

// FromEnv returns an Option that has been populated from the environs or
// FromEnv returns a Context that has been populated from the environs or
// default options values
func FromEnv() *Context {
chrootVal := option.EnvOrDefaultChroot()
Expand All @@ -67,6 +68,21 @@ func FromEnv() *Context {
}
}

// FromParent returns a Context which is subordinate to another one.
// This means only the parent Context is in charge to the environment (e.g. setting up the snapshot).
// Subordinate contexts will defer any environment-changing action to their parent.
func FromParent(ctx *Context) *Context {
return &Context{
Chroot: ctx.Chroot,
EnableTools: ctx.EnableTools,
SnapshotPath: ctx.SnapshotPath,
SnapshotRoot: ctx.SnapshotRoot,
SnapshotExclusive: ctx.SnapshotExclusive,
alert: ctx.alert,
parent: ctx,
}
}

// Do wraps a Setup/Teardown pair around the given function
func (ctx *Context) Do(fn func() error) error {
err := ctx.Setup()
Expand All @@ -83,6 +99,10 @@ func (ctx *Context) Do(fn func() error) error {
// You should call `Setup` just once. It is safe to call `Setup` if you don't make
// use of optional extra features - `Setup` will do nothing.
func (ctx *Context) Setup() error {
if ctx.parent != nil {
// nothing to do - the parent is in charge
return nil
}
if ctx.SnapshotPath == "" {
// nothing to do!
return nil
Expand Down Expand Up @@ -111,6 +131,10 @@ func (ctx *Context) Setup() error {
// You should always call `Teardown` if you called `Setup` to free any resources
// acquired by `Setup`. Check `Do` for more automated management.
func (ctx *Context) Teardown() error {
if ctx.parent != nil {
// nothing to do - the parent is in charge
return nil
}
if ctx.SnapshotRoot != "" {
// if the client code provided the unpack directory,
// then it is also in charge of the cleanup.
Expand Down
13 changes: 12 additions & 1 deletion pkg/cpu/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,18 @@ type Info struct {
// New returns a pointer to an Info struct that contains information about the
// CPUs on the host system
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return newWithContext(context.New(opts...))
}

// New returns a pointer to an Info struct that contains information about the
// CPUs on the host system, reusing a given context.
// Use this function when you want to consume this package from another,
// ensuring the two see a coherent set of resources.
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down
13 changes: 12 additions & 1 deletion pkg/gpu/gpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,18 @@ type Info struct {
// New returns a pointer to an Info struct that contains information about the
// graphics cards on the host system
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return newWithContext(context.New(opts...))
}

// New returns a pointer to an Info struct that contains information about the
// graphics cards on the host system, reusing a given context.
// Use this function when you want to consume this package from another,
// ensuring the two see a coherent set of resources.
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down
15 changes: 14 additions & 1 deletion pkg/memory/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,21 @@ type Info struct {
Modules []*Module `json:"modules"`
}

// New returns a pointer to an Info struct containing information about the
// host's memory.
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return newWithContext(context.New(opts...))
}

// New returns a pointer to an Info struct containing information about the
// host's memory, reusing a given context.
// Use this function when you want to consume this package from another,
// ensuring the two see a coherent set of resources.
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down
13 changes: 12 additions & 1 deletion pkg/net/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,18 @@ type Info struct {
// New returns a pointer to an Info struct that contains information about the
// network interface controllers (NICs) on the host system
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return newWithContext(context.New(opts...))
}

// New returns a pointer to an Info struct that contains information about the
// network interface controllers (NICs) on the host system, reusing a given context.
// Use this function when you want to consume this package from another,
// ensuring the two see a coherent set of resources.
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down
13 changes: 12 additions & 1 deletion pkg/pci/pci.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,18 @@ func (i *Info) String() string {
// New returns a pointer to an Info struct that contains information about the
// PCI devices on the host system
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return newWithContext(context.New(opts...))
}

// New returns a pointer to an Info struct that contains information about the
// PCI devices on the host system, reusing a given context.
// Use this function when you want to consume this package from another,
// ensuring the two see a coherent set of resources.
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
// by default we don't report NUMA information;
// we will only if are sure we are running on NUMA architecture
arch := topology.ARCHITECTURE_SMP
Expand Down
13 changes: 12 additions & 1 deletion pkg/product/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,18 @@ func (i *Info) String() string {
// New returns a pointer to a Info struct containing information
// about the host's product
func New(opts ...*option.Option) (*Info, error) {
ctx := context.New(opts...)
return newWithContext(context.New(opts...))
}

// New returns a pointer to a Info struct containing information
// about the host's product, reusing a given context.
// Use this function when you want to consume this package from another,
// ensuring the two see a coherent set of resources.
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down
6 changes: 5 additions & 1 deletion pkg/topology/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,17 @@ type Info struct {
// New returns a pointer to an Info struct that contains information about the
// NUMA topology on the host system
func New(opts ...*option.Option) (*Info, error) {
return NewWithContext(context.New(opts...))
return newWithContext(context.New(opts...))
}

// NewWithContext returns a pointer to an Info struct that contains information about
// the NUMA topology on the host system. Use this function when you want to consume
// the topology package from another package (e.g. pci, gpu)
func NewWithContext(ctx *context.Context) (*Info, error) {
return newWithContext(context.FromParent(ctx))
}

func newWithContext(ctx *context.Context) (*Info, error) {
info := &Info{ctx: ctx}
if err := ctx.Do(info.load); err != nil {
return nil, err
Expand Down

0 comments on commit 0c2739a

Please sign in to comment.