Skip to content

Commit a614779

Browse files
authored
Fix port conflict error message and cleanup on failure (#40102)
1 parent cd05f5c commit a614779

6 files changed

Lines changed: 65 additions & 13 deletions

File tree

localization/strings/en-US/Resources.resw

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,6 +2135,10 @@ For privacy information about this product please visit https://aka.ms/privacy.<
21352135
<value>Failed to create volume '{}': {}</value>
21362136
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
21372137
</data>
2138+
<data name = "MessageWslcPortInUse" xml:space = "preserve" >
2139+
<value>Port {} is already in use, cannot start container {}</value>
2140+
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2141+
</data>
21382142
<data name = "MessageWslcBothDockerAndContainerFileFound" xml:space = "preserve" >
21392143
<value>Both Dockerfile and Containerfile found. Use -f to select the file to use</value>
21402144
<comment>{FixedPlaceholder="Dockerfile"}{FixedPlaceholder="Containerfile"}{FixedPlaceholder="-f"}Command line arguments, file names and string inserts should not be translated</comment>

src/windows/wslc/services/ContainerService.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,14 +291,16 @@ int ContainerService::Run(Session& session, const std::string& image, ContainerO
291291
{
292292
// Create the container
293293
auto runningContainer = CreateInternal(session, image, runOptions);
294-
runningContainer.SetDeleteOnClose(false);
295294
auto& container = runningContainer.Get();
296295

297296
// Start the created container
298297
WSLCContainerStartFlags startFlags{};
299298
WI_SetFlagIf(startFlags, WSLCContainerStartFlagsAttach, !runOptions.Detach);
300299
THROW_IF_FAILED(container.Start(startFlags, nullptr)); // TODO: Error message, detach keys
301300

301+
// Disable auto-delete only after successful start
302+
runningContainer.SetDeleteOnClose(false);
303+
302304
// Handle attach if requested
303305
if (WI_IsFlagSet(startFlags, WSLCContainerStartFlagsAttach))
304306
{

src/windows/wslcsession/WSLCContainer.cpp

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,16 @@ std::string ExtractContainerName(const std::vector<std::string>& names, const st
231231
return CleanContainerName(names[0]);
232232
}
233233

234+
std::string FormatPortEndpoint(const ContainerPortMapping& portMapping)
235+
{
236+
auto addr = portMapping.VmMapping.BindingAddressString();
237+
return std::format(
238+
"{}:{}/{}",
239+
portMapping.VmMapping.IsIPv6() ? std::format("[{}]", addr) : addr,
240+
portMapping.VmMapping.HostPort(),
241+
portMapping.ProtocolString());
242+
}
243+
234244
WSLCContainerMetadataV1 ParseContainerMetadata(const std::string& json)
235245
{
236246
auto wrapper = wsl::shared::FromJson<WSLCContainerMetadata>(json.c_str());
@@ -1497,20 +1507,31 @@ void WSLCContainerImpl::MapPorts()
14971507
auto allocatedPort =
14981508
m_virtualMachine.TryAllocatePort(e.ContainerPort, e.VmMapping.BindAddress.si_family, e.VmMapping.Protocol);
14991509

1500-
THROW_HR_IF_MSG(
1510+
THROW_HR_WITH_USER_ERROR_IF(
15011511
HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS),
1502-
!allocatedPort,
1503-
"Port %hu is in use, cannot start container %hs",
1504-
e.ContainerPort,
1505-
m_id.c_str());
1512+
wsl::shared::Localization::MessageWslcPortInUse(FormatPortEndpoint(e), m_id),
1513+
!allocatedPort);
15061514

15071515
e.VmMapping.AssignVmPort(allocatedPort);
15081516

15091517
allocatedPorts.emplace(e.ContainerPort, allocatedPort);
15101518
}
15111519
}
15121520

1513-
m_virtualMachine.MapPort(e.VmMapping);
1521+
try
1522+
{
1523+
m_virtualMachine.MapPort(e.VmMapping);
1524+
}
1525+
catch (...)
1526+
{
1527+
auto result = wil::ResultFromCaughtException();
1528+
if (result == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS) || result == HRESULT_FROM_WIN32(WSAEADDRINUSE))
1529+
{
1530+
THROW_HR_WITH_USER_ERROR(
1531+
HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), wsl::shared::Localization::MessageWslcPortInUse(FormatPortEndpoint(e), m_id));
1532+
}
1533+
throw;
1534+
}
15141535
}
15151536
}
15161537

src/windows/wslcsession/WSLCVirtualMachine.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ bool VMPortMapping::IsLocalhost() const
167167
}
168168
}
169169

170+
bool VMPortMapping::IsIPv6() const
171+
{
172+
return BindAddress.si_family == AF_INET6;
173+
}
174+
170175
uint16_t VMPortMapping::HostPort() const
171176
{
172177
if (BindAddress.si_family == AF_INET6)

src/windows/wslcsession/WSLCVirtualMachine.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ struct VMPortMapping
8686
void Unmap();
8787
void Release();
8888
bool IsLocalhost() const;
89+
bool IsIPv6() const;
8990
std::string BindingAddressString() const;
9091
void Attach(WSLCVirtualMachine& Vm);
9192
void Detach();

test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class WSLCE2EContainerRunTests
4040
TEST_CLASS_CLEANUP(ClassCleanup)
4141
{
4242
EnsureContainerDoesNotExist(WslcContainerName);
43+
EnsureContainerDoesNotExist(WslcContainerName2);
4344
EnsureImageIsDeleted(DebianImage);
4445
EnsureImageIsDeleted(PythonImage);
4546

@@ -54,6 +55,7 @@ class WSLCE2EContainerRunTests
5455
TEST_METHOD_SETUP(TestMethodSetup)
5556
{
5657
EnsureContainerDoesNotExist(WslcContainerName);
58+
EnsureContainerDoesNotExist(WslcContainerName2);
5759

5860
EnvTestFile1 = wsl::windows::common::filesystem::GetTempFilename();
5961
EnvTestFile2 = wsl::windows::common::filesystem::GetTempFilename();
@@ -384,9 +386,6 @@ class WSLCE2EContainerRunTests
384386

385387
WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortAlreadyInUse)
386388
{
387-
// Bug: https://github.com/microsoft/WSL/issues/14448
388-
SKIP_TEST_NOT_IMPL();
389-
390389
// Start a container with a simple server listening on a port
391390
auto result1 = RunWslc(std::format(
392391
L"container run -d --name {} -p {}:{} {} {}",
@@ -397,9 +396,28 @@ class WSLCE2EContainerRunTests
397396
GetPythonHttpServerScript(ContainerTestPort)));
398397
result1.Verify({.Stderr = L"", .ExitCode = 0});
399398

400-
// Attempt to start another container mapping the same host port
401-
auto result2 = RunWslc(std::format(L"container run -p {}:{} {}", HostTestPort1, ContainerTestPort, DebianImage.NameAndTag()));
402-
result2.Verify({.ExitCode = 1});
399+
// Create a second container mapping the same host port to validate the full error message
400+
auto createResult =
401+
RunWslc(std::format(L"container create -p {}:{} {}", HostTestPort1, ContainerTestPort, DebianImage.NameAndTag()));
402+
createResult.Verify({.Stderr = L"", .ExitCode = 0});
403+
auto containerId = createResult.GetStdoutOneLine();
404+
405+
// Attempt to start — should fail with port conflict
406+
auto startResult = RunWslc(std::format(L"container start {}", containerId));
407+
startResult.Verify(
408+
{.Stderr = std::format(
409+
L"Port 127.0.0.1:{}/tcp is already in use, cannot start container {}\r\nError code: ERROR_ALREADY_EXISTS\r\n", HostTestPort1, containerId),
410+
.ExitCode = 1});
411+
412+
// Clean up the created container
413+
RunWslc(std::format(L"container rm {}", containerId)).Verify({.Stderr = L"", .ExitCode = 0});
414+
415+
// Verify 'container run' auto-cleans up on port conflict (no ghost container)
416+
auto runResult = RunWslc(std::format(
417+
L"container run --name {} -p {}:{} {}", WslcContainerName2, HostTestPort1, ContainerTestPort, DebianImage.NameAndTag()));
418+
runResult.Verify({.ExitCode = 1});
419+
420+
VerifyContainerIsNotListed(WslcContainerName2);
403421
}
404422

405423
// https://github.com/microsoft/WSL/issues/14433
@@ -538,6 +556,7 @@ class WSLCE2EContainerRunTests
538556
private:
539557
// Test container name
540558
const std::wstring WslcContainerName = L"wslc-test-container";
559+
const std::wstring WslcContainerName2 = L"wslc-test-container-2";
541560

542561
// Test environment variables
543562
const std::wstring HostEnvVariableName = L"WSLC_TEST_HOST_ENV";

0 commit comments

Comments
 (0)