Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions src/cmd/initContainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package cmd

import (
"context"
"errors"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -353,6 +354,28 @@ func initContainer(cmd *cobra.Command, args []string) error {
return err
}

referenceCountGlobalLock, err := utils.GetReferenceCountGlobalLock(targetUser)
if err != nil {
return err
}

var waitForRun bool
if referenceCountGlobalLockFile, err := utils.Flock(referenceCountGlobalLock,
syscall.LOCK_EX|syscall.LOCK_NB); err == nil {
waitForRun = true
if err := referenceCountGlobalLockFile.Close(); err != nil {
logrus.Debugf("Releasing global reference count lock: %s", err)
return utils.ErrFlockRelease
}
}

parentCtx := context.Background()
waitForExitCtx, waitForExitCancel := context.WithCancelCause(parentCtx)
defer waitForExitCancel(errors.New("clean-up"))

detectWhenContainerIsUnsedAsync(waitForExitCancel, initializedStamp, referenceCountGlobalLock, waitForRun)
done := waitForExitCtx.Done()

logrus.Debugf("Creating initialization stamp %s", initializedStamp)

initializedStampFile, err := os.Create(initializedStamp)
Expand All @@ -372,6 +395,16 @@ func initContainer(cmd *cobra.Command, args []string) error {

for {
select {
case <-done:
logrus.Debugf("Removing initialization stamp %s", initializedStamp)
if err := os.Remove(initializedStamp); err != nil {
logrus.Debugf("Removing initialization stamp %s failed: %s", initializedStamp, err)
return errors.New("failed to remove initialization stamp")
}

cause := context.Cause(waitForExitCtx)
logrus.Debugf("Exiting entry point: %s", cause)
return nil
case event := <-tickerDaily.C:
handleDailyTick(event)
case event := <-watcherForHostEvents:
Expand Down Expand Up @@ -879,6 +912,55 @@ func createSymbolicLink(existingTarget, newLink string) error {
return nil
}

func detectWhenContainerIsUnsedAsync(cancel context.CancelCauseFunc,
initializedStamp, referenceCountGlobalLock string,
waitForRun bool) {

go func() {
if waitForRun {
logrus.Debugf("This entry point was not started by 'toolbox enter' or 'toolbox run'")
logrus.Debugf("Waiting for 'toolbox enter' or 'toolbox run'")
time.Sleep(25 * time.Second)
}

for {
logrus.Debugf("Waiting for 'podman exec' to begin")
if err := waitForExecToBegin(referenceCountGlobalLock); err != nil {
if errors.Is(err, utils.ErrFlockRelease) {
cancel(err)
} else {
logrus.Debugf("Waiting for 'podman exec' to begin: %s", err)
logrus.Debug("This entry point will not exit when the container is unused")
}

return
}

logrus.Debugf("Waiting for the container to be unused")
if err := waitForContainerToBeUnused(initializedStamp,
referenceCountGlobalLock); err != nil {
if errors.Is(err, syscall.EWOULDBLOCK) {
logrus.Debug("Detected potentially new use of the container")
continue
}

if errors.Is(err, utils.ErrFlockRelease) {
cancel(err)
} else {
logrus.Debugf("Waiting for the container to be unused: %s", err)
logrus.Debug("This entry point will not exit when the container is unused")
}

return
}

cause := errors.New("all 'podman exec' sessions exited")
cancel(cause)
return
}
}()
}

func getDelayEntryPoint() (time.Duration, bool) {
valueString := os.Getenv("TOOLBX_DELAY_ENTRY_POINT")
if valueString == "" {
Expand Down Expand Up @@ -1214,6 +1296,43 @@ func updateTimeZoneFromLocalTime() error {
return nil
}

func waitForExecToBegin(referenceCountGlobalLock string) error {
referenceCountGlobalLockFile, err := utils.Flock(referenceCountGlobalLock, syscall.LOCK_EX)
if err != nil {
return err
}

if err := referenceCountGlobalLockFile.Close(); err != nil {
logrus.Debugf("Releasing global reference count lock: %s", err)
return utils.ErrFlockRelease
}

return nil
}

func waitForContainerToBeUnused(initializedStamp, referenceCountGlobalLock string) error {
referenceCountLocalLockFile, err := utils.Flock(initializedStamp, syscall.LOCK_EX)
if err != nil {
if errors.Is(err, syscall.EWOULDBLOCK) {
panicMsg := fmt.Sprintf("unexpected %T: %s", err, err)
panic(panicMsg)
}

return err
}

if _, err := utils.Flock(referenceCountGlobalLock, syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
if err := referenceCountLocalLockFile.Close(); err != nil {
logrus.Debugf("Releasing local reference count lock: %s", err)
return utils.ErrFlockRelease
}

return err
}

return nil
}

func writeTimeZone(timeZone string) error {
const etcTimeZone = "/etc/timezone"

Expand Down
72 changes: 65 additions & 7 deletions src/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,35 @@ func runCommand(container string,
}
}

logrus.Debug("Acquiring global reference count lock")

referenceCountGlobalLock, err := utils.GetReferenceCountGlobalLock(currentUser)
if err != nil {
return err
}

referenceCountGlobalLockFile, err := utils.Flock(referenceCountGlobalLock, syscall.LOCK_SH)
if err != nil {
logrus.Debugf("Acquiring global reference count lock: %s", err)

var errFlock *utils.FlockError

if errors.As(err, &errFlock) {
if errors.Is(err, utils.ErrFlockAcquire) {
err = utils.ErrFlockAcquire
} else if errors.Is(err, utils.ErrFlockCreate) {
err = utils.ErrFlockCreate
} else {
panicMsg := fmt.Sprintf("unexpected %T: %s", err, err)
panic(panicMsg)
}
}

return err
}

defer referenceCountGlobalLockFile.Close()

logrus.Debugf("Inspecting container %s", container)
containerObj, err := podman.InspectContainer(container)
if err != nil {
Expand Down Expand Up @@ -335,11 +364,45 @@ func runCommand(container string,
logrus.Debugf("Waiting for container %s to finish initializing", container)
}

if err := ensureContainerIsInitialized(container, entryPointPID, startContainerTimestamp); err != nil {
initializedStamp, err := utils.GetInitializedStamp(entryPointPID, currentUser)
if err != nil {
return err
}

if err := ensureContainerIsInitialized(container, initializedStamp, startContainerTimestamp); err != nil {
return err
}

logrus.Debugf("Container %s is initialized", container)
logrus.Debug("Acquiring local reference count lock")

referenceCountLocalLockFile, err := utils.Flock(initializedStamp, syscall.LOCK_SH)
if err != nil {
logrus.Debugf("Acquiring local reference count lock: %s", err)

var errFlock *utils.FlockError

if errors.As(err, &errFlock) {
if errors.Is(err, utils.ErrFlockAcquire) {
err = utils.ErrFlockAcquire
} else if errors.Is(err, utils.ErrFlockCreate) {
err = utils.ErrFlockCreate
} else {
panicMsg := fmt.Sprintf("unexpected %T: %s", err, err)
panic(panicMsg)
}
}

return err
}

defer referenceCountLocalLockFile.Close()

logrus.Debug("Releasing global reference count lock")
if err := referenceCountGlobalLockFile.Close(); err != nil {
logrus.Debugf("Releasing global reference count lock: %s", err)
return utils.ErrFlockRelease
}

environ := append(cdiEnviron, p11KitServerEnviron...)
if err := runCommandWithFallbacks(container,
Expand Down Expand Up @@ -601,12 +664,7 @@ func constructExecArgs(container, preserveFDs string,
return execArgs
}

func ensureContainerIsInitialized(container string, entryPointPID int, timestamp time.Time) error {
initializedStamp, err := utils.GetInitializedStamp(entryPointPID, currentUser)
if err != nil {
return err
}

func ensureContainerIsInitialized(container, initializedStamp string, timestamp time.Time) error {
logrus.Debugf("Checking if initialization stamp %s exists", initializedStamp)

shouldUsePolling := isUsePollingSet()
Expand Down
12 changes: 12 additions & 0 deletions src/pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ var (

ErrFlockCreate = errors.New("failed to create lock file")

ErrFlockRelease = errors.New("failed to release lock")

ErrImageWithoutBasename = errors.New("image does not have a basename")
)

Expand Down Expand Up @@ -498,6 +500,16 @@ func GetP11KitServerSocketLock(targetUser *user.User) (string, error) {
return p11KitServerSocketLock, nil
}

func GetReferenceCountGlobalLock(targetUser *user.User) (string, error) {
toolbxRuntimeDirectory, err := GetRuntimeDirectory(targetUser)
if err != nil {
return "", err
}

referenceCountGlobalLock := filepath.Join(toolbxRuntimeDirectory, "container-reference-count.lock")
return referenceCountGlobalLock, nil
}

func GetRuntimeDirectory(targetUser *user.User) (string, error) {
if runtimeDirectories == nil {
runtimeDirectories = make(map[string]string)
Expand Down