The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6256
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From eb476bdacb91a77d263169b292ec9d25cd952e24 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Fri, 27 Sep 2019 16:00:23 +0100 Subject: [PATCH 1/2] lxd/instance/operationlock: Adds operationlock package Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/operationlock/operationlock.go | 109 ++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 lxd/instance/operationlock/operationlock.go diff --git a/lxd/instance/operationlock/operationlock.go b/lxd/instance/operationlock/operationlock.go new file mode 100644 index 0000000000..a115506a1e --- /dev/null +++ b/lxd/instance/operationlock/operationlock.go @@ -0,0 +1,109 @@ +package operationlock + +import ( + "fmt" + "sync" + "time" +) + +var instanceOperationsLock sync.Mutex +var instanceOperations map[int]*InstanceOperation = make(map[int]*InstanceOperation) + +// InstanceOperation operation locking. +type InstanceOperation struct { + action string + chanDone chan error + chanReset chan bool + err error + id int + reusable bool +} + +// Action returns operation's action. +func (op InstanceOperation) Action() string { + return op.action +} + +// Create creates a new operation lock for an Instance if one does not already exist and returns it. +// The lock will be released after 30s or when Done() is called, which ever occurs first. +// If reusable is set as true then future lock attempts can specify the reuse argument as true which +// will then trigger a reset of the 30s timeout on the existing lock and return it. +func Create(instanceID int, action string, reusable bool, reuse bool) (*InstanceOperation, error) { + instanceOperationsLock.Lock() + defer instanceOperationsLock.Unlock() + + op := instanceOperations[instanceID] + if op != nil { + if op.reusable && reuse { + op.Reset() + return op, nil + } + + return nil, fmt.Errorf("Instance is busy running a %s operation", op.action) + } + + op = &InstanceOperation{} + op.id = instanceID + op.action = action + op.reusable = reusable + op.chanDone = make(chan error, 0) + op.chanReset = make(chan bool, 0) + + go func(op *InstanceOperation) { + for { + select { + case <-op.chanReset: + continue + case <-time.After(time.Second * 30): + op.Done(fmt.Errorf("Instance %s operation timed out after 30 seconds", op.action)) + return + } + } + }(op) + + instanceOperations[instanceID] = op + + return op, nil +} + +// Get retrieves an existing lock or returns nil if no lock exists. +func Get(instanceID int) *InstanceOperation { + instanceOperationsLock.Lock() + defer instanceOperationsLock.Unlock() + + return instanceOperations[instanceID] +} + +// Reset resets an operation. +func (op *InstanceOperation) Reset() error { + if !op.reusable { + return fmt.Errorf("Can't reset a non-reusable operation") + } + + op.chanReset <- true + return nil +} + +// Wait waits for an operation to finish. +func (op *InstanceOperation) Wait() error { + <-op.chanDone + + return op.err +} + +// Done indicates the operation has finished. +func (op *InstanceOperation) Done(err error) { + instanceOperationsLock.Lock() + defer instanceOperationsLock.Unlock() + + // Check if already done + runningOp, ok := instanceOperations[op.id] + if !ok || runningOp != op { + return + } + + op.err = err + close(op.chanDone) + + delete(instanceOperations, op.id) +} From 938d7ef6a77a3ab8032a7a96bffa8b5e9a5634f0 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Fri, 27 Sep 2019 16:00:41 +0100 Subject: [PATCH 2/2] lxd/container/lxc: Migrates container_lxc to use operationlock package Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/container_lxc.go | 137 ++++--------------------------------------- 1 file changed, 11 insertions(+), 126 deletions(-) diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go index 91e787796d..3ec915c73b 100644 --- a/lxd/container_lxc.go +++ b/lxd/container_lxc.go @@ -33,6 +33,7 @@ import ( "github.com/lxc/lxd/lxd/device/config" "github.com/lxc/lxd/lxd/events" "github.com/lxc/lxd/lxd/instance/instancetype" + "github.com/lxc/lxd/lxd/instance/operationlock" "github.com/lxc/lxd/lxd/maas" "github.com/lxc/lxd/lxd/operations" "github.com/lxc/lxd/lxd/project" @@ -53,72 +54,6 @@ import ( log "github.com/lxc/lxd/shared/log15" ) -// Operation locking -type lxcContainerOperation struct { - action string - chanDone chan error - chanReset chan bool - err error - id int - reusable bool -} - -func (op *lxcContainerOperation) Create(id int, action string, reusable bool) *lxcContainerOperation { - op.id = id - op.action = action - op.reusable = reusable - op.chanDone = make(chan error, 0) - op.chanReset = make(chan bool, 0) - - go func(op *lxcContainerOperation) { - for { - select { - case <-op.chanReset: - continue - case <-time.After(time.Second * 30): - op.Done(fmt.Errorf("Container %s operation timed out after 30 seconds", op.action)) - return - } - } - }(op) - - return op -} - -func (op *lxcContainerOperation) Reset() error { - if !op.reusable { - return fmt.Errorf("Can't reset a non-reusable operation") - } - - op.chanReset <- true - return nil -} - -func (op *lxcContainerOperation) Wait() error { - <-op.chanDone - - return op.err -} - -func (op *lxcContainerOperation) Done(err error) { - lxcContainerOperationsLock.Lock() - defer lxcContainerOperationsLock.Unlock() - - // Check if already done - runningOp, ok := lxcContainerOperations[op.id] - if !ok || runningOp != op { - return - } - - op.err = err - close(op.chanDone) - - delete(lxcContainerOperations, op.id) -} - -var lxcContainerOperationsLock sync.Mutex -var lxcContainerOperations map[int]*lxcContainerOperation = make(map[int]*lxcContainerOperation) - // Helper functions func lxcSetConfigItem(c *lxc.Container, key string, value string) error { if c == nil { @@ -618,56 +553,6 @@ func (c *containerLXC) Type() instancetype.Type { return c.dbType } -func (c *containerLXC) createOperation(action string, reusable bool, reuse bool) (*lxcContainerOperation, error) { - op, _ := c.getOperation("") - if op != nil { - if reuse && op.reusable { - op.Reset() - return op, nil - } - - return nil, fmt.Errorf("Container is busy running a %s operation", op.action) - } - - lxcContainerOperationsLock.Lock() - defer lxcContainerOperationsLock.Unlock() - - op = &lxcContainerOperation{} - op.Create(c.id, action, reusable) - lxcContainerOperations[c.id] = op - - return lxcContainerOperations[c.id], nil -} - -func (c *containerLXC) getOperation(action string) (*lxcContainerOperation, error) { - lxcContainerOperationsLock.Lock() - defer lxcContainerOperationsLock.Unlock() - - op := lxcContainerOperations[c.id] - - if op == nil { - return nil, fmt.Errorf("No running %s container operation", action) - } - - if action != "" && op.action != action { - return nil, fmt.Errorf("Container is running a %s operation, not a %s operation", op.action, action) - } - - return op, nil -} - -func (c *containerLXC) waitOperation() error { - op, _ := c.getOperation("") - if op != nil { - err := op.Wait() - if err != nil { - return err - } - } - - return nil -} - func idmapSize(state *state.State, isolatedStr string, size string) (int64, error) { isolated := false if shared.IsTrue(isolatedStr) { @@ -2527,7 +2412,7 @@ func (c *containerLXC) Start(stateful bool) error { var ctxMap log.Ctx // Setup a new operation - op, err := c.createOperation("start", false, false) + op, err := operationlock.Create(c.id, "start", false, false) if err != nil { return errors.Wrap(err, "Create container start operation") } @@ -2553,7 +2438,7 @@ func (c *containerLXC) Start(stateful bool) error { ctxMap = log.Ctx{ "project": c.project, "name": c.name, - "action": op.action, + "action": op.Action(), "created": c.creationDate, "ephemeral": c.ephemeral, "used": c.lastUsedDate, @@ -2774,7 +2659,7 @@ func (c *containerLXC) Stop(stateful bool) error { } // Setup a new operation - op, err := c.createOperation("stop", false, true) + op, err := operationlock.Create(c.id, "stop", false, true) if err != nil { return err } @@ -2782,7 +2667,7 @@ func (c *containerLXC) Stop(stateful bool) error { ctxMap = log.Ctx{ "project": c.project, "name": c.name, - "action": op.action, + "action": op.Action(), "created": c.creationDate, "ephemeral": c.ephemeral, "used": c.lastUsedDate, @@ -2908,7 +2793,7 @@ func (c *containerLXC) Shutdown(timeout time.Duration) error { } // Setup a new operation - op, err := c.createOperation("stop", true, true) + op, err := operationlock.Create(c.id, "stop", true, true) if err != nil { return err } @@ -2984,10 +2869,10 @@ func (c *containerLXC) OnStop(target string) error { return fmt.Errorf("Invalid stop target: %s", target) } - // Get operation - op, _ := c.getOperation("") - if op != nil && op.action != "stop" { - return fmt.Errorf("Container is already running a %s operation", op.action) + // Pick up the existing stop operation lock created in Stop() function. + op := operationlock.Get(c.id) + if op != nil && op.Action() != "stop" { + return fmt.Errorf("Container is already running a %s operation", op.Action()) } // Make sure we can't call go-lxc functions by mistake @@ -3042,7 +2927,7 @@ func (c *containerLXC) OnStop(target string) error { logger.Error("Failed to set container state", log.Ctx{"container": c.Name(), "err": err}) } - go func(c *containerLXC, target string, op *lxcContainerOperation) { + go func(c *containerLXC, target string, op *operationlock.InstanceOperation) { c.fromHook = false err = nil
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel