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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/windows/common/WSLCUserSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ static constexpr std::string_view s_DefaultSettingsTemplate =
" # used without an explicit address (default: 127.0.0.1)\n"
" # defaultBindingAddress: default\n"
"\n"
" # Seconds an idle session VM stays running before it is torn down (default: 30)\n"
" # idleTimeout: default\n"
"\n"
"# Credential storage backend: \"wincred\" or \"file\" (default: wincred)\n"
"# credentialStore: wincred\n";

Expand Down Expand Up @@ -147,6 +150,11 @@ namespace details {
return value;
}

WSLC_VALIDATE_SETTING(SessionIdleTimeout)
{
return value > 0 ? std::optional{value} : std::nullopt;
}

WSLC_VALIDATE_SETTING(CredentialStore)
{
if (value == "wincred")
Expand Down
2 changes: 2 additions & 0 deletions src/windows/common/WSLCUserSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ enum class Setting : size_t
CredentialStore,
SessionPortRelay,
SessionDefaultBindingAddress,
SessionIdleTimeout,

Max
};
Expand Down Expand Up @@ -99,6 +100,7 @@ namespace details {
DEFINE_SETTING_MAPPING(CredentialStore, std::string, CredentialStoreType, CredentialStoreType::WinCred, "credentialStore")
DEFINE_SETTING_MAPPING(SessionPortRelay, std::string, PortRelayType, PortRelayType::VirtioNet, "experimental.portRelay")
DEFINE_SETTING_MAPPING(SessionDefaultBindingAddress, std::string, std::string, std::string{}, "session.defaultBindingAddress")
DEFINE_SETTING_MAPPING(SessionIdleTimeout, uint32_t, uint32_t, 30, "session.idleTimeout")

#undef DEFINE_SETTING_MAPPING
// clang-format on
Expand Down
4 changes: 3 additions & 1 deletion src/windows/service/exe/HcsVirtualMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,9 @@ HcsVirtualMachine::HcsVirtualMachine(_In_ const WSLCSessionSettings* Settings)

HcsVirtualMachine::~HcsVirtualMachine()
{
std::lock_guard lock(m_lock);
// Do not hold m_lock: waiting on m_vmExitEvent and closing the compute system below both block
// on in-flight HCS exit/crash callbacks, which may themselves need m_lock. OnExit() is lock-free,
// and closing the compute system drains all callbacks, so the rest of teardown needs no lock.

// Wait up to 5 seconds for the VM to terminate gracefully.
bool forceTerminate = false;
Expand Down
5 changes: 3 additions & 2 deletions src/windows/service/exe/WSLCSessionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ struct SessionSettings
Settings.MemoryMb = memoryMb > 0 ? memoryMb : SessionSettings::DefaultMemoryMb();
Settings.MaximumStorageSizeMb = userSettings.Get<settings::Setting::SessionStorageSizeMb>();
Settings.BootTimeoutMs = wsl::windows::wslc::DefaultBootTimeoutMs;
Settings.IdleTimeoutSec = userSettings.Get<settings::Setting::SessionIdleTimeout>();
Settings.NetworkingMode = userSettings.Get<settings::Setting::SessionNetworkingMode>();

// TODO: Add a config setting to opt-out of GPU support.
Expand Down Expand Up @@ -277,8 +278,7 @@ void WSLCSessionManagerImpl::CreateSession(
g_pluginManager, sessionId, creatorPid, std::wstring(resolvedDisplayName), wil::shared_handle(sharedToken), std::vector<BYTE>(storedSid));

// Create the VM factory in the SYSTEM service (privileged). The per-user session
// uses it to create the VM. Funneling VM creation through a factory lets the session
// own when VMs are created, rather than having one handed to it up front.
// uses it to create VMs on demand and recreate them after idle-termination.
auto vmFactory = Microsoft::WRL::Make<WSLCVirtualMachineFactory>(Settings);

// Launch per-user COM server factory and add it to a fresh per-session job object for crash cleanup.
Expand Down Expand Up @@ -465,6 +465,7 @@ WSLCSessionInitSettings WSLCSessionManagerImpl::CreateSessionSettings(
sessionSettings.RootVhdTypeOverride = Settings->RootVhdTypeOverride;
sessionSettings.StorageFlags = Settings->StorageFlags;
sessionSettings.SwapSizeMb = Settings->MemoryMb;
sessionSettings.IdleTimeoutSec = Settings->IdleTimeoutSec;
return sessionSettings;
}

Expand Down
10 changes: 10 additions & 0 deletions src/windows/service/inc/wslc.idl
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ typedef struct _WSLCSessionSettings {
WSLCFeatureFlags FeatureFlags;
WSLCHandle DmesgOutput;
WSLCSessionStorageFlags StorageFlags;
ULONG IdleTimeoutSec;

// Below options are used for debugging purposes only.
[unique] LPCWSTR RootVhdOverride;
Expand Down Expand Up @@ -578,6 +579,7 @@ typedef struct _WSLCSessionInitSettings
WSLCNetworkingMode NetworkingMode;
WSLCFeatureFlags FeatureFlags;
[unique] LPCSTR RootVhdTypeOverride;
ULONG IdleTimeoutSec;
} WSLCSessionInitSettings;

[
Expand Down Expand Up @@ -616,6 +618,14 @@ interface IWSLCSession : IUnknown
// Container management.
HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCContainer** Container);
HRESULT OpenContainer([in, ref] LPCSTR Id, [out] IWSLCContainer** Container);

// Keeps the VM alive for the duration of a client-side container operation. The CLI performs
// each mutation as two round-trips (OpenContainer followed by the operation) and may stream
// output afterwards. With on-demand VM idle-termination the VM could otherwise tear down
// between those calls, disconnecting the container wrapper and failing the second call with
// RPC_E_DISCONNECTED. The client holds the returned token for the whole operation; releasing
// it (or the client exiting) lets the VM idle-terminate again.
HRESULT BeginContainerOperation([out] IUnknown** Operation);
HRESULT ListContainers([in, unique] const WSLCListContainersOptions* Options,[out, size_is(, *Count)] WSLCContainerEntry** Containers,[out] ULONG* Count, [out, size_is(, *PortsCount)] WSLCContainerPortMapping** Ports, [out] ULONG* PortsCount);
HRESULT PruneContainers([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out] WSLCPruneContainersResults* Result);

Expand Down
11 changes: 11 additions & 0 deletions src/windows/wslc/services/ContainerService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ std::wstring ContainerService::FormatRelativeTime(ULONGLONG timestamp)

int ContainerService::Attach(Session& session, const std::string& id)
{
auto operation = session.BeginContainerOperation();
Comment thread
benhillis marked this conversation as resolved.
Comment thread
benhillis marked this conversation as resolved.
wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));

Expand Down Expand Up @@ -457,6 +458,7 @@ CreateContainerResult ContainerService::Create(Session& session, const std::stri

int ContainerService::Start(Session& session, const std::string& id, bool attach)
{
auto operation = session.BeginContainerOperation();
Comment thread
benhillis marked this conversation as resolved.
Comment thread
benhillis marked this conversation as resolved.
wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
WSLCContainerStartFlags flags = attach ? WSLCContainerStartFlagsAttach : WSLCContainerStartFlagsNone;
Expand Down Expand Up @@ -487,20 +489,23 @@ int ContainerService::Start(Session& session, const std::string& id, bool attach

void ContainerService::Stop(Session& session, const std::string& id, StopContainerOptions options)
{
auto operation = session.BeginContainerOperation();
Comment thread
benhillis marked this conversation as resolved.
Comment thread
benhillis marked this conversation as resolved.
wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
THROW_IF_FAILED_EXCEPT(container->Stop(options.Signal, options.Timeout), WSLC_E_CONTAINER_NOT_RUNNING);
}

void ContainerService::Kill(Session& session, const std::string& id, WSLCSignal signal)
{
auto operation = session.BeginContainerOperation();
Comment thread
benhillis marked this conversation as resolved.
Comment thread
benhillis marked this conversation as resolved.
wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
THROW_IF_FAILED(container->Kill(signal));
}

void ContainerService::Delete(Session& session, const std::string& id, bool force)
{
auto operation = session.BeginContainerOperation();
Comment thread
benhillis marked this conversation as resolved.
Comment thread
benhillis marked this conversation as resolved.
wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
THROW_IF_FAILED(container->Delete(force ? WSLCDeleteFlagsForce : WSLCDeleteFlagsNone));
Expand Down Expand Up @@ -555,6 +560,7 @@ std::vector<ContainerInformation> ContainerService::List(

int ContainerService::Exec(Session& session, const std::string& id, ContainerOptions options)
{
auto operation = session.BeginContainerOperation();
Comment thread
benhillis marked this conversation as resolved.
Comment thread
benhillis marked this conversation as resolved.
wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));

Expand Down Expand Up @@ -586,6 +592,7 @@ int ContainerService::Exec(Session& session, const std::string& id, ContainerOpt

InspectContainer ContainerService::Inspect(Session& session, const std::string& id)
{
auto operation = session.BeginContainerOperation();
Comment thread
benhillis marked this conversation as resolved.
Comment thread
benhillis marked this conversation as resolved.
wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
wil::unique_cotaskmem_ansistring output;
Expand All @@ -604,6 +611,8 @@ void ContainerService::Export(Session& session, const std::string& id, const std

void ContainerService::Export(Session& session, const std::string& id, HANDLE outputHandle)
{
auto operation = session.BeginContainerOperation();

wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));

Expand All @@ -615,6 +624,7 @@ void ContainerService::Export(Session& session, const std::string& id, HANDLE ou

void ContainerService::Logs(Session& session, const std::string& id, bool follow, bool timestamps, ULONGLONG since, ULONGLONG until, ULONGLONG tail)
{
auto operation = session.BeginContainerOperation();
Comment thread
benhillis marked this conversation as resolved.
Comment thread
benhillis marked this conversation as resolved.
wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));

Expand Down Expand Up @@ -642,6 +652,7 @@ void ContainerService::Logs(Session& session, const std::string& id, bool follow

wsl::windows::common::docker_schema::ContainerStats ContainerService::Stats(Session& session, const std::string& id)
{
auto operation = session.BeginContainerOperation();
Comment thread
benhillis marked this conversation as resolved.
Comment thread
benhillis marked this conversation as resolved.
wil::com_ptr<IWSLCContainer> container;
THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
wil::unique_cotaskmem_ansistring output;
Expand Down
10 changes: 10 additions & 0 deletions src/windows/wslc/services/SessionModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ struct Session
return m_session.get();
}

// Acquires an activity token that keeps the VM alive for the duration of a client-side
// container operation (resolve + operate, plus any streamed output). Hold the returned
// pointer for the whole operation; releasing it lets the VM idle-terminate again.
[[nodiscard]] wil::com_ptr<IUnknown> BeginContainerOperation() const
{
wil::com_ptr<IUnknown> operation;
THROW_IF_FAILED(m_session->BeginContainerOperation(&operation));
return operation;
}

private:
wil::com_ptr<IWSLCSession> m_session;
};
Expand Down
4 changes: 3 additions & 1 deletion src/windows/wslc/services/SessionService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ Session SessionService::OpenOrCreateDefaultSession()
{
auto manager = CreateSessionManager();

// Null Settings = default session with server-determined name and settings.
// Null Settings = default session with server-determined name and settings. The warning callback
// is consumed during CreateSession (session initialization); it is not retained afterwards.
wil::com_ptr<IWSLCSession> session;
auto warningCallback = Microsoft::WRL::Make<WarningCallback>();
THROW_IF_FAILED(manager->CreateSession(nullptr, WSLCSessionFlagsNone, warningCallback.Get(), &session));
wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());

return Session(std::move(session));
}

Expand Down
2 changes: 2 additions & 0 deletions src/windows/wslcsession/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ set(SOURCES

# Session and container implementation
WSLCSession.cpp
WSLCSessionRuntime.cpp
WSLCContainer.cpp
WSLCVirtualMachine.cpp

Expand Down Expand Up @@ -44,6 +45,7 @@ set(HEADERS
WSLCProcessControl.h
WSLCProcessIO.h
WSLCSession.h
WSLCSessionRuntime.h
WSLCSessionFactory.h
WSLCSessionReference.h
WSLCVirtualMachine.h
Expand Down
9 changes: 9 additions & 0 deletions src/windows/wslcsession/IORelay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,20 @@ void IORelay::Stop()
}
}

bool IORelay::IsRelayThread() const noexcept
{
return m_thread.get_id() == std::this_thread::get_id();
}

void IORelay::Run()
try
{
common::wslutil::SetThreadDescription(L"IORelay");

// Handle callbacks dispatched from this thread (e.g. unexpected VM exit) can tear the VM down,
// releasing cross-process COM proxies, so join the process MTA to avoid RPC_E_WRONG_THREAD.
const auto coInit = wil::CoInitializeEx(COINIT_MULTITHREADED);

windows::common::io::MultiHandleWait io;

// N.B. All the IO must happen on the thread.
Expand Down
6 changes: 6 additions & 0 deletions src/windows/wslcsession/IORelay.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ class IORelay

void Stop();

// Returns true if the calling thread is the IORelay's own worker thread (i.e. the call
// is being made from a handle callback). Destroying the IORelay from this thread would
// join the thread with itself and call std::terminate(), so callers that may run on the
// relay thread must check this before destroying the object.
bool IsRelayThread() const noexcept;

private:
void Start();
void Run();
Expand Down
Loading