diff --git a/server/power.go b/server/power.go index fff2f85..39544840 100644 --- a/server/power.go +++ b/server/power.go @@ -53,27 +53,44 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error s.powerLock = semaphore.NewWeighted(1) } - // Determines if we should wait for the lock or not. If a value greater than 0 is passed - // into this function we will wait that long for a lock to be acquired. - if len(waitSeconds) > 0 && waitSeconds[0] != 0 { - ctx, _ := context.WithTimeout(context.Background(), time.Second*time.Duration(waitSeconds[0])) - // Attempt to acquire a lock on the power action lock for up to 30 seconds. If more - // time than that passes an error will be propagated back up the chain and this - // request will be aborted. - if err := s.powerLock.Acquire(ctx, 1); err != nil { - return errors.WithMessage(err, "could not acquire lock on power state") + // Only attempt to acquire a lock on the process if this is not a termination event. We want to + // just allow those events to pass right through for good reason. If a server is currently trying + // to process a power action but has gotten stuck you still should be able to pass through the + // terminate event. The good news here is that doing that oftentimes will get the stuck process to + // move again, and naturally continue through the process. + if action != PowerActionTerminate { + // Determines if we should wait for the lock or not. If a value greater than 0 is passed + // into this function we will wait that long for a lock to be acquired. + if len(waitSeconds) > 0 && waitSeconds[0] != 0 { + ctx, _ := context.WithTimeout(context.Background(), time.Second*time.Duration(waitSeconds[0])) + // Attempt to acquire a lock on the power action lock for up to 30 seconds. If more + // time than that passes an error will be propagated back up the chain and this + // request will be aborted. + if err := s.powerLock.Acquire(ctx, 1); err != nil { + return errors.WithMessage(err, "could not acquire lock on power state") + } + } else { + // If no wait duration was provided we will attempt to immediately acquire the lock + // and bail out with a context deadline error if it is not acquired immediately. + if ok := s.powerLock.TryAcquire(1); !ok { + return errors.WithMessage(context.DeadlineExceeded, "could not acquire lock on power state") + } } + + // Release the lock once the process being requested has finished executing. + defer s.powerLock.Release(1) } else { - // If no wait duration was provided we will attempt to immediately acquire the lock - // and bail out with a context deadline error if it is not acquired immediately. - if ok := s.powerLock.TryAcquire(1); !ok { - return errors.WithMessage(context.DeadlineExceeded, "could not acquire lock on power state") + // Still try to acquire the lock if terminating and it is available, just so that other power + // actions are blocked until it has completed. However, if it is unavailable we won't stop + // the entire process. + if ok := s.powerLock.TryAcquire(1); ok { + // If we managed to acquire the lock be sure to released it once this process is completed. + defer s.powerLock.Release(1) } } - // Release the lock once the process being requested has finished executing. - defer s.powerLock.Release(1) - + // Ensure the server data is properly synced before attempting to start the process, and that there + // is enough disk space available. if action.IsStart() { s.Log().Info("syncing server configuration with panel") if err := s.Sync(); err != nil {