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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions localization/strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -2125,6 +2125,10 @@ Usage:
<value>Container '{}' is not running.</value>
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="MessageWslcContainerRestartAutoRemove" xml:space="preserve">
<value>Container '{}' was created with --rm and cannot be restarted.</value>
<comment>{FixedPlaceholder="{}"}{Locked="--rm "}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="MessageWslcContainerIsRunning" xml:space="preserve">
<value>Container '{}' is running.</value>
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
Expand Down Expand Up @@ -2515,6 +2519,12 @@ For privacy information about this product please visit https://aka.ms/privacy.<
<data name="WSLCCLI_ContainerRemoveLongDesc" xml:space="preserve">
<value>Removes containers.</value>
</data>
<data name="WSLCCLI_ContainerRestartDesc" xml:space="preserve">
<value>Restart containers.</value>
</data>
<data name="WSLCCLI_ContainerRestartLongDesc" xml:space="preserve">
<value>Restarts one or more containers. Running containers are stopped and started again; stopped containers are started.</value>
</data>
<data name="WSLCCLI_ContainerRunDesc" xml:space="preserve">
<value>Run a container.</value>
</data>
Expand Down
1 change: 1 addition & 0 deletions src/windows/service/inc/wslc.idl
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ interface IWSLCContainer : IUnknown
HRESULT Stats([out] LPSTR* Output);
HRESULT ConnectToNetwork([in] const WSLCNetworkConnectionOptions* Options);
HRESULT DisconnectFromNetwork([in] LPCSTR NetworkName);
HRESULT Restart([in] WSLCSignal Signal, [in] LONG TimeoutSeconds, [in, unique] IWarningCallback* WarningCallback);
}

typedef struct _WSLCDeletedImageInformation
Expand Down
1 change: 1 addition & 0 deletions src/windows/wslc/commands/ContainerCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ std::vector<std::unique_ptr<Command>> ContainerCommand::GetCommands() const
commands.push_back(std::make_unique<ContainerListCommand>(FullName()));
commands.push_back(std::make_unique<ContainerPruneCommand>(FullName()));
commands.push_back(std::make_unique<ContainerRemoveCommand>(FullName()));
commands.push_back(std::make_unique<ContainerRestartCommand>(FullName()));
commands.push_back(std::make_unique<ContainerRunCommand>(FullName()));
commands.push_back(std::make_unique<ContainerStartCommand>(FullName()));
commands.push_back(std::make_unique<ContainerStatsCommand>(FullName()));
Expand Down
15 changes: 15 additions & 0 deletions src/windows/wslc/commands/ContainerCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,21 @@ struct ContainerRemoveCommand final : public Command
void ExecuteInternal(CLIExecutionContext& context) const override;
};

// Restart Command
struct ContainerRestartCommand final : public Command
{
constexpr static std::wstring_view CommandName = L"restart";
ContainerRestartCommand(const std::wstring& parent) : Command(CommandName, parent)
{
}
std::vector<Argument> GetArguments() const override;
std::wstring ShortDescription() const override;
std::wstring LongDescription() const override;

protected:
void ExecuteInternal(CLIExecutionContext& context) const override;
};

// Run Command
struct ContainerRunCommand final : public Command
{
Expand Down
54 changes: 54 additions & 0 deletions src/windows/wslc/commands/ContainerRestartCommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

ContainerRestartCommand.cpp

Abstract:

Implementation of command execution logic.

--*/

#include "ContainerCommand.h"
#include "CLIExecutionContext.h"
#include "ContainerTasks.h"
#include "SessionTasks.h"
#include "Task.h"

using namespace wsl::windows::wslc::execution;
using namespace wsl::windows::wslc::task;
using namespace wsl::shared;

namespace wsl::windows::wslc {
// Container Restart Command
std::vector<Argument> ContainerRestartCommand::GetArguments() const
{
return {
Argument::Create(ArgType::ContainerId, std::nullopt, NO_LIMIT),
Comment thread
beena352 marked this conversation as resolved.
Argument::Create(ArgType::Signal),
Argument::Create(ArgType::Time),
};
}

std::wstring ContainerRestartCommand::ShortDescription() const
{
return Localization::WSLCCLI_ContainerRestartDesc();
}

std::wstring ContainerRestartCommand::LongDescription() const
{
return Localization::WSLCCLI_ContainerRestartLongDesc();
}

// clang-format off
void ContainerRestartCommand::ExecuteInternal(CLIExecutionContext& context) const
{
context
<< ResolveSession
<< RestartContainers;
}
// clang-format on
} // namespace wsl::windows::wslc
8 changes: 8 additions & 0 deletions src/windows/wslc/services/ContainerService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,14 @@ void ContainerService::Stop(Session& session, const std::string& id, StopContain
THROW_IF_FAILED_EXCEPT(container->Stop(options.Signal, options.Timeout), WSLC_E_CONTAINER_NOT_RUNNING);
}

void ContainerService::Restart(Session& session, const std::string& id, StopContainerOptions options)
{
wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
THROW_IF_FAILED(container->Restart(options.Signal, options.Timeout, warningCallback.Get()));
}

void ContainerService::Kill(Session& session, const std::string& id, WSLCSignal signal)
{
wil::com_ptr<IWSLCContainer> container;
Expand Down
1 change: 1 addition & 0 deletions src/windows/wslc/services/ContainerService.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct ContainerService
static models::CreateContainerResult Create(models::Session& session, const std::string& image, models::ContainerOptions options);
static int Start(models::Session& session, const std::string& id, bool attach = false);
static void Stop(models::Session& session, const std::string& id, models::StopContainerOptions options);
static void Restart(models::Session& session, const std::string& id, models::StopContainerOptions options);
static void Kill(models::Session& session, const std::string& id, WSLCSignal signal = WSLCSignalSIGKILL);
static void Delete(models::Session& session, const std::string& id, bool force);
static std::vector<models::ContainerInformation> List(
Expand Down
23 changes: 23 additions & 0 deletions src/windows/wslc/tasks/ContainerTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,29 @@ void StopContainers(CLIExecutionContext& context)
}
}

void RestartContainers(CLIExecutionContext& context)
{
WI_ASSERT(context.Data.Contains(Data::Session));
auto& session = context.Data.Get<Data::Session>();
auto containersToRestart = context.Args.GetAll<ArgType::ContainerId>();
StopContainerOptions options;
if (context.Args.Contains(ArgType::Signal))
{
options.Signal = validation::GetWSLCSignalFromString(context.Args.Get<ArgType::Signal>());
}

if (context.Args.Contains(ArgType::Time))
{
options.Timeout = validation::GetIntegerFromString<LONG>(context.Args.Get<ArgType::Time>());
}

for (const auto& id : containersToRestart)
{
ContainerService::Restart(session, WideToMultiByte(id), options);
PrintMessage(id);
}
}

void ViewContainerLogs(CLIExecutionContext& context)
{
WI_ASSERT(context.Data.Contains(Data::Session));
Expand Down
1 change: 1 addition & 0 deletions src/windows/wslc/tasks/ContainerTasks.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void KillContainers(CLIExecutionContext& context);
void ListContainers(CLIExecutionContext& context);
void PruneContainers(CLIExecutionContext& context);
void RemoveContainers(CLIExecutionContext& context);
void RestartContainers(CLIExecutionContext& context);
void RunContainer(CLIExecutionContext& context);
void SetContainerOptionsFromArgs(CLIExecutionContext& context);
void ShowContainerStats(CLIExecutionContext& context);
Expand Down
43 changes: 43 additions & 0 deletions src/windows/wslcsession/WSLCContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2524,6 +2524,40 @@ void WSLCContainerImpl::DisconnectFromNetwork(LPCSTR NetworkName)
TraceLoggingValue(NetworkName, "NetworkName"));
}

void WSLCContainerImpl::Restart(WSLCSignal Signal, LONG TimeoutSeconds)
{
// N.B. Snapshot under a brief shared lock and release before composing Stop/Start.
// m_lock is a non-recursive srwlock; Stop and Start each take it exclusively.
WSLCContainerState snapshotState{};
WSLCContainerFlags snapshotFlags{};
{
auto lock = m_lock.lock_shared();
snapshotState = m_state;
snapshotFlags = m_containerFlags;
}

// Refuse --rm before stopping: Stop -> OnStopped would delete the container.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locking wise, I don't think that we can drop the lock here unfortunately, otherwise another stop / start operation could arrive before we complete, which would leak to weird states

THROW_HR_WITH_USER_ERROR_IF(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the behavior we want: Docker supports restarting containers when --rm is set.

HRESULT_FROM_WIN32(ERROR_INVALID_STATE),
Localization::MessageWslcContainerRestartAutoRemove(m_id),
WI_IsFlagSet(snapshotFlags, WSLCContainerFlagsRm));

THROW_HR_IF_MSG(
HRESULT_FROM_WIN32(ERROR_INVALID_STATE),
snapshotState != WslcContainerStateRunning && snapshotState != WslcContainerStateExited && snapshotState != WslcContainerStateCreated,
"Cannot restart container '%hs', state %i",
m_id.c_str(),
snapshotState);

if (snapshotState == WslcContainerStateRunning)
{
// N.B. If the container exits on its own before Stop acquires m_lock, Stop no-ops on Exited.
Stop(Signal, TimeoutSeconds, /*Kill*/ false);
}

Start(WSLCContainerStartFlagsNone, nullptr);
}

HRESULT WSLCContainer::GetLabels(WSLCLabelInformation** Labels, ULONG* Count)
try
{
Expand Down Expand Up @@ -2553,6 +2587,15 @@ try
}
CATCH_RETURN();

HRESULT WSLCContainer::Restart(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds, _In_opt_ IWarningCallback* WarningCallback)
try
{
// N.B. WarningCallback is ambient here so EMIT_USER_WARNING reaches the client from both the Stop and Start phases.
WSLCExecutionContext context(&m_session, WarningCallback);
return CallImpl(&WSLCContainerImpl::Restart, Signal, TimeoutSeconds);
}
CATCH_RETURN();

HRESULT WSLCContainer::InterfaceSupportsErrorInfo(REFIID riid)
{
return riid == __uuidof(IWSLCContainer) || riid == __uuidof(IWSLCCompatContainer) ? S_OK : S_FALSE;
Expand Down
2 changes: 2 additions & 0 deletions src/windows/wslcsession/WSLCContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class WSLCContainerImpl
void GetLabels(WSLCLabelInformation** Labels, ULONG* Count) const;
void ConnectToNetwork(const WSLCNetworkConnectionOptions* Options);
void DisconnectFromNetwork(LPCSTR NetworkName);
void Restart(WSLCSignal Signal, LONG TimeoutSeconds);

void CopyTo(IWSLCContainer** Container) const;

Expand Down Expand Up @@ -247,6 +248,7 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLCContainer
IFACEMETHOD(Stats)(_Out_ LPSTR* Output) override;
IFACEMETHOD(ConnectToNetwork)(_In_ const WSLCNetworkConnectionOptions* Options) override;
IFACEMETHOD(DisconnectFromNetwork)(_In_ LPCSTR NetworkName) override;
IFACEMETHOD(Restart)(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds, _In_opt_ IWarningCallback* WarningCallback) override;

// IWSLCCompatContainer.
IFACEMETHOD(Start)(_In_ WSLCContainerStartFlags Flags) override;
Expand Down
57 changes: 57 additions & 0 deletions test/windows/WSLCTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6192,6 +6192,63 @@ class WSLCTests
}
}

WSLC_TEST_METHOD(ContainerRestart)
{
// Restart a running container.
{
WSLCContainerLauncher launcher("debian:latest", "test-restart-running", {"sleep", "99999"});
auto container = launcher.Launch(*m_defaultSession);
VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);

VERIFY_SUCCEEDED(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr));
VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
}

// Restart of an exited container just starts it.
{
WSLCContainerLauncher launcher("debian:latest", "test-restart-exited", {"sleep", "99999"});
auto container = launcher.Launch(*m_defaultSession);
VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGKILL, 0));
VERIFY_ARE_EQUAL(container.State(), WslcContainerStateExited);

VERIFY_SUCCEEDED(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr));
VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
}

// Restart of a created (never-started) container starts it.
{
WSLCContainerLauncher launcher("debian:latest", "test-restart-created", {"sleep", "99999"});
auto container = launcher.Create(*m_defaultSession);
VERIFY_ARE_EQUAL(container.State(), WslcContainerStateCreated);

VERIFY_SUCCEEDED(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr));
VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
}

// --rm containers are refused; Stop is not invoked so the container stays Running.
{
WSLCContainerLauncher launcher("debian:latest", "test-restart-autorm", {"sleep", "99999"});
launcher.SetContainerFlags(WSLCContainerFlagsRm);
auto container = launcher.Launch(*m_defaultSession);
VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);

auto id = container.Id();
VERIFY_ARE_EQUAL(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
ValidateCOMErrorMessage(std::format(L"Container '{}' was created with --rm and cannot be restarted.", id));
VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
}

// Deleted containers can't be restarted.
{
WSLCContainerLauncher launcher("debian:latest", "test-restart-deleted", {"sleep", "99999"});
auto container = launcher.Launch(*m_defaultSession);
VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGKILL, 0));
VERIFY_SUCCEEDED(container.Get().Delete(WSLCDeleteFlagsNone));

VERIFY_ARE_EQUAL(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr), RPC_E_DISCONNECTED);
Comment thread
beena352 marked this conversation as resolved.
}
}

WSLC_TEST_METHOD(OpenContainer)
{
auto expectOpen = [&](const char* Id, HRESULT expectedResult = S_OK) {
Expand Down
Loading