diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw
index d95f6a646..fccec5f14 100644
--- a/localization/strings/en-US/Resources.resw
+++ b/localization/strings/en-US/Resources.resw
@@ -2355,6 +2355,10 @@ For privacy information about this product please visit https://aka.ms/privacy.<
Unsupported network mode: '{}'
{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+ Invalid stop timeout value: {}
+ {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
Additional networks are not allowed when the primary network mode is 'host' or 'none'.
{Locked="host"}{Locked="none"}Command line arguments, file names and string inserts should not be translated
@@ -2920,6 +2924,9 @@ On first run, creates the file with all settings commented out at their defaults
Signal to stop the container
+
+ Timeout (in seconds) to stop the container before killing it (-1 for no timeout)
+
Size of /dev/shm (e.g. 64M, 1G)
{Locked="/dev/shm"}Command line arguments should not be translated
diff --git a/src/windows/common/WSLCContainerLauncher.cpp b/src/windows/common/WSLCContainerLauncher.cpp
index e022d7976..cdbb50a0c 100644
--- a/src/windows/common/WSLCContainerLauncher.cpp
+++ b/src/windows/common/WSLCContainerLauncher.cpp
@@ -129,6 +129,11 @@ void WSLCContainerLauncher::SetDefaultStopSignal(WSLCSignal Signal)
m_stopSignal = Signal;
}
+void WSLCContainerLauncher::SetStopTimeout(LONG Timeout)
+{
+ m_stopTimeout = Timeout;
+}
+
void WSLCContainerLauncher::SetShmSize(int64_t ShmSize)
{
m_shmSize = ShmSize;
@@ -296,6 +301,12 @@ std::pair> WSLCContainerLauncher::C
options.PortsCount = static_cast(m_ports.size());
options.StopSignal = m_stopSignal;
options.Flags = m_containerFlags;
+ if (m_stopTimeout.has_value())
+ {
+ options.StopTimeout = m_stopTimeout.value();
+ WI_SetFlag(options.Flags, WSLCContainerFlagsStopTimeout);
+ }
+
options.ShmSize = m_shmSize;
if (!entrypointStorage.empty())
diff --git a/src/windows/common/WSLCContainerLauncher.h b/src/windows/common/WSLCContainerLauncher.h
index fa01c59a2..0cde0d926 100644
--- a/src/windows/common/WSLCContainerLauncher.h
+++ b/src/windows/common/WSLCContainerLauncher.h
@@ -75,6 +75,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
void SetName(std::string&& Name);
void SetEntrypoint(std::vector&& entrypoint);
void SetDefaultStopSignal(WSLCSignal Signal);
+ void SetStopTimeout(LONG Timeout);
void SetShmSize(int64_t ShmSize);
void SetContainerFlags(WSLCContainerFlags Flags);
void SetHostname(std::string&& Hostname);
@@ -103,6 +104,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher
std::string m_networkMode;
std::vector m_entrypoint;
WSLCSignal m_stopSignal = WSLCSignalNone;
+ std::optional m_stopTimeout;
int64_t m_shmSize = 0;
WSLCContainerFlags m_containerFlags = WSLCContainerFlagsNone;
std::string m_hostname;
diff --git a/src/windows/inc/docker_schema.h b/src/windows/inc/docker_schema.h
index a7e998bc6..fcc796a7a 100644
--- a/src/windows/inc/docker_schema.h
+++ b/src/windows/inc/docker_schema.h
@@ -297,6 +297,7 @@ struct CreateContainer
std::string Hostname;
std::string Domainname;
std::optional StopSignal;
+ std::optional StopTimeout;
std::optional WorkingDir;
std::optional> Cmd;
std::optional> Entrypoint;
@@ -307,7 +308,7 @@ struct CreateContainer
NetworkingConfig NetworkingConfig;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(
- CreateContainer, Image, Cmd, Tty, OpenStdin, StdinOnce, Entrypoint, Env, ExposedPorts, HostConfig, StopSignal, WorkingDir, User, Hostname, Domainname, Labels, NetworkingConfig);
+ CreateContainer, Image, Cmd, Tty, OpenStdin, StdinOnce, Entrypoint, Env, ExposedPorts, HostConfig, StopSignal, StopTimeout, WorkingDir, User, Hostname, Domainname, Labels, NetworkingConfig);
};
struct ContainerInspectState
@@ -329,8 +330,10 @@ struct ContainerConfig
std::optional> Env;
std::optional> Cmd;
std::optional> Entrypoint;
+ std::optional StopSignal;
+ std::optional StopTimeout;
- NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Image, User, WorkingDir, Env, Cmd, Entrypoint);
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Image, User, WorkingDir, Env, Cmd, Entrypoint, StopSignal, StopTimeout);
};
struct InspectMount
diff --git a/src/windows/inc/wslc_schema.h b/src/windows/inc/wslc_schema.h
index db4354ba3..a936a3355 100644
--- a/src/windows/inc/wslc_schema.h
+++ b/src/windows/inc/wslc_schema.h
@@ -76,8 +76,9 @@ struct ContainerConfig
std::optional> Entrypoint;
std::string User;
std::string WorkingDir;
+ std::optional StopTimeout;
- NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Env, Cmd, Entrypoint, User, WorkingDir);
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Env, Cmd, Entrypoint, User, WorkingDir, StopTimeout);
};
struct InspectEndpointSettings
diff --git a/src/windows/service/inc/WSLCShared.idl b/src/windows/service/inc/WSLCShared.idl
index d02849aa0..39e22eb76 100644
--- a/src/windows/service/inc/WSLCShared.idl
+++ b/src/windows/service/inc/WSLCShared.idl
@@ -98,9 +98,10 @@ typedef enum _WSLCContainerFlags
WSLCContainerFlagsGpu = 2, // Enable GPU access.
WSLCContainerFlagsInit = 4, // Run the container under an init process.
WSLCContainerFlagsPublishAll = 8, // Publish all exposed ports.
+ WSLCContainerFlagsStopTimeout = 16, // The StopTimeout field is set and should be honored (otherwise StopTimeout is ignored).
} WSLCContainerFlags;
-cpp_quote("#define WSLCContainerFlagsValid (WSLCContainerFlagsRm | WSLCContainerFlagsGpu | WSLCContainerFlagsInit | WSLCContainerFlagsPublishAll)")
+cpp_quote("#define WSLCContainerFlagsValid (WSLCContainerFlagsRm | WSLCContainerFlagsGpu | WSLCContainerFlagsInit | WSLCContainerFlagsPublishAll | WSLCContainerFlagsStopTimeout)")
cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLCContainerFlags);")
diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl
index 147b44ce4..9fc0fa4e5 100644
--- a/src/windows/service/inc/wslc.idl
+++ b/src/windows/service/inc/wslc.idl
@@ -44,6 +44,8 @@ cpp_quote("#define WSLC_CONTAINER_ID_LENGTH 64")
cpp_quote("#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45")
cpp_quote("#define WSLC_MAX_SAVE_IMAGES_COUNT 256")
cpp_quote("#define WSLC_EPHEMERAL_PORT 0")
+cpp_quote("#define WSLC_STOP_TIMEOUT_DEFAULT LONG_MIN") // Pass to Stop() to use the default container stop timeout
+cpp_quote("#define WSLC_STOP_TIMEOUT_NONE -1") // Wait forever for the container to stop.
typedef
struct _WSLCVersion {
@@ -263,6 +265,9 @@ typedef struct _WSLCContainerOptions
LONGLONG NanoCpus;
[unique, size_is(UlimitsCount)] const WSLCUlimit* Ulimits;
ULONG UlimitsCount;
+
+ // Ignored unless WSLCContainerFlagsStopTimeout is set in Flags.
+ LONG StopTimeout;
} WSLCContainerOptions;
typedef char WSLCContainerId[WSLC_CONTAINER_ID_LENGTH + 1] ;
@@ -726,7 +731,7 @@ interface IWSLCSessionManager : IUnknown
HRESULT OpenSession([in] ULONG Id, [out] IWSLCSession** Session);
HRESULT OpenSessionByName([in, unique] LPCWSTR DisplayName, [out] IWSLCSession** Session);
}
-
+
// Ensure wslcsdk.h and wslcsdk.idl are also updated.
cpp_quote("#define WSLC_E_BASE (0x0600)")
cpp_quote("#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */")
diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h
index 200022bcb..c43bb3c91 100644
--- a/src/windows/wslc/arguments/ArgumentDefinitions.h
+++ b/src/windows/wslc/arguments/ArgumentDefinitions.h
@@ -103,6 +103,7 @@ _(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, L
_(Signal, "signal", L"s", Kind::Value, Localization::WSLCCLI_SignalArgDescription()) \
_(Source, "source", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SourceArgDescription()) \
_(StopSignal, "stop-signal", NO_ALIAS, Kind::Value, Localization::WSLCCLI_StopSignalArgDescription()) \
+_(StopTimeout, "stop-timeout", NO_ALIAS, Kind::Value, Localization::WSLCCLI_StopTimeoutArgDescription()) \
_(Tail, "tail", L"n", Kind::Value, Localization::WSLCCLI_TailArgDescription()) \
_(Tag, "tag", L"t", Kind::Value, Localization::WSLCCLI_TagArgDescription()) \
_(Target, "target", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_TargetArgDescription()) \
diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp
index 735196f01..fd78795f8 100644
--- a/src/windows/wslc/arguments/ArgumentValidation.cpp
+++ b/src/windows/wslc/arguments/ArgumentValidation.cpp
@@ -48,6 +48,10 @@ void Argument::Validate(const ArgMap& execArgs) const
validation::ValidateWSLCSignalFromString(execArgs.GetAll(), m_name);
break;
+ case ArgType::StopTimeout:
+ validation::ValidateIntegerFromString(execArgs.GetAll(), m_name);
+ break;
+
case ArgType::ShmSize:
validation::ValidateMemorySize(execArgs.GetAll(), m_name);
break;
diff --git a/src/windows/wslc/commands/ContainerCreateCommand.cpp b/src/windows/wslc/commands/ContainerCreateCommand.cpp
index 1997e8f27..c21d0ecec 100644
--- a/src/windows/wslc/commands/ContainerCreateCommand.cpp
+++ b/src/windows/wslc/commands/ContainerCreateCommand.cpp
@@ -58,6 +58,7 @@ std::vector ContainerCreateCommand::GetArguments() const
// Argument::Create(ArgType::Scheme),
Argument::Create(ArgType::ShmSize),
Argument::Create(ArgType::StopSignal),
+ Argument::Create(ArgType::StopTimeout),
Argument::Create(ArgType::TMPFS, false, NO_LIMIT),
Argument::Create(ArgType::TTY),
Argument::Create(ArgType::Ulimit, false, NO_LIMIT),
diff --git a/src/windows/wslc/commands/ContainerRunCommand.cpp b/src/windows/wslc/commands/ContainerRunCommand.cpp
index c754e4c80..990b5dd8d 100644
--- a/src/windows/wslc/commands/ContainerRunCommand.cpp
+++ b/src/windows/wslc/commands/ContainerRunCommand.cpp
@@ -59,6 +59,7 @@ std::vector ContainerRunCommand::GetArguments() const
// Argument::Create(ArgType::Scheme),
Argument::Create(ArgType::ShmSize),
Argument::Create(ArgType::StopSignal),
+ Argument::Create(ArgType::StopTimeout),
Argument::Create(ArgType::TMPFS, false, NO_LIMIT),
Argument::Create(ArgType::TTY),
Argument::Create(ArgType::Ulimit, false, NO_LIMIT),
diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h
index da9d5dbc6..eb7bd2f1a 100644
--- a/src/windows/wslc/services/ContainerModel.h
+++ b/src/windows/wslc/services/ContainerModel.h
@@ -38,6 +38,7 @@ struct ContainerOptions
bool TTY = false;
bool PublishAll = false;
WSLCSignal StopSignal = WSLCSignalNone;
+ std::optional StopTimeout{};
std::optional ShmSize{};
bool Gpu = false;
std::vector Ports;
@@ -67,10 +68,8 @@ struct CreateContainerResult
struct StopContainerOptions
{
- static constexpr LONG DefaultTimeout = -1;
-
WSLCSignal Signal = WSLCSignalNone;
- LONG Timeout = DefaultTimeout;
+ LONG Timeout = WSLC_STOP_TIMEOUT_DEFAULT;
};
struct PruneContainersResult
diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp
index 529c1039f..9a85ebf98 100644
--- a/src/windows/wslc/services/ContainerService.cpp
+++ b/src/windows/wslc/services/ContainerService.cpp
@@ -136,6 +136,11 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(
containerLauncher.SetDefaultStopSignal(options.StopSignal);
}
+ if (options.StopTimeout.has_value())
+ {
+ containerLauncher.SetStopTimeout(options.StopTimeout.value());
+ }
+
if (options.ShmSize.has_value())
{
containerLauncher.SetShmSize(options.ShmSize.value());
diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp
index bbd61025a..ad7d112d8 100644
--- a/src/windows/wslc/tasks/ContainerTasks.cpp
+++ b/src/windows/wslc/tasks/ContainerTasks.cpp
@@ -443,6 +443,11 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
options.StopSignal = validation::GetWSLCSignalFromString(context.Args.Get());
}
+ if (context.Args.Contains(ArgType::StopTimeout))
+ {
+ options.StopTimeout = validation::GetIntegerFromString(context.Args.Get());
+ }
+
if (context.Args.Contains(ArgType::ShmSize))
{
options.ShmSize = validation::GetMemorySizeFromString(context.Args.Get());
diff --git a/src/windows/wslcsession/DockerHTTPClient.cpp b/src/windows/wslcsession/DockerHTTPClient.cpp
index 9c855a6d6..98b9a3e45 100644
--- a/src/windows/wslcsession/DockerHTTPClient.cpp
+++ b/src/windows/wslcsession/DockerHTTPClient.cpp
@@ -302,7 +302,7 @@ void DockerHTTPClient::StartContainer(const std::string& Id, const std::optional
Transaction(verb::post, url);
}
-void DockerHTTPClient::StopContainer(const std::string& Id, std::optional Signal, std::optional TimeoutSeconds)
+void DockerHTTPClient::StopContainer(const std::string& Id, std::optional Signal, std::optional TimeoutSeconds)
{
auto url = URL::Create("/containers/{}/stop", Id);
if (Signal.has_value())
diff --git a/src/windows/wslcsession/DockerHTTPClient.h b/src/windows/wslcsession/DockerHTTPClient.h
index fd24a490b..3e837be72 100644
--- a/src/windows/wslcsession/DockerHTTPClient.h
+++ b/src/windows/wslcsession/DockerHTTPClient.h
@@ -126,7 +126,7 @@ class DockerHTTPClient
bool all = false, int limit = -1, const std::map>& filters = {});
common::docker_schema::CreatedContainer CreateContainer(const common::docker_schema::CreateContainer& Request, const std::optional& Name);
void StartContainer(const std::string& Id, const std::optional& DetachKeys);
- void StopContainer(const std::string& Id, std::optional Signal, std::optional TimeoutSeconds);
+ void StopContainer(const std::string& Id, std::optional Signal, std::optional TimeoutSeconds);
void DeleteContainer(const std::string& Id, bool Force, bool DeleteVolumes = false);
void SignalContainer(const std::string& Id, std::optional Signal);
common::docker_schema::InspectContainer InspectContainer(const std::string& Id);
diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp
index 4da373ed4..6618fceea 100644
--- a/src/windows/wslcsession/WSLCContainer.cpp
+++ b/src/windows/wslcsession/WSLCContainer.cpp
@@ -67,6 +67,14 @@ using WslcInspectContainer = wsl::windows::common::wslc_schema::InspectContainer
namespace {
+void ValidateStopTimeout(LONG TimeoutSeconds)
+{
+ THROW_HR_WITH_USER_ERROR_IF(
+ E_INVALIDARG,
+ Localization::MessageWslcInvalidStopTimeout(TimeoutSeconds),
+ TimeoutSeconds < 0 && TimeoutSeconds != WSLC_STOP_TIMEOUT_NONE && TimeoutSeconds != WSLC_STOP_TIMEOUT_DEFAULT);
+}
+
std::vector StringArrayToVector(const WSLCStringArray& array)
{
if (array.Count == 0)
@@ -944,6 +952,8 @@ void WSLCContainerImpl::Stop(WSLCSignal Signal, LONG TimeoutSeconds, bool Kill)
SignalArg = Signal;
}
+ ValidateStopTimeout(TimeoutSeconds);
+
// Don't wait for the container to stop if we're not sending SIGKILL, since it may not stop the container.
// N.B. If the signal was SIGTERM for instance, we'll receive the stop notification via OnEvent().
bool waitForStop = !Kill || (SignalArg.value_or(WSLCSignalSIGKILL) == WSLCSignalSIGKILL);
@@ -961,10 +971,10 @@ void WSLCContainerImpl::Stop(WSLCSignal Signal, LONG TimeoutSeconds, bool Kill)
}
else
{
- std::optional TimeoutArg;
- if (TimeoutSeconds >= 0)
+ std::optional TimeoutArg;
+ if (TimeoutSeconds != WSLC_STOP_TIMEOUT_DEFAULT)
{
- TimeoutArg = static_cast(TimeoutSeconds);
+ TimeoutArg = TimeoutSeconds;
}
m_dockerClient.StopContainer(m_id, SignalArg, TimeoutArg);
@@ -1305,6 +1315,7 @@ WslcInspectContainer WSLCContainerImpl::BuildInspectContainer(const DockerInspec
wslcInspect.Config.Entrypoint = dockerInspect.Config.Entrypoint;
wslcInspect.Config.User = dockerInspect.Config.User;
wslcInspect.Config.WorkingDir = dockerInspect.Config.WorkingDir;
+ wslcInspect.Config.StopTimeout = dockerInspect.Config.StopTimeout;
// Map WSLC port mappings (Windows host ports only).
for (const auto& e : m_mappedPorts)
@@ -1419,6 +1430,14 @@ std::unique_ptr WSLCContainerImpl::Create(
request.StopSignal = std::to_string(containerOptions.StopSignal);
}
+ if (WI_IsFlagSet(containerOptions.Flags, WSLCContainerFlagsStopTimeout))
+ {
+ ValidateStopTimeout(containerOptions.StopTimeout);
+
+ THROW_HR_IF(E_INVALIDARG, containerOptions.StopTimeout == WSLC_STOP_TIMEOUT_DEFAULT);
+ request.StopTimeout = static_cast(containerOptions.StopTimeout);
+ }
+
if (containerOptions.InitProcessOptions.CurrentDirectory != nullptr)
{
request.WorkingDir = containerOptions.InitProcessOptions.CurrentDirectory;
diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp
index 8baad20ab..09baf22a9 100644
--- a/test/windows/WSLCTests.cpp
+++ b/test/windows/WSLCTests.cpp
@@ -5572,7 +5572,7 @@ class WSLCTests
// Invalid container flags are rejected with E_INVALIDARG.
options.Image = "debian:latest";
- options.Flags = static_cast(0x10);
+ options.Flags = static_cast(0x20);
VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateContainer(&options, nullptr, &container));
// Invalid init process flags are rejected with E_INVALIDARG.
@@ -5647,7 +5647,7 @@ class WSLCTests
VERIFY_ARE_EQUAL(process.Wait(), WSLCSignalSIGHUP + 128);
}
- // Validate that the default stop signal can be overriden.
+ // Validate that the default stop signal can be overridden.
{
WSLCContainerLauncher launcher("debian:latest", "test-stop-signal-2", {"/bin/cat"}, {}, {}, WSLCProcessFlagsStdin);
launcher.SetDefaultStopSignal(WSLCSignalSIGHUP);
@@ -6232,6 +6232,67 @@ class WSLCTests
expectContainerList({});
}
+ // test StopContainer with custom timeouts.
+ // N.B. We can't validate the actual timeouts since the tests environment will affect container stop times.
+ {
+ {
+ // Create a container with a no stop timeout.
+ WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout-1", {"sleep", "99999"});
+ launcher.SetStopTimeout(WSLC_STOP_TIMEOUT_NONE);
+
+ auto container = launcher.Launch(*m_defaultSession);
+
+ auto inspect = container.Inspect();
+ VERIFY_ARE_EQUAL(inspect.Config.StopTimeout.value_or(0), WSLC_STOP_TIMEOUT_NONE);
+
+ // Validate that passing '0' as the stop timeout overrides the default
+ VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalNone, 0));
+ }
+
+ {
+ // Create a container with an instant stop timeout.
+ WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout-2", {"sleep", "99999"});
+ launcher.SetStopTimeout(0);
+
+ auto container = launcher.Create(*m_defaultSession);
+
+ auto inspect = container.Inspect();
+ VERIFY_ARE_EQUAL(inspect.Config.StopTimeout.value_or(-1), 0);
+ }
+
+ {
+ // Create a container with an short stop timeout.
+ WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout-3", {"sleep", "99999"});
+ launcher.SetStopTimeout(1);
+
+ auto container = launcher.Launch(*m_defaultSession);
+
+ auto inspect = container.Inspect();
+ VERIFY_ARE_EQUAL(inspect.Config.StopTimeout.value_or(0), 1);
+
+ auto initProcess = container.GetInitProcess();
+ std::thread stopThread([&]() { VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalNone, -1)); });
+
+ auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
+ // TODO: calling Kill() here hangs since Stop() holds the container lock.
+ // Update this once fixed to:
+ // LOG_IF_FAILED(container.Get().Kill(WSLCSignalSIGKILL));
+
+ LOG_IF_FAILED(initProcess.Get().Signal(WSLCSignalSIGKILL));
+
+ if (stopThread.joinable())
+ {
+ stopThread.join();
+ }
+ });
+
+ // Wait for at least 2 seconds for the stop to complete to prove that the default 1 second timeout was correctly overriden.
+ auto waitResult = WaitForSingleObject(stopThread.native_handle(), 2000);
+
+ VERIFY_ARE_EQUAL(waitResult, WAIT_TIMEOUT);
+ }
+ }
+
// Validate that Kill() works as expected
{
WSLCContainerLauncher launcher("debian:latest", "test-container-kill", {"sleep", "99999"}, {});
diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
index ca3bb555b..1c7bd9007 100644
--- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
@@ -726,6 +726,72 @@ class WSLCE2EContainerCreateTests
VERIFY_ARE_EQUAL(ExpectedExitCode, inspect.State.ExitCode);
}
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_StopTimeout)
+ {
+ // A positive value is forwarded to the container configuration.
+ {
+ constexpr int ExpectedStopTimeout = 30;
+ auto result = RunWslc(std::format(
+ L"container create --stop-timeout {} --name {} {}", ExpectedStopTimeout, WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ const auto inspect = InspectContainer(WslcContainerName);
+ VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
+ VERIFY_ARE_EQUAL(ExpectedStopTimeout, inspect.Config.StopTimeout.value());
+ EnsureContainerDoesNotExist(WslcContainerName);
+ }
+
+ // A value of 0 (stop the container immediately) is a valid, explicit timeout.
+ {
+ auto result =
+ RunWslc(std::format(L"container create --stop-timeout 0 --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ const auto inspect = InspectContainer(WslcContainerName);
+ VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
+ VERIFY_ARE_EQUAL(0, inspect.Config.StopTimeout.value());
+ EnsureContainerDoesNotExist(WslcContainerName);
+ }
+
+ // A value of -1 means "no timeout"; it is a valid, explicit value forwarded to the configuration.
+ {
+ auto result =
+ RunWslc(std::format(L"container create --stop-timeout -1 --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ const auto inspect = InspectContainer(WslcContainerName);
+ VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
+ VERIFY_ARE_EQUAL(-1, inspect.Config.StopTimeout.value());
+ EnsureContainerDoesNotExist(WslcContainerName);
+ }
+
+ // When --stop-timeout is not specified, no timeout is forwarded to the container configuration.
+ {
+ auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ const auto inspect = InspectContainer(WslcContainerName);
+ VERIFY_IS_FALSE(inspect.Config.StopTimeout.has_value());
+ }
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_StopTimeout_Invalid)
+ {
+ {
+ auto result =
+ RunWslc(std::format(L"container create --stop-timeout abc --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"Invalid stop-timeout argument value: abc\r\n", .ExitCode = 1});
+ VerifyContainerIsNotListed(WslcContainerName);
+ }
+
+ {
+ auto result =
+ RunWslc(std::format(L"container create --stop-timeout -2 --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"Invalid stop timeout value: -2\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
+ VerifyContainerIsNotListed(WslcContainerName);
+ }
+ }
+
WSLC_TEST_METHOD(WSLCE2E_Container_Create_ShmSize)
{
auto result = RunWslc(
@@ -1225,6 +1291,7 @@ class WSLCE2EContainerCreateTests
<< L" --rm Remove the container after it stops\r\n"
<< L" --shm-size Size of /dev/shm (e.g. 64M, 1G)\r\n"
<< L" --stop-signal Signal to stop the container\r\n"
+ << L" --stop-timeout Timeout (in seconds) to stop the container before killing it (-1 for no timeout)\r\n"
<< L" --tmpfs Mount tmpfs to the container at the given path\r\n"
<< L" -t,--tty Open a TTY with the container process.\r\n"
<< L" --ulimit Ulimit options (format: =[:], use -1 for unlimited)\r\n"
diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
index 03f19ecda..290ed3bc4 100644
--- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
@@ -971,6 +971,76 @@ class WSLCE2EContainerRunTests
VERIFY_ARE_EQUAL(ExpectedExitCode, inspect.State.ExitCode);
}
+ WSLC_TEST_METHOD(WSLCE2E_Container_Run_StopTimeout)
+ {
+ // A positive value is forwarded to the container configuration.
+ {
+ constexpr int ExpectedStopTimeout = 25;
+ auto result = RunWslc(std::format(
+ L"container run -d --stop-timeout {} --name {} {} sleep infinity",
+ ExpectedStopTimeout,
+ WslcContainerName,
+ DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ const auto inspect = InspectContainer(WslcContainerName);
+ VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
+ VERIFY_ARE_EQUAL(ExpectedStopTimeout, inspect.Config.StopTimeout.value());
+ EnsureContainerDoesNotExist(WslcContainerName);
+ }
+
+ // A value of 0 (stop the container immediately) is a valid, explicit timeout.
+ {
+ auto result = RunWslc(std::format(
+ L"container run -d --stop-timeout 0 --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ const auto inspect = InspectContainer(WslcContainerName);
+ VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
+ VERIFY_ARE_EQUAL(0, inspect.Config.StopTimeout.value());
+ EnsureContainerDoesNotExist(WslcContainerName);
+ }
+
+ // A value of -1 means "no timeout"; it is a valid, explicit value forwarded to the configuration.
+ {
+ auto result = RunWslc(std::format(
+ L"container run -d --stop-timeout -1 --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ const auto inspect = InspectContainer(WslcContainerName);
+ VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
+ VERIFY_ARE_EQUAL(-1, inspect.Config.StopTimeout.value());
+ EnsureContainerDoesNotExist(WslcContainerName);
+ }
+
+ // When --stop-timeout is not specified, no timeout is forwarded to the container configuration.
+ {
+ auto result =
+ RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ const auto inspect = InspectContainer(WslcContainerName);
+ VERIFY_IS_FALSE(inspect.Config.StopTimeout.has_value());
+ }
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Run_StopTimeout_Invalid)
+ {
+ {
+ auto result =
+ RunWslc(std::format(L"container run --rm --stop-timeout abc --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"Invalid stop-timeout argument value: abc\r\n", .ExitCode = 1});
+ EnsureContainerDoesNotExist(WslcContainerName);
+ }
+
+ {
+ auto result =
+ RunWslc(std::format(L"container run --rm --stop-timeout -2 --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"Invalid stop timeout value: -2\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
+ EnsureContainerDoesNotExist(WslcContainerName);
+ }
+ }
+
WSLC_TEST_METHOD(WSLCE2E_Container_Run_ShmSize)
{
auto result = RunWslc(std::format(L"container run --rm --shm-size 128M {} df -h /dev/shm", DebianImage.NameAndTag()));
@@ -1177,6 +1247,7 @@ class WSLCE2EContainerRunTests
<< L" --rm Remove the container after it stops\r\n"
<< L" --shm-size Size of /dev/shm (e.g. 64M, 1G)\r\n"
<< L" --stop-signal Signal to stop the container\r\n"
+ << L" --stop-timeout Timeout (in seconds) to stop the container before killing it (-1 for no timeout)\r\n"
<< L" --tmpfs Mount tmpfs to the container at the given path\r\n"
<< L" -t,--tty Open a TTY with the container process.\r\n"
<< L" --ulimit Ulimit options (format: =[:], use -1 for unlimited)\r\n"
diff --git a/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp
index 0cef70d0d..52ac84538 100644
--- a/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp
@@ -237,25 +237,6 @@ class WSLCE2EContainerStopTests
}
}
- WSLC_TEST_METHOD(WSLCE2E_Container_Stop_ValidTimeoutNegativeOne)
- {
- // Run a container in the background
- auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
- result.Verify({.Stderr = L"", .ExitCode = 0});
- const auto containerId = result.GetStdoutOneLine();
- VERIFY_IS_FALSE(containerId.empty());
-
- // Verify container is running
- VerifyContainerIsListed(containerId, L"running");
-
- // -1 is a valid timeout value
- result = RunWslc(std::format(L"container stop {} -t -1", containerId));
- result.Verify({.Stderr = L"", .ExitCode = 0});
-
- // Verify the container is no longer running
- VerifyContainerIsListed(containerId, L"exited");
- }
-
private:
const std::wstring WslcContainerName = L"wslc-test-container";
const std::wstring WslcContainerName2 = L"wslc-test-container-2";