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
20 changes: 20 additions & 0 deletions src/windows/common/relay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,26 @@ void ScopedRelay::Sync()
}
}

void ScopedRelay::Sync(std::chrono::milliseconds Timeout)
{
// Drain to natural EOF within the timeout; otherwise cancel before joining.
if (!m_thread.joinable())
{
return;
}

// Keep the wait bounded and below INFINITE.
const DWORD timeoutMs =
Timeout.count() <= 0 ? 0 : static_cast<DWORD>(std::min<long long>(Timeout.count(), static_cast<long long>(INFINITE) - 1));

if (!m_completed.wait(timeoutMs))
{
m_exitEvent.SetEvent();
}

m_thread.join();
}

ScopedRelay::~ScopedRelay()
{
try
Expand Down
12 changes: 11 additions & 1 deletion src/windows/common/relay.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ Module Name:
#pragma once

#include <winsock2.h>
#include <chrono>
#include "ConsoleState.h"
#include "HandleIO.h"

namespace wsl::windows::common::relay {

using namespace wsl::windows::common::io;

// Default cap for bounded relay drains.
constexpr auto c_relayDrainTimeout = std::chrono::seconds{60};

std::thread CreateThread(_In_ HANDLE InputHandle, _In_ HANDLE OutputHandle, _In_opt_ HANDLE ExitHandle = nullptr, _In_ size_t BufferSize = LX_RELAY_BUFFER_SIZE);

std::thread CreateThread(_In_ wil::unique_handle&& InputHandle, _In_ HANDLE OutputHandle, _In_opt_ HANDLE ExitHandle = nullptr, _In_ size_t BufferSize = LX_RELAY_BUFFER_SIZE);
Expand Down Expand Up @@ -98,6 +102,8 @@ class ScopedRelay
m_onDestroy(std::move(OnDestroy))
{
m_thread = std::thread{[this, Input = std::move(Input), Output = std::move(Output), BufferSize = BufferSize]() {
// Signal completion for bounded Sync().
auto signalCompleted = wil::scope_exit([this]() { m_completed.SetEvent(); });
try
{
Run(GetUnderlyingHandle(Input), GetUnderlyingHandle(Output), BufferSize);
Expand All @@ -108,7 +114,7 @@ class ScopedRelay

~ScopedRelay();

ScopedRelay(ScopedRelay&& other) = default;
ScopedRelay(ScopedRelay&&) = delete;
ScopedRelay(const ScopedRelay&) = delete;

ScopedRelay& operator=(const ScopedRelay&) = delete;
Expand All @@ -119,6 +125,9 @@ class ScopedRelay
// the content has been flushed before exiting.
void Sync();

// Blocks until EOF, or cancels after Timeout.
void Sync(std::chrono::milliseconds Timeout);

private:
template <typename THandle>
static HANDLE GetUnderlyingHandle(THandle& handle)
Expand Down Expand Up @@ -150,6 +159,7 @@ class ScopedRelay

std::thread m_thread;
wil::unique_event m_exitEvent{wil::EventOptions::ManualReset};
wil::unique_event m_completed{wil::EventOptions::ManualReset};
std::function<void()> m_onDestroy;
};

Expand Down
30 changes: 19 additions & 11 deletions src/windows/common/svccomm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,8 @@ wsl::windows::common::SvcComm::ExportDistribution(_In_opt_ LPCGUID DistroGuid, _
}

stdErrWrite.reset();
stdErrRelay.Sync();
// Client relay is EOF-bounded; timeout is a defensive cap.
stdErrRelay.Sync(relay::c_relayDrainTimeout);

RETURN_HR(result);
}
Expand Down Expand Up @@ -421,19 +422,23 @@ wsl::windows::common::SvcComm::LaunchProcess(
// Create stdin, stdout and stderr worker threads.
//

std::thread StdOutWorker;
std::thread StdErrWorker;
// StdOut/StdErr drain to EOF, bounded in case a guest socket wedges.
std::optional<relay::ScopedRelay> StdOutWorker;
std::optional<relay::ScopedRelay> StdErrWorker;
auto ExitEvent = wil::unique_event(wil::EventOptions::ManualReset);
auto outWorkerExit = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&StdOutWorker, &StdErrWorker, &ExitEvent] {
// Signal the detached stdin relay (it uses ExitEvent as its ExitHandle).
ExitEvent.SetEvent();
if (StdOutWorker.joinable())

// Drain stdout/stderr, bounded in case a guest socket wedges.
if (StdOutWorker.has_value())
{
StdOutWorker.join();
StdOutWorker->Sync(relay::c_relayDrainTimeout);
}

if (StdErrWorker.joinable())
if (StdErrWorker.has_value())
{
StdErrWorker.join();
StdErrWorker->Sync(relay::c_relayDrainTimeout);
}
});

Expand Down Expand Up @@ -476,9 +481,10 @@ wsl::windows::common::SvcComm::LaunchProcess(
}

auto StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
StdOutWorker = relay::CreateThread(std::move(StdOutSocket), IS_VALID_HANDLE(StdOut) ? StdOut : nullptr);
// Relay guest stdout with bounded teardown drain.
StdOutWorker.emplace(std::move(StdOutSocket), IS_VALID_HANDLE(StdOut) ? StdOut : nullptr);
auto StdErr = GetStdHandle(STD_ERROR_HANDLE);
StdErrWorker = relay::CreateThread(std::move(StdErrSocket), IS_VALID_HANDLE(StdErr) ? StdErr : nullptr);
StdErrWorker.emplace(std::move(StdErrSocket), IS_VALID_HANDLE(StdErr) ? StdErr : nullptr);

//
// Spawn wslhost to handle interop requests from processes that have
Expand Down Expand Up @@ -612,7 +618,8 @@ std::pair<GUID, wil::unique_cotaskmem_string> wsl::windows::common::SvcComm::Reg
}

stdErrWrite.reset();
stdErrRelay.Sync();
// Client relay is EOF-bounded; timeout is a defensive cap.
stdErrRelay.Sync(relay::c_relayDrainTimeout);

THROW_IF_FAILED(Result);

Expand Down Expand Up @@ -648,7 +655,8 @@ wsl::windows::common::SvcComm::ResizeDistribution(_In_ LPCGUID DistroGuid, _In_
const auto result = m_userSession->ResizeDistribution(DistroGuid, outputWrite.get(), NewSize, context.OutError());

outputWrite.reset();
outputRelay.Sync();
// Client relay is EOF-bounded; timeout is a defensive cap.
outputRelay.Sync(relay::c_relayDrainTimeout);

RETURN_HR(result);
}
Expand Down
8 changes: 4 additions & 4 deletions src/windows/service/exe/LxssUserSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1156,8 +1156,8 @@ HRESULT LxssUserSessionImpl::ExportDistribution(_In_opt_ LPCGUID DistroGuid, _In
ULONG exitCode = 1;
vmContext.instance->GetInitPort()->Receive(&exitCode, sizeof(exitCode), clientProcess.get());

// Flush any pending IO on the error relay before exiting.
stdErrRelay.Sync();
// Drain error output, bounded in case the guest socket wedges.
stdErrRelay.Sync(wsl::windows::common::relay::c_relayDrainTimeout);

THROW_HR_IF(WSL_E_EXPORT_FAILED, (exitCode != 0));
}
Expand Down Expand Up @@ -1603,10 +1603,10 @@ HRESULT LxssUserSessionImpl::RegisterDistribution(
gsl::span<gsl::byte> span;
const auto& message = channel->GetChannel().ReceiveMessage<LX_MINI_INIT_IMPORT_RESULT>(&span);

// Flush any pending IO on the error relay before exiting.
// Drain error output, bounded in case the guest socket wedges.
if (errorRelay.has_value())
{
errorRelay->Sync();
errorRelay->Sync(wsl::windows::common::relay::c_relayDrainTimeout);
}

// Process the import result message.
Expand Down
3 changes: 2 additions & 1 deletion test/windows/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ set(SOURCES
WSLCTests.cpp
WslcSdkTests.cpp
WslcSdkWinRtTests.cpp
WindowsUpdateTests.cpp)
WindowsUpdateTests.cpp
ScopedRelayUnitTests.cpp)

set(HEADERS
Common.h
Expand Down
Loading