Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package containerd for openSUSE:Factory 
checked in at 2024-09-12 16:54:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/containerd (Old)
 and      /work/SRC/openSUSE:Factory/.containerd.new.17570 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "containerd"

Thu Sep 12 16:54:01 2024 rev:70 rq:1200080 version:1.7.22

Changes:
--------
--- /work/SRC/openSUSE:Factory/containerd/containerd.changes    2024-09-06 
17:18:21.839440126 +0200
+++ /work/SRC/openSUSE:Factory/.containerd.new.17570/containerd.changes 
2024-09-12 16:54:06.291231009 +0200
@@ -1,0 +2,9 @@
+Wed Sep 11 08:58:49 UTC 2024 - Aleksa Sarai <[email protected]>
+
+- Update to containerd v1.7.22. Upstream release notes:
+  <https://github.com/containerd/containerd/releases/tag/v1.7.22>
+- Bump minimum Go version to 1.22.
+- Rebase patches:
+  * 0001-BUILD-SLE12-revert-btrfs-depend-on-kernel-UAPI-inste.patch
+
+-------------------------------------------------------------------

Old:
----
  containerd-1.7.21_472731909fa3.tar.xz

New:
----
  containerd-1.7.22_7f7fdf5fed64.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ containerd.spec ++++++
--- /var/tmp/diff_new_pack.g1IeYw/_old  2024-09-12 16:54:07.919298843 +0200
+++ /var/tmp/diff_new_pack.g1IeYw/_new  2024-09-12 16:54:07.931299342 +0200
@@ -23,14 +23,14 @@
 %endif
 
 # MANUAL: Update the git_version.
-%define git_version 472731909fa34bd7bc9c087e4c27943f9835f111
-%define git_short   472731909fa3
+%define git_version 7f7fdf5fed64eb6a7caf99b3e12efcf9d60e311c
+%define git_short   7f7fdf5fed64
 
 %global provider_prefix github.com/containerd/containerd
 %global import_path %{provider_prefix}
 
 Name:           containerd
-Version:        1.7.21
+Version:        1.7.22
 Release:        0
 Summary:        Standalone OCI Container Daemon
 License:        Apache-2.0
@@ -43,7 +43,7 @@
 Patch1:         0001-BUILD-SLE12-revert-btrfs-depend-on-kernel-UAPI-inste.patch
 BuildRequires:  fdupes
 BuildRequires:  glibc-devel-static
-BuildRequires:  go >= 1.19
+BuildRequires:  go >= 1.22
 BuildRequires:  go-go-md2man
 BuildRequires:  golang-packaging
 BuildRequires:  libbtrfs-devel >= 3.8

++++++ 0001-BUILD-SLE12-revert-btrfs-depend-on-kernel-UAPI-inste.patch ++++++
--- /var/tmp/diff_new_pack.g1IeYw/_old  2024-09-12 16:54:08.159308842 +0200
+++ /var/tmp/diff_new_pack.g1IeYw/_new  2024-09-12 16:54:08.191310176 +0200
@@ -1,4 +1,4 @@
-From 876958fd21984b77e0a70d6ac5763e757ca976ef Mon Sep 17 00:00:00 2001
+From 0d0424334da85c9192a29a4e108094f7c28bbab5 Mon Sep 17 00:00:00 2001
 From: Aleksa Sarai <[email protected]>
 Date: Wed, 22 May 2024 12:58:32 -0700
 Subject: [PATCH] BUILD: SLE12: revert "btrfs: depend on kernel UAPI instead of

++++++ _service ++++++
--- /var/tmp/diff_new_pack.g1IeYw/_old  2024-09-12 16:54:08.371317676 +0200
+++ /var/tmp/diff_new_pack.g1IeYw/_new  2024-09-12 16:54:08.407319176 +0200
@@ -3,8 +3,8 @@
     <param name="url">https://github.com/containerd/containerd.git</param>
     <param name="scm">git</param>
     <param name="filename">containerd</param>
-    <param name="versionformat">1.7.21_%h</param>
-    <param name="revision">v1.7.21</param>
+    <param name="versionformat">1.7.22_%h</param>
+    <param name="revision">v1.7.22</param>
     <param name="exclude">.git</param>
   </service>
   <service name="recompress" mode="manual">

++++++ containerd-1.7.21_472731909fa3.tar.xz -> 
containerd-1.7.22_7f7fdf5fed64.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/.github/actions/install-go/action.yml 
new/containerd-1.7.22_7f7fdf5fed64/.github/actions/install-go/action.yml
--- old/containerd-1.7.21_472731909fa3/.github/actions/install-go/action.yml    
2024-08-27 00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/.github/actions/install-go/action.yml    
2024-09-09 22:09:31.000000000 +0200
@@ -3,7 +3,7 @@
 inputs:
   go-version:
     required: true
-    default: "1.22.6"
+    default: "1.22.7"
     description: "Go version to install"
 
 runs:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/.github/workflows/api-release.yml 
new/containerd-1.7.22_7f7fdf5fed64/.github/workflows/api-release.yml
--- old/containerd-1.7.21_472731909fa3/.github/workflows/api-release.yml        
2024-08-27 00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/.github/workflows/api-release.yml        
2024-09-09 22:09:31.000000000 +0200
@@ -6,7 +6,7 @@
 name: API Release
 
 env:
-  GO_VERSION: "1.22.6"
+  GO_VERSION: "1.22.7"
 
 permissions: # added using https://github.com/step-security/secure-workflows
   contents: read
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/.github/workflows/ci.yml 
new/containerd-1.7.22_7f7fdf5fed64/.github/workflows/ci.yml
--- old/containerd-1.7.21_472731909fa3/.github/workflows/ci.yml 2024-08-27 
00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/.github/workflows/ci.yml 2024-09-09 
22:09:31.000000000 +0200
@@ -192,7 +192,7 @@
     strategy:
       matrix:
         os: [ubuntu-20.04, actuated-arm64-4cpu-16gb, macos-12, windows-2019, 
windows-2022]
-        go-version: ["1.22.6", "1.23.0"]
+        go-version: ["1.22.7", "1.23.1"]
     steps:
       - uses: actions/checkout@v4
       - uses: ./.github/actions/install-go
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/.github/workflows/release.yml 
new/containerd-1.7.22_7f7fdf5fed64/.github/workflows/release.yml
--- old/containerd-1.7.21_472731909fa3/.github/workflows/release.yml    
2024-08-27 00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/.github/workflows/release.yml    
2024-09-09 22:09:31.000000000 +0200
@@ -13,7 +13,7 @@
 name: Release
 
 env:
-  GO_VERSION: "1.22.6"
+  GO_VERSION: "1.22.7"
 
 permissions: # added using https://github.com/step-security/secure-workflows
   contents: read
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/containerd-1.7.21_472731909fa3/Vagrantfile 
new/containerd-1.7.22_7f7fdf5fed64/Vagrantfile
--- old/containerd-1.7.21_472731909fa3/Vagrantfile      2024-08-27 
00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/Vagrantfile      2024-09-09 
22:09:31.000000000 +0200
@@ -104,7 +104,7 @@
   config.vm.provision "install-golang", type: "shell", run: "once" do |sh|
     sh.upload_path = "/tmp/vagrant-install-golang"
     sh.env = {
-        'GO_VERSION': ENV['GO_VERSION'] || "1.22.6",
+        'GO_VERSION': ENV['GO_VERSION'] || "1.22.7",
     }
     sh.inline = <<~SHELL
         #!/usr/bin/env bash
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/contrib/Dockerfile.test 
new/containerd-1.7.22_7f7fdf5fed64/contrib/Dockerfile.test
--- old/containerd-1.7.21_472731909fa3/contrib/Dockerfile.test  2024-08-27 
00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/contrib/Dockerfile.test  2024-09-09 
22:09:31.000000000 +0200
@@ -29,7 +29,7 @@
 #   docker run --privileged containerd-test
 # 
------------------------------------------------------------------------------
 
-ARG GOLANG_VERSION=1.22.6
+ARG GOLANG_VERSION=1.22.7
 ARG GOLANG_IMAGE=golang
 
 FROM ${GOLANG_IMAGE}:${GOLANG_VERSION} AS golang
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/contrib/fuzz/oss_fuzz_build.sh 
new/containerd-1.7.22_7f7fdf5fed64/contrib/fuzz/oss_fuzz_build.sh
--- old/containerd-1.7.21_472731909fa3/contrib/fuzz/oss_fuzz_build.sh   
2024-08-27 00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/contrib/fuzz/oss_fuzz_build.sh   
2024-09-09 22:09:31.000000000 +0200
@@ -43,11 +43,11 @@
 
 apt-get update && apt-get install -y wget
 cd $SRC
-wget --quiet https://go.dev/dl/go1.22.6.linux-amd64.tar.gz
+wget --quiet https://go.dev/dl/go1.22.7.linux-amd64.tar.gz
 
 mkdir temp-go
 rm -rf /root/.go/*
-tar -C temp-go/ -xzf go1.22.6.linux-amd64.tar.gz
+tar -C temp-go/ -xzf go1.22.7.linux-amd64.tar.gz
 mv temp-go/go/* /root/.go/
 cd $SRC/containerd
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/integration/client/container_linux_test.go 
new/containerd-1.7.22_7f7fdf5fed64/integration/client/container_linux_test.go
--- 
old/containerd-1.7.21_472731909fa3/integration/client/container_linux_test.go   
    2024-08-27 00:04:19.000000000 +0200
+++ 
new/containerd-1.7.22_7f7fdf5fed64/integration/client/container_linux_test.go   
    2024-09-09 22:09:31.000000000 +0200
@@ -37,7 +37,9 @@
        . "github.com/containerd/containerd"
        "github.com/containerd/containerd/cio"
        "github.com/containerd/containerd/containers"
+       "github.com/containerd/containerd/integration/failpoint"
        "github.com/containerd/containerd/oci"
+       "github.com/containerd/containerd/pkg/fifosync"
        "github.com/containerd/containerd/plugin"
        "github.com/containerd/containerd/runtime/linux/runctypes"
        "github.com/containerd/containerd/runtime/v2/runc/options"
@@ -45,6 +47,7 @@
        "github.com/containerd/errdefs"
 
        "github.com/opencontainers/runtime-spec/specs-go"
+       "github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/require"
        "golang.org/x/sys/unix"
 )
@@ -1595,3 +1598,210 @@
                })
        }
 }
+
+// TestIssue10589 is used as regression case for issue 10589.
+//
+// This issue was caused by a race between init exits and new exec process 
tracking inside the shim.  The test operates
+// by controlling the time between when the shim invokes "runc exec" and when 
the actual "runc exec" is triggered.  This
+// allows validating that races for shim state tracking between pre- and 
post-start of the exec process do not exist.
+//
+// The workflow is as follows:
+// 1. Create a container as normal
+// 2. Make an exec1 using runc-fp with delayexec
+// 3. Wait until the exec is waiting to start (triggered by delayexec)
+// 4. Kill the container init process (signalling it is easiest)
+// 5. Make an exec2 using runc-fp with delayexec
+// 6. Wait until the exec is waiting to start
+// 7. Allow exec1 to proceed
+// 8. Allow exec2 to proceed
+// 9. See that the container has exited and all execs have exited too
+//
+// https://github.com/containerd/containerd/issues/10589
+func TestIssue10589(t *testing.T) {
+       if f := os.Getenv("RUNC_FLAVOR"); f != "" && f != "runc" {
+               t.Skip("test requires runc")
+       }
+       if rt := os.Getenv("TEST_RUNTIME"); rt != "" && rt != 
plugin.RuntimeRuncV2 {
+               t.Skip("test requires io.containerd.runc.v2")
+       }
+
+       client, err := newClient(t, address)
+       require.NoError(t, err)
+       t.Cleanup(func() {
+               client.Close()
+       })
+
+       var (
+               image       Image
+               ctx, cancel = testContext(t)
+               id          = t.Name()
+       )
+       t.Cleanup(cancel)
+
+       image, err = client.GetImage(ctx, testImage)
+       require.NoError(t, err)
+
+       // 1. Create a sleeping container
+       t.Log("1. Create a sleeping container")
+       container, err := client.NewContainer(ctx, id,
+               WithNewSnapshot(id, image),
+               WithNewSpec(oci.WithImageConfig(image),
+                       withProcessArgs("sleep", "inf"),
+                       oci.WithAnnotations(map[string]string{
+                               "oci.runc.failpoint.profile": "delayExec",
+                       }),
+               ),
+               WithRuntime(client.Runtime(), &options.Options{
+                       BinaryName: "runc-fp",
+               }),
+       )
+       require.NoError(t, err, "create container")
+       t.Cleanup(func() {
+               ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+               err := container.Delete(ctx, WithSnapshotCleanup)
+               if err != nil {
+                       t.Log("delete err", err)
+               }
+               cancel()
+       })
+
+       task, err := container.NewTask(ctx, empty())
+       require.NoError(t, err, "create task")
+       t.Cleanup(func() {
+               ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
+               st, err := task.Delete(ctx, WithProcessKill)
+               t.Log("exit status", st)
+               if err != nil {
+                       t.Log("kill err", err)
+               }
+               cancel()
+       })
+
+       err = task.Start(ctx)
+       require.NoError(t, err, "start container")
+
+       status, err := task.Status(ctx)
+       require.NoError(t, err, "container status")
+       require.Equal(t, Running, status.Status)
+
+       // 2. Create an exec
+       t.Log("2. Create exec1")
+       exec1ReadyFifo, err := fifosync.NewWaiter(filepath.Join(t.TempDir(), 
"exec1-ready.fifo"), 0600)
+       require.NoError(t, err, "create exec1 ready fifo")
+       exec1DelayFifo, err := fifosync.NewTrigger(filepath.Join(t.TempDir(), 
"exec1-delay.fifo"), 0600)
+       require.NoError(t, err, "create exec1 delay fifo")
+       exec1, err := task.Exec(ctx, "exec1", &specs.Process{
+               Args: []string{"/bin/sleep", "301"},
+               Cwd:  "/",
+               Env: []string{
+                       failpoint.DelayExecReadyEnv + "=" + 
exec1ReadyFifo.Name(),
+                       failpoint.DelayExecDelayEnv + "=" + 
exec1DelayFifo.Name(),
+               },
+       }, cio.NullIO)
+       require.NoError(t, err, "create exec1")
+
+       exec1done := make(chan struct{})
+       go func() {
+               defer close(exec1done)
+               t.Log("Starting exec1")
+               err := exec1.Start(ctx)
+               assert.Error(t, err, "start exec1")
+               t.Logf("error starting exec1: %s", err)
+       }()
+
+       // 3. Wait until the exec is waiting to start
+       t.Log("3. Wait until exec1 is waiting to start")
+       err = exec1ReadyFifo.Wait()
+       require.NoError(t, err, "open exec1 fifo")
+
+       // 4. Kill the container init process
+       t.Log("4. Kill the container init process")
+       target := task.Pid()
+       t.Logf("Killing main pid (%v) of container %s", target, container.ID())
+       syscall.Kill(int(target), syscall.SIGKILL)
+       status, err = task.Status(ctx)
+       require.NoError(t, err, "container status")
+       t.Log("container status", status.Status)
+
+       // 5. Make an exec (2) using this failpoint
+       t.Log("5. Create exec2")
+       exec2ReadyFifo, err := fifosync.NewWaiter(filepath.Join(t.TempDir(), 
"exec2-ready.fifo"), 0600)
+       require.NoError(t, err, "create exec2 ready fifo: %q", exec2ReadyFifo)
+       exec2DelayFifo, err := fifosync.NewTrigger(filepath.Join(t.TempDir(), 
"exec2-delay.fifo"), 0600)
+       require.NoError(t, err, "create exec2 delay fifo: %q", exec2DelayFifo)
+       exec2, err := task.Exec(ctx, "exec2", &specs.Process{
+               Args: []string{"/bin/sleep", "302"},
+               Cwd:  "/",
+               Env: []string{
+                       failpoint.DelayExecReadyEnv + "=" + 
exec2ReadyFifo.Name(),
+                       failpoint.DelayExecDelayEnv + "=" + 
exec2DelayFifo.Name(),
+               },
+       }, cio.NullIO)
+       require.NoError(t, err, "create exec2")
+
+       exec2done := make(chan struct{})
+       didExec2Run := true
+       go func() {
+               defer close(exec2done)
+               t.Log("Starting exec2")
+               err := exec2.Start(ctx)
+               assert.Error(t, err, "start exec2")
+               t.Logf("error starting exec2: %s", err)
+       }()
+
+       // 6. Wait until the exec is waiting to start
+       t.Log("6. Wait until exec2 is waiting to start")
+       exec2ready := make(chan struct{})
+       go func() {
+               exec2ReadyFifo.Wait()
+               close(exec2ready)
+       }()
+       select {
+       case <-exec2ready:
+       case <-exec2done:
+               didExec2Run = false
+       }
+
+       // 7. Allow exec=1 to proceed
+       t.Log("7. Allow exec=1 to proceed")
+       err = exec1DelayFifo.Trigger()
+       assert.NoError(t, err, "trigger exec1 fifo")
+       status, err = task.Status(ctx)
+       require.NoError(t, err, "container status")
+       t.Log("container status", status.Status)
+       <-exec1done
+       status, err = task.Status(ctx)
+       require.NoError(t, err, "container status")
+       t.Log("container status", status.Status)
+
+       // 8. Allow exec=2 to proceed
+       if didExec2Run {
+               t.Log("8. Allow exec2 to proceed")
+               err = exec2DelayFifo.Trigger()
+               assert.NoError(t, err, "trigger exec2 fifo")
+               status, err = task.Status(ctx)
+               require.NoError(t, err, "container status")
+               t.Log("container status", status.Status)
+               <-exec2done
+               status, err = task.Status(ctx)
+               require.NoError(t, err, "container status")
+               t.Log("container status", status.Status)
+       } else {
+               t.Log("8. Skip exec2")
+       }
+
+       // 9. Validate
+       t.Log("9. Validate")
+       status, err = exec1.Status(ctx)
+       require.NoError(t, err, "exec1 status")
+       t.Logf("exec1 status: %s", status.Status)
+       assert.Equal(t, Created, status.Status)
+       status, err = exec2.Status(ctx)
+       require.NoError(t, err, "exec2 status")
+       t.Logf("exec2 status: %s", status.Status)
+       assert.Equal(t, Created, status.Status)
+       status, err = task.Status(ctx)
+       t.Logf("task status: %s", status.Status)
+       require.NoError(t, err, "container status")
+       assert.Equal(t, Stopped, status.Status)
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/integration/failpoint/cmd/runc-fp/delayexec.go
 
new/containerd-1.7.22_7f7fdf5fed64/integration/failpoint/cmd/runc-fp/delayexec.go
--- 
old/containerd-1.7.21_472731909fa3/integration/failpoint/cmd/runc-fp/delayexec.go
   1970-01-01 01:00:00.000000000 +0100
+++ 
new/containerd-1.7.22_7f7fdf5fed64/integration/failpoint/cmd/runc-fp/delayexec.go
   2024-09-09 22:09:31.000000000 +0200
@@ -0,0 +1,131 @@
+//go:build linux
+
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package main
+
+import (
+       "context"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "io"
+       "os"
+       "strings"
+
+       "github.com/opencontainers/runtime-spec/specs-go"
+       "github.com/sirupsen/logrus"
+
+       "github.com/containerd/containerd/integration/failpoint"
+       "github.com/containerd/containerd/pkg/fifosync"
+)
+
+// delayExec delays an "exec" command until a trigger is received from the 
calling test program.  This can be used to
+// test races around container lifecycle and exec processes.
+func delayExec(ctx context.Context, method invoker) error {
+       isExec := strings.Contains(strings.Join(os.Args, ","), ",exec,")
+       if !isExec {
+               if err := method(ctx); err != nil {
+                       return err
+               }
+               return nil
+       }
+       logrus.Debug("EXEC!")
+
+       if err := delay(); err != nil {
+               return err
+       }
+       if err := method(ctx); err != nil {
+               return err
+       }
+       return nil
+}
+
+func delay() error {
+       ready, delay, err := fifoFromProcessEnv()
+       if err != nil {
+               return err
+       }
+       if err := ready.Trigger(); err != nil {
+               return err
+       }
+       return delay.Wait()
+}
+
+// fifoFromProcessEnv finds a fifo specified in the environment variables of 
an exec process
+func fifoFromProcessEnv() (fifosync.Trigger, fifosync.Waiter, error) {
+       env, err := processEnvironment()
+       if err != nil {
+               return nil, nil, err
+       }
+
+       readyName, ok := env[failpoint.DelayExecReadyEnv]
+       if !ok {
+               return nil, nil, fmt.Errorf("fifo: failed to find %q env var in 
%v", failpoint.DelayExecReadyEnv, env)
+       }
+       delayName, ok := env[failpoint.DelayExecDelayEnv]
+       if !ok {
+               return nil, nil, fmt.Errorf("fifo: failed to find %q env var in 
%v", failpoint.DelayExecDelayEnv, env)
+       }
+       logrus.WithField("ready", readyName).WithField("delay", 
delayName).Debug("Found FIFOs!")
+       readyFIFO, err := fifosync.NewTrigger(readyName, 0600)
+       if err != nil {
+               return nil, nil, err
+       }
+       delayFIFO, err := fifosync.NewWaiter(delayName, 0600)
+       if err != nil {
+               return nil, nil, err
+       }
+       return readyFIFO, delayFIFO, nil
+}
+
+func processEnvironment() (map[string]string, error) {
+       idx := 2
+       for ; idx < len(os.Args); idx++ {
+               if os.Args[idx] == "--process" {
+                       break
+               }
+       }
+
+       if idx >= len(os.Args)-1 || os.Args[idx] != "--process" {
+               return nil, errors.New("env: option --process required")
+       }
+
+       specFile := os.Args[idx+1]
+       f, err := os.OpenFile(specFile, os.O_RDONLY, 0o644)
+       if err != nil {
+               return nil, fmt.Errorf("env: failed to open %s: %w", specFile, 
err)
+       }
+
+       b, err := io.ReadAll(f)
+       if err != nil {
+               return nil, fmt.Errorf("env: failed to read spec from %q", 
specFile)
+       }
+       var spec specs.Process
+       if err := json.Unmarshal(b, &spec); err != nil {
+               return nil, fmt.Errorf("env: failed to unmarshal spec from %q: 
%w", specFile, err)
+       }
+
+       // XXX: env vars can be specified multiple times, but we only keep one
+       env := make(map[string]string)
+       for _, e := range spec.Env {
+               k, v, _ := strings.Cut(e, "=")
+               env[k] = v
+       }
+
+       return env, nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/integration/failpoint/cmd/runc-fp/main.go 
new/containerd-1.7.22_7f7fdf5fed64/integration/failpoint/cmd/runc-fp/main.go
--- 
old/containerd-1.7.21_472731909fa3/integration/failpoint/cmd/runc-fp/main.go    
    2024-08-27 00:04:19.000000000 +0200
+++ 
new/containerd-1.7.22_7f7fdf5fed64/integration/failpoint/cmd/runc-fp/main.go    
    2024-09-09 22:09:31.000000000 +0200
@@ -25,8 +25,9 @@
        "os/exec"
        "syscall"
 
-       "github.com/containerd/containerd/oci"
        "github.com/sirupsen/logrus"
+
+       "github.com/containerd/containerd/oci"
 )
 
 const (
@@ -40,6 +41,7 @@
 var (
        failpointProfiles = map[string]invokerInterceptor{
                "issue9103": issue9103KillInitAfterCreate,
+               "delayExec": delayExec,
        }
 )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/integration/failpoint/const.go 
new/containerd-1.7.22_7f7fdf5fed64/integration/failpoint/const.go
--- old/containerd-1.7.21_472731909fa3/integration/failpoint/const.go   
1970-01-01 01:00:00.000000000 +0100
+++ new/containerd-1.7.22_7f7fdf5fed64/integration/failpoint/const.go   
2024-09-09 22:09:31.000000000 +0200
@@ -0,0 +1,22 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package failpoint
+
+const (
+       DelayExecReadyEnv = "_RUNC_FP_DELAY_EXEC_READY"
+       DelayExecDelayEnv = "_RUNC_FP_DELAY_EXEC_DELAY"
+)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/pkg/cri/sbserver/container_stats_list.go 
new/containerd-1.7.22_7f7fdf5fed64/pkg/cri/sbserver/container_stats_list.go
--- old/containerd-1.7.21_472731909fa3/pkg/cri/sbserver/container_stats_list.go 
2024-08-27 00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/pkg/cri/sbserver/container_stats_list.go 
2024-09-09 22:09:31.000000000 +0200
@@ -192,6 +192,11 @@
                return 0, nil
        }
 
+       // can't go backwards, this value might come in as 0 if the container 
was just removed
+       if currentUsageCoreNanoSeconds < oldStats.UsageCoreNanoSeconds {
+               return 0, nil
+       }
+
        newUsageNanoCores := 
uint64(float64(currentUsageCoreNanoSeconds-oldStats.UsageCoreNanoSeconds) /
                float64(nanoSeconds) * float64(time.Second/time.Nanosecond))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/pkg/cri/sbserver/container_stats_list_test.go
 
new/containerd-1.7.22_7f7fdf5fed64/pkg/cri/sbserver/container_stats_list_test.go
--- 
old/containerd-1.7.21_472731909fa3/pkg/cri/sbserver/container_stats_list_test.go
    2024-08-27 00:04:19.000000000 +0200
+++ 
new/containerd-1.7.22_7f7fdf5fed64/pkg/cri/sbserver/container_stats_list_test.go
    2024-09-09 22:09:31.000000000 +0200
@@ -35,45 +35,56 @@
 func TestContainerMetricsCPUNanoCoreUsage(t *testing.T) {
        c := newTestCRIService()
        timestamp := time.Now()
-       secondAfterTimeStamp := timestamp.Add(time.Second)
-       ID := "ID"
+       tenSecondAftertimeStamp := timestamp.Add(time.Second * 10)
 
        for desc, test := range map[string]struct {
+               id                          string
+               desc                        string
                firstCPUValue               uint64
                secondCPUValue              uint64
                expectedNanoCoreUsageFirst  uint64
                expectedNanoCoreUsageSecond uint64
        }{
                "metrics": {
+                       id:                          "id1",
+                       desc:                        "metrics",
                        firstCPUValue:               50,
                        secondCPUValue:              500,
                        expectedNanoCoreUsageFirst:  0,
-                       expectedNanoCoreUsageSecond: 450,
+                       expectedNanoCoreUsageSecond: 45,
+               },
+               "no metrics in second CPU sample": {
+                       id:                          "id2",
+                       desc:                        "metrics",
+                       firstCPUValue:               234235,
+                       secondCPUValue:              0,
+                       expectedNanoCoreUsageFirst:  0,
+                       expectedNanoCoreUsageSecond: 0,
                },
        } {
                t.Run(desc, func(t *testing.T) {
                        container, err := containerstore.NewContainer(
-                               containerstore.Metadata{ID: ID},
+                               containerstore.Metadata{ID: test.id},
                        )
                        assert.NoError(t, err)
                        assert.Nil(t, container.Stats)
                        err = c.containerStore.Add(container)
                        assert.NoError(t, err)
 
-                       cpuUsage, err := c.getUsageNanoCores(ID, false, 
test.firstCPUValue, timestamp)
+                       cpuUsage, err := c.getUsageNanoCores(test.id, false, 
test.firstCPUValue, timestamp)
                        assert.NoError(t, err)
 
-                       container, err = c.containerStore.Get(ID)
+                       container, err = c.containerStore.Get(test.id)
                        assert.NoError(t, err)
                        assert.NotNil(t, container.Stats)
 
                        assert.Equal(t, test.expectedNanoCoreUsageFirst, 
cpuUsage)
 
-                       cpuUsage, err = c.getUsageNanoCores(ID, false, 
test.secondCPUValue, secondAfterTimeStamp)
+                       cpuUsage, err = c.getUsageNanoCores(test.id, false, 
test.secondCPUValue, tenSecondAftertimeStamp)
                        assert.NoError(t, err)
                        assert.Equal(t, test.expectedNanoCoreUsageSecond, 
cpuUsage)
 
-                       container, err = c.containerStore.Get(ID)
+                       container, err = c.containerStore.Get(test.id)
                        assert.NoError(t, err)
                        assert.NotNil(t, container.Stats)
                })
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/pkg/cri/server/container_stats_list.go 
new/containerd-1.7.22_7f7fdf5fed64/pkg/cri/server/container_stats_list.go
--- old/containerd-1.7.21_472731909fa3/pkg/cri/server/container_stats_list.go   
2024-08-27 00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/pkg/cri/server/container_stats_list.go   
2024-09-09 22:09:31.000000000 +0200
@@ -122,6 +122,11 @@
                return 0, nil
        }
 
+       // can't go backwards, this value might come in as 0 if the container 
was just removed
+       if currentUsageCoreNanoSeconds < oldStats.UsageCoreNanoSeconds {
+               return 0, nil
+       }
+
        newUsageNanoCores := 
uint64(float64(currentUsageCoreNanoSeconds-oldStats.UsageCoreNanoSeconds) /
                float64(nanoSeconds) * float64(time.Second/time.Nanosecond))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/pkg/cri/server/container_stats_list_test.go 
new/containerd-1.7.22_7f7fdf5fed64/pkg/cri/server/container_stats_list_test.go
--- 
old/containerd-1.7.21_472731909fa3/pkg/cri/server/container_stats_list_test.go  
    2024-08-27 00:04:19.000000000 +0200
+++ 
new/containerd-1.7.22_7f7fdf5fed64/pkg/cri/server/container_stats_list_test.go  
    2024-09-09 22:09:31.000000000 +0200
@@ -27,48 +27,58 @@
 func TestContainerMetricsCPUNanoCoreUsage(t *testing.T) {
        c := newTestCRIService()
        timestamp := time.Now()
-       secondAfterTimeStamp := timestamp.Add(time.Second)
-       ID := "ID"
+       tenSecondAftertimeStamp := timestamp.Add(time.Second * 10)
 
        for desc, test := range map[string]struct {
+               id                          string
+               desc                        string
                firstCPUValue               uint64
                secondCPUValue              uint64
                expectedNanoCoreUsageFirst  uint64
                expectedNanoCoreUsageSecond uint64
        }{
                "metrics": {
+                       id:                          "id1",
+                       desc:                        "metrics",
                        firstCPUValue:               50,
                        secondCPUValue:              500,
                        expectedNanoCoreUsageFirst:  0,
-                       expectedNanoCoreUsageSecond: 450,
+                       expectedNanoCoreUsageSecond: 45,
+               },
+               "no metrics in second CPU sample": {
+                       id:                          "id2",
+                       desc:                        "metrics",
+                       firstCPUValue:               234235,
+                       secondCPUValue:              0,
+                       expectedNanoCoreUsageFirst:  0,
+                       expectedNanoCoreUsageSecond: 0,
                },
        } {
                t.Run(desc, func(t *testing.T) {
                        container, err := containerstore.NewContainer(
-                               containerstore.Metadata{ID: ID},
+                               containerstore.Metadata{ID: test.id},
                        )
                        assert.NoError(t, err)
                        assert.Nil(t, container.Stats)
                        err = c.containerStore.Add(container)
                        assert.NoError(t, err)
 
-                       cpuUsage, err := c.getUsageNanoCores(ID, false, 
test.firstCPUValue, timestamp)
+                       cpuUsage, err := c.getUsageNanoCores(test.id, false, 
test.firstCPUValue, timestamp)
                        assert.NoError(t, err)
 
-                       container, err = c.containerStore.Get(ID)
+                       container, err = c.containerStore.Get(test.id)
                        assert.NoError(t, err)
                        assert.NotNil(t, container.Stats)
 
                        assert.Equal(t, test.expectedNanoCoreUsageFirst, 
cpuUsage)
 
-                       cpuUsage, err = c.getUsageNanoCores(ID, false, 
test.secondCPUValue, secondAfterTimeStamp)
+                       cpuUsage, err = c.getUsageNanoCores(test.id, false, 
test.secondCPUValue, tenSecondAftertimeStamp)
                        assert.NoError(t, err)
                        assert.Equal(t, test.expectedNanoCoreUsageSecond, 
cpuUsage)
 
-                       container, err = c.containerStore.Get(ID)
+                       container, err = c.containerStore.Get(test.id)
                        assert.NoError(t, err)
                        assert.NotNil(t, container.Stats)
                })
        }
-
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/pkg/fifosync/fifo_unix.go 
new/containerd-1.7.22_7f7fdf5fed64/pkg/fifosync/fifo_unix.go
--- old/containerd-1.7.21_472731909fa3/pkg/fifosync/fifo_unix.go        
1970-01-01 01:00:00.000000000 +0100
+++ new/containerd-1.7.22_7f7fdf5fed64/pkg/fifosync/fifo_unix.go        
2024-09-09 22:09:31.000000000 +0200
@@ -0,0 +1,125 @@
+//go:build unix
+
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+/*
+Package fifosync provides a pattern on Unix-like operating systems for 
synchronizing across processes using Unix FIFOs
+(named pipes).
+*/
+package fifosync
+
+import (
+       "errors"
+       "fmt"
+       "io"
+       "os"
+
+       "golang.org/x/sys/unix"
+)
+
+// Trigger is a FIFO which is used to signal another process to proceed.
+type Trigger interface {
+       // Name returns the name of the trigger
+       Name() string
+       // Trigger triggers another process to proceed.
+       Trigger() error
+}
+
+// Waiter is a FIFO which is used to wait for trigger provided by another 
process.
+type Waiter interface {
+       // Name returns the name of the waiter
+       Name() string
+       // Wait waits for a trigger from another process.
+       Wait() error
+}
+
+type fifo struct {
+       name string
+}
+
+// NewTrigger creates a new Trigger
+func NewTrigger(name string, mode uint32) (Trigger, error) {
+       return new(name, mode)
+}
+
+// NewWaiter creates a new Waiter
+func NewWaiter(name string, mode uint32) (Waiter, error) {
+       return new(name, mode)
+}
+
+// New creates a new FIFO if it does not already exist. Use AsTrigger or 
AsWaiter to convert the new FIFO to a Trigger
+// or Waiter.
+func new(name string, mode uint32) (*fifo, error) {
+       s, err := os.Stat(name)
+       exist := true
+       if err != nil {
+               if !errors.Is(err, os.ErrNotExist) {
+                       return nil, fmt.Errorf("fifo: failed to stat %q: %w", 
name, err)
+               }
+               exist = false
+       }
+       if s != nil && s.Mode()&os.ModeNamedPipe == 0 {
+               return nil, fmt.Errorf("fifo: not a named pipe: %q", name)
+       }
+       if !exist {
+               err = unix.Mkfifo(name, mode)
+               if err != nil && !errors.Is(err, unix.EEXIST) {
+                       return nil, fmt.Errorf("fifo: failed to create %q: %w", 
name, err)
+               }
+       }
+       return &fifo{
+               name: name,
+       }, nil
+}
+
+func (f *fifo) Name() string {
+       return f.name
+}
+
+// AsTrigger converts the FIFO to a Trigger.
+func (f *fifo) AsTrigger() Trigger {
+       return f
+}
+
+// Trigger triggers another process to proceed.
+func (f *fifo) Trigger() error {
+       file, err := os.OpenFile(f.name, os.O_RDONLY, 0)
+       if err != nil {
+               return fmt.Errorf("fifo: failed to open %s: %w", f.name, err)
+       }
+       defer file.Close()
+       _, err = io.ReadAll(file)
+       return err
+}
+
+// AsWaiter converts the FIFO to a Waiter.
+func (f *fifo) AsWaiter() Waiter {
+       return f
+}
+
+// Wait waits for a trigger from another process.
+func (f *fifo) Wait() error {
+       fd, err := unix.Open(f.name, unix.O_WRONLY, 0)
+       if err != nil {
+               return fmt.Errorf("fifo: failed to open %s: %w", f.name, err)
+       }
+       defer unix.Close(fd)
+       if _, err := unix.Write(fd, []byte("0")); err != nil {
+               return fmt.Errorf("failed to write to %d: %w", fd, err)
+       }
+       return nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/containerd-1.7.21_472731909fa3/releases/v1.7.22.toml 
new/containerd-1.7.22_7f7fdf5fed64/releases/v1.7.22.toml
--- old/containerd-1.7.21_472731909fa3/releases/v1.7.22.toml    1970-01-01 
01:00:00.000000000 +0100
+++ new/containerd-1.7.22_7f7fdf5fed64/releases/v1.7.22.toml    2024-09-09 
22:09:31.000000000 +0200
@@ -0,0 +1,27 @@
+# commit to be tagged for new release
+commit = "HEAD"
+
+# project_name is used to refer to the project in the notes
+project_name = "containerd"
+
+# github_repo is the github project, only github is currently supported
+github_repo = "containerd/containerd"
+
+# match_deps is a pattern to determine which dependencies should be included
+# as part of this release. The changelog will also include changes for these
+# dependencies based on the change in the dependency's version.
+match_deps = "^github.com/(containerd/[a-zA-Z0-9-]+)$"
+
+# previous release of this project for determining changes
+previous = "v1.7.21"
+
+# pre_release is whether to include a disclaimer about being a pre-release
+pre_release = false
+
+# preface is the description of the release which precedes the author list
+# and changelog. This description could include highlights as well as any
+# description of changes. Use markdown formatting.
+preface = """\
+The twenty-second patch release for containerd 1.7 contains various fixes
+and updates.
+"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/runtime/v2/runc/task/service.go 
new/containerd-1.7.22_7f7fdf5fed64/runtime/v2/runc/task/service.go
--- old/containerd-1.7.21_472731909fa3/runtime/v2/runc/task/service.go  
2024-08-27 00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/runtime/v2/runc/task/service.go  
2024-09-09 22:09:31.000000000 +0200
@@ -73,15 +73,17 @@
        }
        go ep.Run(ctx)
        s := &service{
-               context:         ctx,
-               events:          make(chan interface{}, 128),
-               ec:              reaper.Default.Subscribe(),
-               ep:              ep,
-               shutdown:        sd,
-               containers:      make(map[string]*runc.Container),
-               running:         make(map[int][]containerProcess),
-               pendingExecs:    make(map[*runc.Container]int),
-               exitSubscribers: make(map[*map[int][]runcC.Exit]struct{}),
+               context:              ctx,
+               events:               make(chan interface{}, 128),
+               ec:                   reaper.Default.Subscribe(),
+               ep:                   ep,
+               shutdown:             sd,
+               containers:           make(map[string]*runc.Container),
+               running:              make(map[int][]containerProcess),
+               runningExecs:         make(map[*runc.Container]int),
+               execCountSubscribers: make(map[*runc.Container]chan<- int),
+               containerInitExit:    make(map[*runc.Container]runcC.Exit),
+               exitSubscribers:      make(map[*map[int][]runcC.Exit]struct{}),
        }
        go s.processExits()
        runcC.Monitor = reaper.Default
@@ -116,7 +118,19 @@
 
        lifecycleMu  sync.Mutex
        running      map[int][]containerProcess // pid -> running process, 
guarded by lifecycleMu
-       pendingExecs map[*runc.Container]int    // container -> num pending 
execs, guarded by lifecycleMu
+       runningExecs map[*runc.Container]int    // container -> num running 
execs, guarded by lifecycleMu
+       // container -> subscription to exec exits/changes to 
s.runningExecs[container],
+       // guarded by lifecycleMu
+       execCountSubscribers map[*runc.Container]chan<- int
+       // container -> init exits, guarded by lifecycleMu
+       // Used to stash container init process exits, so that we can hold them
+       // until after we've made sure to publish all the container's exec 
exits.
+       // Also used to prevent starting new execs from being started if the
+       // container's init process (read: pid, not [process.Init]) has already 
been
+       // reaped by the shim.
+       // Note that this flag gets updated before the container's 
[process.Init.Status]
+       // is transitioned to "stopped".
+       containerInitExit map[*runc.Container]runcC.Exit
        // Subscriptions to exits for PIDs. Adding/deleting subscriptions and
        // dereferencing the subscription pointers must only be done while 
holding
        // lifecycleMu.
@@ -137,8 +151,7 @@
 //
 // The returned handleStarted closure records that the process has started so
 // that its exit can be handled efficiently. If the process has already exited,
-// it handles the exit immediately. In addition, if the process is an exec and
-// its container's init process has already exited, that exit is also 
processed.
+// it handles the exit immediately.
 // handleStarted should be called after the event announcing the start of the
 // process has been published. Note that s.lifecycleMu must not be held when
 // calling handleStarted.
@@ -173,44 +186,8 @@
                        pid = p.Pid()
                }
 
-               _, init := p.(*process.Init)
                s.lifecycleMu.Lock()
 
-               var initExits []runcC.Exit
-               var initCps []containerProcess
-               if !init {
-                       s.pendingExecs[c]--
-
-                       initPid := c.Pid()
-                       iExits, initExited := exits[initPid]
-                       if initExited && s.pendingExecs[c] == 0 {
-                               // c's init process has exited before 
handleStarted was called and
-                               // this is the last pending exec process start 
- we need to process
-                               // the exit for the init process after 
processing this exec, so:
-                               // - delete c from the s.pendingExecs map
-                               // - keep the exits for the init pid to process 
later (after we process
-                               // this exec's exits)
-                               // - get the necessary containerProcesses for 
the init process (that we
-                               // need to process the exits), and remove them 
from s.running (which we skipped
-                               // doing in processExits).
-                               delete(s.pendingExecs, c)
-                               initExits = iExits
-                               var skipped []containerProcess
-                               for _, initPidCp := range s.running[initPid] {
-                                       if initPidCp.Container == c {
-                                               initCps = append(initCps, 
initPidCp)
-                                       } else {
-                                               skipped = append(skipped, 
initPidCp)
-                                       }
-                               }
-                               if len(skipped) == 0 {
-                                       delete(s.running, initPid)
-                               } else {
-                                       s.running[initPid] = skipped
-                               }
-                       }
-               }
-
                ees, exited := exits[pid]
                delete(s.exitSubscribers, &exits)
                exits = nil
@@ -219,11 +196,6 @@
                        for _, ee := range ees {
                                s.handleProcessExit(ee, c, p)
                        }
-                       for _, eee := range initExits {
-                               for _, cp := range initCps {
-                                       s.handleProcessExit(eee, cp.Container, 
cp.Process)
-                               }
-                       }
                } else {
                        // Process start was successful, add to `s.running`.
                        s.running[pid] = append(s.running[pid], 
containerProcess{
@@ -304,7 +276,11 @@
        if r.ExecID == "" {
                cinit = container
        } else {
-               s.pendingExecs[container]++
+               if _, initExited := s.containerInitExit[container]; initExited {
+                       s.lifecycleMu.Unlock()
+                       return nil, 
errdefs.ToGRPCf(errdefs.ErrFailedPrecondition, "container %s init process is 
not running", container.ID)
+               }
+               s.runningExecs[container]++
        }
        handleStarted, cleanup := s.preStart(cinit)
        s.lifecycleMu.Unlock()
@@ -312,6 +288,17 @@
 
        p, err := container.Start(ctx, r)
        if err != nil {
+               // If we failed to even start the process, s.runningExecs
+               // won't get decremented in s.handleProcessExit. We still need
+               // to update it.
+               if r.ExecID != "" {
+                       s.lifecycleMu.Lock()
+                       s.runningExecs[container]--
+                       if ch, ok := s.execCountSubscribers[container]; ok {
+                               ch <- s.runningExecs[container]
+                       }
+                       s.lifecycleMu.Unlock()
+               }
                handleStarted(container, p)
                return nil, errdefs.ToGRPC(err)
        }
@@ -676,28 +663,23 @@
                // Handle the exit for a created/started process. If there's 
more than
                // one, assume they've all exited. One of them will be the 
correct
                // process.
-               var cps, skipped []containerProcess
+               var cps []containerProcess
                for _, cp := range s.running[e.Pid] {
                        _, init := cp.Process.(*process.Init)
-                       if init && s.pendingExecs[cp.Container] != 0 {
-                               // This exit relates to a container for which 
we have pending execs. In
-                               // order to ensure order between execs and the 
init process for a given
-                               // container, skip processing this exit here 
and let the `handleStarted`
-                               // closure for the pending exec publish it.
-                               skipped = append(skipped, cp)
-                       } else {
-                               cps = append(cps, cp)
+                       if init {
+                               s.containerInitExit[cp.Container] = e
                        }
+                       cps = append(cps, cp)
                }
-               if len(skipped) > 0 {
-                       s.running[e.Pid] = skipped
-               } else {
-                       delete(s.running, e.Pid)
-               }
+               delete(s.running, e.Pid)
                s.lifecycleMu.Unlock()
 
                for _, cp := range cps {
-                       s.handleProcessExit(e, cp.Container, cp.Process)
+                       if ip, ok := cp.Process.(*process.Init); ok {
+                               s.handleInitExit(e, cp.Container, ip)
+                       } else {
+                               s.handleProcessExit(e, cp.Container, cp.Process)
+                       }
                }
        }
 }
@@ -706,18 +688,60 @@
        s.events <- evt
 }
 
-// s.mu must be locked when calling handleProcessExit
-func (s *service) handleProcessExit(e runcC.Exit, c *runc.Container, p 
process.Process) {
-       if ip, ok := p.(*process.Init); ok {
-               // Ensure all children are killed
-               if runc.ShouldKillAllOnExit(s.context, c.Bundle) {
-                       if err := ip.KillAll(s.context); err != nil {
-                               logrus.WithError(err).WithField("id", ip.ID()).
-                                       Error("failed to kill init's children")
-                       }
+// handleInitExit processes container init process exits.
+// This is handled separately from non-init exits, because there
+// are some extra invariants we want to ensure in this case, namely:
+// - for a given container, the init process exit MUST be the last exit 
published
+// This is achieved by:
+// - killing all running container processes (if the container has a shared pid
+// namespace, otherwise all other processes have been reaped already).
+// - waiting for the container's running exec counter to reach 0.
+// - finally, publishing the init exit.
+func (s *service) handleInitExit(e runcC.Exit, c *runc.Container, p 
*process.Init) {
+       // kill all running container processes
+       if runc.ShouldKillAllOnExit(s.context, c.Bundle) {
+               if err := p.KillAll(s.context); err != nil {
+                       logrus.WithError(err).WithField("id", p.ID()).
+                               Error("failed to kill init's children")
                }
        }
 
+       s.lifecycleMu.Lock()
+       numRunningExecs := s.runningExecs[c]
+       if numRunningExecs == 0 {
+               delete(s.runningExecs, c)
+               s.lifecycleMu.Unlock()
+               s.handleProcessExit(e, c, p)
+               return
+       }
+
+       events := make(chan int, numRunningExecs)
+       s.execCountSubscribers[c] = events
+
+       s.lifecycleMu.Unlock()
+
+       go func() {
+               defer func() {
+                       s.lifecycleMu.Lock()
+                       defer s.lifecycleMu.Unlock()
+                       delete(s.execCountSubscribers, c)
+                       delete(s.runningExecs, c)
+               }()
+
+               // wait for running processes to exit
+               for {
+                       if runningExecs := <-events; runningExecs == 0 {
+                               break
+                       }
+               }
+
+               // all running processes have exited now, and no new
+               // ones can start, so we can publish the init exit
+               s.handleProcessExit(e, c, p)
+       }()
+}
+
+func (s *service) handleProcessExit(e runcC.Exit, c *runc.Container, p 
process.Process) {
        p.SetExited(e.Status)
        s.send(&eventstypes.TaskExit{
                ContainerID: c.ID,
@@ -726,6 +750,14 @@
                ExitStatus:  uint32(e.Status),
                ExitedAt:    protobuf.ToTimestamp(p.ExitedAt()),
        })
+       if _, init := p.(*process.Init); !init {
+               s.lifecycleMu.Lock()
+               s.runningExecs[c]--
+               if ch, ok := s.execCountSubscribers[c]; ok {
+                       ch <- s.runningExecs[c]
+               }
+               s.lifecycleMu.Unlock()
+       }
 }
 
 func (s *service) getContainerPids(ctx context.Context, container 
*runc.Container) ([]uint32, error) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/script/setup/prepare_env_windows.ps1 
new/containerd-1.7.22_7f7fdf5fed64/script/setup/prepare_env_windows.ps1
--- old/containerd-1.7.21_472731909fa3/script/setup/prepare_env_windows.ps1     
2024-08-27 00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/script/setup/prepare_env_windows.ps1     
2024-09-09 22:09:31.000000000 +0200
@@ -5,7 +5,7 @@
 # lived test environment.
 Set-MpPreference -DisableRealtimeMonitoring:$true
 
-$PACKAGES= @{ mingw = "10.2.0"; git = ""; golang = "1.22.6"; make = ""; nssm = 
"" }
+$PACKAGES= @{ mingw = "10.2.0"; git = ""; golang = "1.22.7"; make = ""; nssm = 
"" }
 
 Write-Host "Downloading chocolatey package"
 curl.exe -L "https://packages.chocolatey.org/chocolatey.0.10.15.nupkg"; -o 
'c:\choco.zip'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/containerd-1.7.21_472731909fa3/script/setup/runc-version 
new/containerd-1.7.22_7f7fdf5fed64/script/setup/runc-version
--- old/containerd-1.7.21_472731909fa3/script/setup/runc-version        
2024-08-27 00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/script/setup/runc-version        
2024-09-09 22:09:31.000000000 +0200
@@ -1 +1 @@
-v1.1.13
+v1.1.14
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/containerd-1.7.21_472731909fa3/version/version.go 
new/containerd-1.7.22_7f7fdf5fed64/version/version.go
--- old/containerd-1.7.21_472731909fa3/version/version.go       2024-08-27 
00:04:19.000000000 +0200
+++ new/containerd-1.7.22_7f7fdf5fed64/version/version.go       2024-09-09 
22:09:31.000000000 +0200
@@ -23,7 +23,7 @@
        Package = "github.com/containerd/containerd"
 
        // Version holds the complete version number. Filled in at linking time.
-       Version = "1.7.21+unknown"
+       Version = "1.7.22+unknown"
 
        // Revision is filled with the VCS (e.g. git) revision being used to 
build
        // the program at linking time.

Reply via email to