Repository: envoyproxy/gateway

Issue:
docs: Add EOL Banner for EOL'd versions
*Description*:
>Describe the issue.

To avoid issues like https://github.com/envoyproxy/gateway/issues/5499

[optional *Relevant Links*:]
>Any extra documentation required to understand the issue.


Discussion:
@zirain: Prefer to remove them entirely.@arkodg: any good prior art we can reference ?@zirain: 1. you can remove the menu
2. add some hint in the menu
3. add tips on the page
4. remove the source codes

Solution patch:
diff --git a/README.md b/README.md
index 66026d003..6401dcca6 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@ Kubernetes-based application gateway.
 * [Goals](GOALS.md)
 * [Quickstart](https://gateway.envoyproxy.io/latest/tasks/quickstart/) to use Envoy Gateway in a few simple steps.
 * [Roadmap](https://gateway.envoyproxy.io/contributions/roadmap/)
+* [Compatibility Matrix](https://gateway.envoyproxy.io/news/releases/matrix/)
 
 ## Contact
 
diff --git a/go.mod b/go.mod
index a0fc4bd67..0d79d1adb 100644
--- a/go.mod
+++ b/go.mod
@@ -46,7 +46,7 @@ require (
 	github.com/spf13/pflag v1.0.6
 	github.com/stretchr/testify v1.10.0
 	github.com/telepresenceio/watchable v0.0.0-20220726211108-9bb86f92afa7
-	github.com/tetratelabs/func-e v1.1.5-0.20240822223546-c85a098d5bf0
+	github.com/tetratelabs/func-e v1.1.5-0.20250319211008-ca8702e12788
 	github.com/tsaarni/certyaml v0.10.0
 	github.com/yuin/gopher-lua v1.1.1
 	go.opentelemetry.io/otel v1.35.0
diff --git a/go.sum b/go.sum
index 2bd4774d8..fb2bd2fe4 100644
--- a/go.sum
+++ b/go.sum
@@ -1226,8 +1226,8 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR
 github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
 github.com/tetafro/godot v1.4.20 h1:z/p8Ek55UdNvzt4TFn2zx2KscpW4rWqcnUrdmvWJj7E=
 github.com/tetafro/godot v1.4.20/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio=
-github.com/tetratelabs/func-e v1.1.5-0.20240822223546-c85a098d5bf0 h1:+OHaiOveLnsmUMSZT5vxL6rrpy5rcSsfnx9Mogfo1Kk=
-github.com/tetratelabs/func-e v1.1.5-0.20240822223546-c85a098d5bf0/go.mod h1:u78wX1mT5MiSZ3rw8+epQ7fcIT7m83YiwdPT2EWgb0Y=
+github.com/tetratelabs/func-e v1.1.5-0.20250319211008-ca8702e12788 h1:hgrf9mjrDENx+A5b5O03qqww7EknhRR0D9TQ0PX9LoU=
+github.com/tetratelabs/func-e v1.1.5-0.20250319211008-ca8702e12788/go.mod h1:FDAoUIMZc6Jkc0oEsbd2aTWRQhWDVrKhKy+yAdMfZbg=
 github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4=
 github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
diff --git a/internal/cmd/server.go b/internal/cmd/server.go
index 80808a420..437e6ed1e 100644
--- a/internal/cmd/server.go
+++ b/internal/cmd/server.go
@@ -31,6 +31,9 @@ import (
 type Runner interface {
 	Start(context.Context) error
 	Name() string
+	// Close closes the runner when the server is shutting down.
+	// This called after all the subscriptions are closed at the very end of the server shutdown.
+	Close() error
 }
 
 // cfgPath is the path to the EnvoyGateway configuration file.
@@ -248,6 +251,11 @@ func setupRunners(ctx context.Context, cfg *config.Server) (err error) {
 	}
 
 	cfg.Logger.Info("runners are shutting down")
+	for _, r := range runners {
+		if err := r.runner.Close(); err != nil {
+			cfg.Logger.Error(err, "failed to close runner", "name", r.runner.Name())
+		}
+	}
 
 	if extMgr != nil {
 		// Close connections to extension services
diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go
index c90c3ba96..c8ae91240 100644
--- a/internal/gatewayapi/runner/runner.go
+++ b/internal/gatewayapi/runner/runner.go
@@ -73,6 +73,10 @@ func New(cfg *Config) *Runner {
 	}
 }
 
+// Close implements Runner interface.
+func (r *Runner) Close() error { return nil }
+
+// Name implements Runner interface.
 func (r *Runner) Name() string {
 	return string(egv1a1.LogComponentGatewayAPIRunner)
 }
diff --git a/internal/globalratelimit/runner/runner.go b/internal/globalratelimit/runner/runner.go
index e34303734..14818d172 100644
--- a/internal/globalratelimit/runner/runner.go
+++ b/internal/globalratelimit/runner/runner.go
@@ -62,6 +62,10 @@ type Runner struct {
 	Config
 }
 
+// Close implements Runner interface.
+func (r *Runner) Close() error { return nil }
+
+// Name implements Runner interface.
 func (r *Runner) Name() string {
 	return string(egv1a1.LogComponentGlobalRateLimitRunner)
 }
diff --git a/internal/infrastructure/host/infra.go b/internal/infrastructure/host/infra.go
index 71804561f..71deb9d3a 100644
--- a/internal/infrastructure/host/infra.go
+++ b/internal/infrastructure/host/infra.go
@@ -73,26 +73,9 @@ func NewInfra(runnerCtx context.Context, cfg *config.Server, logger logging.Logg
 		proxyContextMap: make(map[string]*proxyContext),
 		sdsConfigPath:   defaultLocalCertPathDir,
 	}
-	go infra.cleanProxy(runnerCtx)
-
 	return infra, nil
 }
 
-// cleanProxy stops all the running proxies when infra provider is closing.
-func (i *Infra) cleanProxy(ctx context.Context) {
-	<-ctx.Done()
-	if len(i.proxyContextMap) < 1 {
-		return
-	}
-
-	i.Logger.Info("start cleaning up proxies")
-	for name, proxyCtx := range i.proxyContextMap {
-		proxyCtx.cancel()
-		i.Logger.Info("proxy closed", "name", name)
-	}
-	i.Logger.Info("all proxies has been cleaned up")
-}
-
 // createSdsConfig creates the needing SDS config under certain directory.
 func createSdsConfig(dir string) error {
 	if err := file.Write(common.GetSdsCAConfigMapData(
diff --git a/internal/infrastructure/host/proxy_infra.go b/internal/infrastructure/host/proxy_infra.go
index 6c3625358..0da0002f6 100644
--- a/internal/infrastructure/host/proxy_infra.go
+++ b/internal/infrastructure/host/proxy_infra.go
@@ -8,6 +8,8 @@ package host
 import (
 	"context"
 	"errors"
+	"io"
+	"os"
 	"path/filepath"
 
 	funcE "github.com/tetratelabs/func-e/api"
@@ -20,9 +22,20 @@ import (
 	"github.com/envoyproxy/gateway/internal/xds/bootstrap"
 )
 
+// proxyContext corresponds to the context of the Envoy process.
 type proxyContext struct {
-	ctx    context.Context
+	// cancel is the function to cancel the context passed to the Envoy process.
 	cancel context.CancelFunc
+	// exit will receive an item when the Envoy process completely stopped, via funcE.ExitChannel.
+	exit chan struct{}
+}
+
+// Close implements the Manager interface.
+func (i *Infra) Close() error {
+	for name := range i.proxyContextMap {
+		i.stopEnvoy(name)
+	}
+	return nil
 }
 
 // CreateOrUpdateProxyInfra creates the managed host process, if it doesn't exist.
@@ -65,25 +78,46 @@ func (i *Infra) CreateOrUpdateProxyInfra(ctx context.Context, infra *ir.Infra) e
 	if err != nil {
 		return err
 	}
+	i.runEnvoy(ctx, os.Stdout, proxyName, args)
+	return nil
+}
 
-	// Create a new context for up-running proxy.
-	pCtx, cancel := context.WithCancel(context.Background())
-	i.proxyContextMap[proxyName] = &proxyContext{ctx: pCtx, cancel: cancel}
-	return funcE.Run(pCtx, args, funcE.HomeDir(i.HomeDir))
+// runEnvoy runs the Envoy process with the given arguments and name in a separate goroutine.
+func (i *Infra) runEnvoy(ctx context.Context, out io.Writer, name string, args []string) {
+	pCtx, cancel := context.WithCancel(ctx)
+	exit := make(chan struct{}, 1)
+	i.proxyContextMap[name] = &proxyContext{cancel: cancel, exit: exit}
+	go func() {
+		// Run blocks until pCtx is done or the process exits where the latter doesn't happen when
+		// Envoy successfully starts up. So, this will not return until pCtx is done in practice.
+		defer func() {
+			exit <- struct{}{}
+		}()
+		err := funcE.Run(pCtx, args, funcE.HomeDir(i.HomeDir), funcE.Out(out))
+		if err != nil {
+			i.Logger.Error(err, "failed to run envoy")
+		}
+	}()
 }
 
 // DeleteProxyInfra removes the managed host process, if it doesn't exist.
-func (i *Infra) DeleteProxyInfra(ctx context.Context, infra *ir.Infra) error {
+func (i *Infra) DeleteProxyInfra(_ context.Context, infra *ir.Infra) error {
 	if infra == nil {
 		return errors.New("infra ir is nil")
 	}
 
 	proxyInfra := infra.GetProxyInfra()
 	proxyName := utils.GetHashedName(proxyInfra.Name, 64)
+	i.stopEnvoy(proxyName)
+	return nil
+}
+
+// stopEnvoy stops the Envoy process by its name. It will block until the process completely stopped.
+func (i *Infra) stopEnvoy(proxyName string) {
 	if pCtx, ok := i.proxyContextMap[proxyName]; ok {
-		pCtx.cancel()
+		pCtx.cancel()    // Cancel causes the Envoy process to exit.
+		<-pCtx.exit      // Wait for the Envoy process to completely exit.
+		close(pCtx.exit) // Close the channel to avoid leaking.
+		delete(i.proxyContextMap, proxyName)
 	}
-
-	// Return directly if the proxy is already stopped.
-	return nil
 }
diff --git a/internal/infrastructure/kubernetes/infra.go b/internal/infrastructure/kubernetes/infra.go
index 4285f3959..a0615a4e8 100644
--- a/internal/infrastructure/kubernetes/infra.go
+++ b/internal/infrastructure/kubernetes/infra.go
@@ -70,6 +70,9 @@ func NewInfra(cli client.Client, cfg *config.Server) *Infra {
 	}
 }
 
+// Close implements Manager interface.
+func (i *Infra) Close() error { return nil }
+
 // createOrUpdate creates a ServiceAccount/ConfigMap/Deployment/Service in the kube api server based on the
 // provided ResourceRender, if it doesn't exist and updates it if it does.
 func (i *Infra) createOrUpdate(ctx context.Context, r ResourceRender) error {
diff --git a/internal/infrastructure/manager.go b/internal/infrastructure/manager.go
index 0254eb185..3e1a0d811 100644
--- a/internal/infrastructure/manager.go
+++ b/internal/infrastructure/manager.go
@@ -28,6 +28,8 @@ var (
 
 // Manager provides the scaffolding for managing infrastructure.
 type Manager interface {
+	// Close is called when Envoy Gateway is shutting down, it can be used to block until all resources are cleaned up.
+	Close() error
 	// CreateOrUpdateProxyInfra creates or updates infra.
 	CreateOrUpdateProxyInfra(ctx context.Context, infra *ir.Infra) error
 	// DeleteProxyInfra deletes infra.
diff --git a/internal/infrastructure/runner/runner.go b/internal/infrastructure/runner/runner.go
index 72beff9c2..4de124caf 100644
--- a/internal/infrastructure/runner/runner.go
+++ b/internal/infrastructure/runner/runner.go
@@ -28,6 +28,12 @@ type Runner struct {
 	mgr infrastructure.Manager
 }
 
+// Close implements Runner interface.
+func (r *Runner) Close() error {
+	return r.mgr.Close()
+}
+
+// Name implements Runner interface.
 func (r *Runner) Name() string {
 	return string(egv1a1.LogComponentInfrastructureRunner)
 }
diff --git a/internal/provider/runner/runner.go b/internal/provider/runner/runner.go
index 1056e193a..673ea867d 100644
--- a/internal/provider/runner/runner.go
+++ b/internal/provider/runner/runner.go
@@ -32,11 +32,15 @@ func New(cfg *Config) *Runner {
 	return &Runner{Config: *cfg}
 }
 
+// Close implements Runner interface.
+func (r *Runner) Close() error { return nil }
+
+// Name implements Runner interface.
 func (r *Runner) Name() string {
 	return string(egv1a1.LogComponentProviderRunner)
 }
 
-// Start the provider runner
+// Start implements Runner interface.
 func (r *Runner) Start(ctx context.Context) (err error) {
 	r.Logger = r.Logger.WithName(r.Name()).WithValues("runner", r.Name())
 
diff --git a/internal/xds/server/runner/runner.go b/internal/xds/server/runner/runner.go
index d73cb7ccd..71d2e7ba5 100644
--- a/internal/xds/server/runner/runner.go
+++ b/internal/xds/server/runner/runner.go
@@ -76,6 +76,9 @@ func (r *Runner) Name() string {
 	return string(egv1a1.LogComponentXdsServerRunner)
 }
 
+// Close implements Runner interface.
+func (r *Runner) Close() error { return nil }
+
 // Start starts the xds-server runner
 func (r *Runner) Start(ctx context.Context) (err error) {
 	r.Logger = r.Logger.WithName(r.Name()).WithValues("runner", r.Name())
diff --git a/internal/xds/translator/runner/runner.go b/internal/xds/translator/runner/runner.go
index faa4a65ec..3be972c52 100644
--- a/internal/xds/translator/runner/runner.go
+++ b/internal/xds/translator/runner/runner.go
@@ -37,6 +37,10 @@ func New(cfg *Config) *Runner {
 	return &Runner{Config: *cfg}
 }
 
+// Close implements Runner interface.
+func (r *Runner) Close() error { return nil }
+
+// Name implements Runner interface.
 func (r *Runner) Name() string {
 	return string(egv1a1.LogComponentXdsTranslatorRunner)
 }
diff --git a/site/content/en/contributions/RELEASING.md b/site/content/en/contributions/RELEASING.md
index 7f18ab47b..bdd6b4668 100644
--- a/site/content/en/contributions/RELEASING.md
+++ b/site/content/en/contributions/RELEASING.md
@@ -9,7 +9,10 @@ This document guides maintainers through the process of creating an Envoy Gatewa
   - [Prerequisites](#prerequisites)
 - [Minor Release](#minor-release)
   - [Prerequisites](#prerequisites-1)
-- [Announce the Release](#announce-the-release)
+  - [Announce the Release](#announce-the-release)
+- [Patch Release](#patch-release)
+  - [Prerequisites](#prerequisites-2)
+  - [Announce the Release](#announce-the-release-1)
 
 ## Release Candidate
 
@@ -109,7 +112,7 @@ export GITHUB_REMOTE=origin
    1. Generate the versioned release docs:
 
       ``` shell
-         make docs-release TAG=v${MAJOR_VERSION}.${MINOR_VERSION}.0
+      make docs-release TAG=v${MAJOR_VERSION}.${MINOR_VERSION}.0
       ```
 
    1. Update `site/layouts/shortcodes/helm-version.html`, add the latest version of the minor release, and update the short code for `{{- with (strings.HasPrefix $pagePrefix "doc") -}}` to the latest minor version.
@@ -148,12 +151,13 @@ export GITHUB_REMOTE=origin
       {{- end -}}
       ```
       
-   1. Update `site/layouts/partials/page-meta-links.html`, change the version to current major version.
+   1. Update `site/hugo.toml`, change the version to current major version.
 
       ```console
-      {{ with (strings.HasPrefix $path "docs") -}}
-      {{ $path = replaceRE "docs" "v1.3" $path -}}
-      {{ end -}}
+      # The version number for the version of the docs represented in this doc set.
+      # Used in the "version-banner" partial to display a version number for the
+      # current doc set.
+      version = "v1.3"
       ```  
 
 3. Sign, commit, and push your changes to your fork.
diff --git a/site/data/adopters.yaml b/site/data/adopters.yaml
index e47a7cdb8..d1a3c56e8 100644
--- a/site/data/adopters.yaml
+++ b/site/data/adopters.yaml
@@ -51,3 +51,8 @@ adopters:
     logo: "/logos/docker.png"
     url: "https://www.docker.com/"
     description: "Docker is using Envoy Gateway to route all internal traffic for Docker Hub."
+  - name: "SenseTime"
+    logo: "/logos/sensetime.png"
+    url: "https://www.sensetime.com/"
+    description: "SenseTime is using Envoy Gateway to route most traffic for SenseCore."
+
diff --git a/site/hugo.toml b/site/hugo.toml
index 5279041bf..70547e11a 100644
--- a/site/hugo.toml
+++ b/site/hugo.toml
@@ -107,11 +107,11 @@ archived_version = false
 # The version number for the version of the docs represented in this doc set.
 # Used in the "version-banner" partial to display a version number for the
 # current doc set.
-version = "latest"
+version = "v1.3"
 
 # A link to latest version of the docs. Used in the "version-banner" partial to
 # point people to the main doc site.
-url_latest_version = "https://gateway.envoyproxy.io"
+url_latest_version = "/docs"
 
 # Repository configuration (URLs for in-page links to opening issues and suggesting changes)
 github_repo = "https://github.com/envoyproxy/gateway"
@@ -234,7 +234,7 @@ enable = true
 
 [[params.versions]]
   version = "v1.2"
-  url = "/docs"
+  url = "/v1.2"
 
 [[params.versions]]
   version = "v1.1"
diff --git a/site/layouts/partials/navbar-version-selector.html b/site/layouts/partials/navbar-version-selector.html
index fa547de96..e5907f386 100644
--- a/site/layouts/partials/navbar-version-selector.html
+++ b/site/layouts/partials/navbar-version-selector.html
@@ -1,4 +1,5 @@
 {{ $baseURL := .Site.BaseURL }}
+{{ $current_version := .Site.Params.version }}
 <div class="dropdown">
     <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
         {{- .Site.Params.version_menu -}}
@@ -9,10 +10,14 @@
         {{ $path = .Page.RelPermalink -}}
         {{ end -}}
         {{ range .Site.Params.versions -}}
+        {{ $url := .url }}
+        {{ if eq .version $current_version }}
+        {{ $url = "/docs" }}
+        {{ end -}}
         {{ if ne $baseURL "/" }}
-        <li><a class="dropdown-item" href="{{ $baseURL }}{{ .url }}{{ $path }}">{{ .version }}</a></li>
+        <li><a class="dropdown-item" href="{{ $baseURL }}{{ $url }}{{ $path }}">{{ .version }}</a></li>
         {{ else }}
-        <li><a class="dropdown-item" href="{{ .url }}{{ $path }}">{{ .version }}</a></li>
+        <li><a class="dropdown-item" href="{{ $url }}{{ $path }}">{{ .version }}</a></li>
         {{ end }}
         {{ end -}}
     </ul>
diff --git a/site/layouts/partials/page-meta-links.html b/site/layouts/partials/page-meta-links.html
index 3c3450c8d..b09dff7ec 100644
--- a/site/layouts/partials/page-meta-links.html
+++ b/site/layouts/partials/page-meta-links.html
@@ -1,6 +1,8 @@
 {{/* cSpell:ignore querify subdir */ -}}
 {{/* Class names ending with `--KIND` are deprecated in favor of `__KIND`, but we're keeping them for a few releases after 0.9.0 */ -}}
 
+{{ $current_version := .Site.Params.version }}
+
 {{ if .File -}}
 {{ $path := strings.TrimPrefix (add hugo.WorkingDir "/") $.File.Filename -}}
 {{ $gh_repo := $.Param "github_repo" -}}
@@ -26,7 +28,7 @@
   {{ end -}}
   {{/* Adjust the alias path for current version */ -}}
   {{ with (strings.Contains $path "docs") -}}
-    {{ $path = replaceRE "docs" "v1.3" $path -}}
+    {{ $path = replaceRE "docs" $current_version $path -}}
   {{ end -}}
 
   {{ $gh_repo_path := printf "%s/%s/%s" $gh_branch $gh_subdir $path -}}
diff --git a/site/layouts/partials/version-banner.html b/site/layouts/partials/version-banner.html
new file mode 100644
index 000000000..e6a6c56b0
--- /dev/null
+++ b/site/layouts/partials/version-banner.html
@@ -0,0 +1,15 @@
+{{ $color := "primary" }}
+{{ $latest_version := .Site.Params.url_latest_version }}
+{{ $path := strings.TrimPrefix (add hugo.WorkingDir "/") $.File.Filename -}}
+{{ $current_version := .Site.Params.version }}
+{{ if not (strings.Contains $path "docs") }}
+{{ if not (strings.Contains $path "latest") }}
+{{ if not (strings.Contains $path $current_version) }}
+<div class="alert alert-warning">
+    Notice: You're viewing documentation for an older version of Envoy Gateway that has reached End of Life (EOL).
+    <br />
+    Please refer to the <a href="{{ $latest_version | safeURL }}" target="_blank">/docs</a> documentation for the most current information.
+</div>
+{{ end }}
+{{ end }}
+{{ end }}
diff --git a/site/static/logos/sensetime.png b/site/static/logos/sensetime.png
new file mode 100644
index 000000000..d8fb771ad
Binary files /dev/null and b/site/static/logos/sensetime.png differ


Test patch:
diff --git a/internal/infrastructure/host/proxy_infra_test.go b/internal/infrastructure/host/proxy_infra_test.go
index 3437fe5a5..de3651da4 100644
--- a/internal/infrastructure/host/proxy_infra_test.go
+++ b/internal/infrastructure/host/proxy_infra_test.go
@@ -6,11 +6,13 @@
 package host
 
 import (
+	"bytes"
 	"context"
 	"path"
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	funcE "github.com/tetratelabs/func-e/api"
 
 	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 	"github.com/envoyproxy/gateway/internal/crypto"
@@ -20,7 +22,7 @@ import (
 	"github.com/envoyproxy/gateway/internal/utils/file"
 )
 
-func newMockInfra(t *testing.T, tCtx context.Context, cfg *config.Server, cleanProxy bool) *Infra {
+func newMockInfra(t *testing.T, cfg *config.Server) *Infra {
 	t.Helper()
 	homeDir := t.TempDir()
 	// Create envoy certs under home dir.
@@ -45,16 +47,13 @@ func newMockInfra(t *testing.T, tCtx context.Context, cfg *config.Server, cleanP
 		proxyContextMap: make(map[string]*proxyContext),
 		sdsConfigPath:   proxyDir,
 	}
-	if cleanProxy {
-		go infra.cleanProxy(tCtx)
-	}
 	return infra
 }
 
 func TestInfraCreateProxy(t *testing.T) {
 	cfg, err := config.New()
 	require.NoError(t, err)
-	infra := newMockInfra(t, context.Background(), cfg, true)
+	infra := newMockInfra(t, cfg)
 
 	// TODO: add more tests once it supports configurable homeDir and runDir.
 	testCases := []struct {
@@ -87,3 +86,27 @@ func TestInfraCreateProxy(t *testing.T) {
 		})
 	}
 }
+
+func TestInfra_runEnvoy_stopEnvoy(t *testing.T) {
+	tmpdir := t.TempDir()
+	// Ensures that all the required binaries are available.
+	err := funcE.Run(context.Background(), []string{"--version"}, funcE.HomeDir(tmpdir))
+	require.NoError(t, err)
+
+	i := &Infra{proxyContextMap: make(map[string]*proxyContext), HomeDir: tmpdir}
+	// Ensures that run -> stop will successfully stop the envoy and we can
+	// run it again without any issues.
+	for range 5 {
+		args := []string{
+			"--config-yaml",
+			"admin: {address: {socket_address: {address: '127.0.0.1', port_value: 9901}}}",
+		}
+		out := &bytes.Buffer{}
+		i.runEnvoy(context.Background(), out, "test", args)
+		require.Len(t, i.proxyContextMap, 1)
+		i.stopEnvoy("test")
+		require.Empty(t, i.proxyContextMap)
+		// If the cleanup didn't work, the error due to "address already in use" will be tried to be written to the nil logger,
+		// which will panic.
+	}
+}