Skip to content

Commit 21c0691

Browse files
committed
fix(MSR-030): repair Windows consumer portability
1 parent db86514 commit 21c0691

9 files changed

Lines changed: 91 additions & 19 deletions

File tree

‎NEXT.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33

44
# NEXT.md
55

6+
> **2026-10-05 — MSR-030 Windows consumer qualification.** The CNA sample campaign's full
7+
> MinGW-w64 consumer build exposed four target-platform defects: Windows `SOCKET` values were
8+
> narrowed to signed `int` in `ServiceHost`, its listener started before `WSAStartup`, Socket's
9+
> async teardown used POSIX `SHUT_RDWR`, and MinGW emitted duplicate TLS initializers for the
10+
> non-trivial inline thread-local state in `Thread` and `CultureInfo`. The socket handles now keep
11+
> pointer width, Winsock initializes once, shutdown selects the platform constant, and non-trivial
12+
> TLS storage has one out-of-line definition. The seven-renderer CNA/cna-samples Windows cross-build
13+
> links the complete corpus. The warning-free two-job build and unchanged original Yacht SOAP
14+
> fixture pass **18,123/18,123 tests across 41 executables**, with zero failures/skips. This is
15+
> cross-build evidence, not native Windows runtime qualification.
16+
>
617
> **2026-09-30 — current entry point:** See
718
> [the human development handoff](docs/HumanDevelopmentHandoff-2026-09-30.md)
819
> for the dated current state, ticket database, audit scope, and human-owned

‎modules/globalization/include/System/Globalization/CultureInfo.hpp‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -675,11 +675,7 @@ class CultureInfo : public System::IFormatProvider {
675675
// Absent by default: a thread that has chosen nothing falls through to the process-wide default
676676
// and then to the invariant culture, which is .NET's chain
677677
// (`s_currentThreadCulture ?? s_DefaultThreadCurrentCulture ?? s_userDefaultCulture`).
678-
inline thread_local std::optional<CultureInfo> CultureInfo::currentCulture_{};
679-
inline thread_local std::optional<CultureInfo> CultureInfo::currentUICulture_{};
680678
inline CultureInfo::SharedCultureSlot CultureInfo::defaultThreadCurrentCulture_{};
681679
inline CultureInfo::SharedCultureSlot CultureInfo::defaultThreadCurrentUICulture_{};
682-
inline thread_local std::shared_ptr<const CultureInfo> CultureInfo::currentCultureHold_{};
683-
inline thread_local std::shared_ptr<const CultureInfo> CultureInfo::currentUICultureHold_{};
684680

685681
} // namespace System::Globalization
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) Robert Vokac and contributors
3+
// Portions based on .NET runtime API (MIT License, Copyright .NET Foundation and Contributors)
4+
#include "System/Globalization/CultureInfo.hpp"
5+
6+
namespace System::Globalization {
7+
8+
thread_local std::optional<CultureInfo> CultureInfo::currentCulture_{};
9+
thread_local std::optional<CultureInfo> CultureInfo::currentUICulture_{};
10+
thread_local std::shared_ptr<const CultureInfo> CultureInfo::currentCultureHold_{};
11+
thread_local std::shared_ptr<const CultureInfo> CultureInfo::currentUICultureHold_{};
12+
13+
} // namespace System::Globalization

‎modules/net-sockets/src/System/Net/Sockets/Socket.cpp‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ namespace {
144144
* The flag is added to **sends only**. A receive cannot raise SIGPIPE, and adding a flag a
145145
* platform does not define would not compile.
146146
*/
147+
#if !defined(_WIN32)
147148
int nativeSendFlags(SocketFlags flags) {
148149
#if defined(MSG_NOSIGNAL)
149150
return nativeSocketFlags(flags) | MSG_NOSIGNAL;
@@ -153,6 +154,7 @@ namespace {
153154
return nativeSocketFlags(flags);
154155
#endif
155156
}
157+
#endif
156158

157159
// Builds a native sockaddr for `ep` into `storage`, returning the address length.
158160
// Supports IPEndPoint (v4/v6) and UnixDomainSocketEndPoint.
@@ -335,7 +337,13 @@ void Socket::waitForAsyncOperations(bool discardDescriptor) noexcept {
335337
// be discarded. shutdown() is irreversible: using it to drain the source side of a move would
336338
// transfer a disabled socket. Source moves therefore stop AcceptAsync through its bounded
337339
// poll loop but let the other operations complete naturally (#2417).
338-
if (needsShutdown && fd_ >= 0) ::shutdown(static_cast<int>(fd_), SHUT_RDWR);
340+
if (needsShutdown && fd_ >= 0) {
341+
#if defined(_WIN32)
342+
::shutdown(toSk(fd_), SD_BOTH);
343+
#else
344+
::shutdown(static_cast<int>(fd_), SHUT_RDWR);
345+
#endif
346+
}
339347
#endif
340348
std::unique_lock<std::mutex> lock(asyncOps_->mutex);
341349
asyncOps_->idle.wait(lock, [this] { return asyncOps_->inFlight == 0; });

‎modules/service-model/include/System/ServiceModel/ServiceHost.hpp‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#pragma once
55

66
#include <atomic>
7+
#include <cstdint>
78
#include <functional>
89
#include <map>
910
#include <memory>
@@ -100,7 +101,7 @@ namespace System::ServiceModel {
100101
[[nodiscard]] const EndpointAddress& getBaseAddressProperty() const { return baseAddress_; }
101102

102103
private:
103-
void Listen();
104+
void Listen(std::uintptr_t listenerSocket);
104105
[[nodiscard]] std::string HandleRequest(const std::string& requestLine,
105106
const std::string& soapAction,
106107
const std::string& body);
@@ -115,7 +116,7 @@ namespace System::ServiceModel {
115116

116117
std::atomic<bool> open_{false};
117118
std::thread listener_;
118-
int listenSocket_ = -1;
119+
std::optional<std::uintptr_t> listenSocket_;
119120
std::mutex mutex_;
120121
};
121122

‎modules/service-model/src/System/ServiceModel/ServiceHost.cpp‎

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
# include <winsock2.h>
1515
# include <ws2tcpip.h>
1616
using socket_t = SOCKET;
17+
constexpr socket_t kInvalidSocket = INVALID_SOCKET;
1718
# define SM_CLOSE_SOCKET closesocket
1819
#else
1920
# include <arpa/inet.h>
2021
# include <netinet/in.h>
2122
# include <sys/socket.h>
2223
# include <unistd.h>
2324
using socket_t = int;
25+
constexpr socket_t kInvalidSocket = -1;
2426
# define SM_CLOSE_SOCKET ::close
2527
#endif
2628

@@ -32,6 +34,26 @@ namespace System::ServiceModel {
3234

3335
namespace {
3436

37+
bool IsInvalidSocket(socket_t socket)
38+
{
39+
return socket == kInvalidSocket;
40+
}
41+
42+
void InitializeSockets()
43+
{
44+
#if defined(_WIN32)
45+
static std::once_flag initialized;
46+
static int result = 0;
47+
std::call_once(initialized, [] {
48+
WSADATA data{};
49+
result = ::WSAStartup(MAKEWORD(2, 2), &data);
50+
});
51+
if (result != 0) {
52+
throw CommunicationException("ServiceHost: could not initialize Windows sockets.");
53+
}
54+
#endif
55+
}
56+
3557
std::string LocalName(const std::string& name)
3658
{
3759
const std::size_t colon = name.find(':');
@@ -185,8 +207,9 @@ void ServiceHost::Open()
185207
}
186208
}
187209

210+
InitializeSockets();
188211
const socket_t listener = ::socket(AF_INET, SOCK_STREAM, 0);
189-
if (listener < 0) {
212+
if (IsInvalidSocket(listener)) {
190213
throw CommunicationException("ServiceHost: could not create a listening socket.");
191214
}
192215

@@ -205,16 +228,17 @@ void ServiceHost::Open()
205228
throw CommunicationException("ServiceHost: could not listen on " + uri + ".");
206229
}
207230

208-
listenSocket_ = static_cast<int>(listener);
231+
listenSocket_ = static_cast<std::uintptr_t>(listener);
209232
open_ = true;
210-
listener_ = std::thread([this] { Listen(); });
233+
listener_ = std::thread([this, listenerSocket = *listenSocket_] { Listen(listenerSocket); });
211234
}
212235

213-
void ServiceHost::Listen()
236+
void ServiceHost::Listen(std::uintptr_t listenerSocket)
214237
{
238+
const socket_t listener = static_cast<socket_t>(listenerSocket);
215239
while (open_) {
216-
const socket_t connection = ::accept(static_cast<socket_t>(listenSocket_), nullptr, nullptr);
217-
if (connection < 0) {
240+
const socket_t connection = ::accept(listener, nullptr, nullptr);
241+
if (IsInvalidSocket(connection)) {
218242
if (!open_) {
219243
break;
220244
}
@@ -331,19 +355,20 @@ void ServiceHost::Close()
331355

332356
open_ = false;
333357

334-
if (listenSocket_ >= 0) {
358+
if (listenSocket_.has_value()) {
359+
const socket_t listener = static_cast<socket_t>(*listenSocket_);
335360
#if defined(_WIN32)
336-
::shutdown(static_cast<socket_t>(listenSocket_), SD_BOTH);
361+
::shutdown(listener, SD_BOTH);
337362
#else
338-
::shutdown(static_cast<socket_t>(listenSocket_), SHUT_RDWR);
363+
::shutdown(listener, SHUT_RDWR);
339364
#endif
340-
SM_CLOSE_SOCKET(static_cast<socket_t>(listenSocket_));
341-
listenSocket_ = -1;
365+
SM_CLOSE_SOCKET(listener);
342366
}
343367

344368
if (listener_.joinable()) {
345369
listener_.join();
346370
}
371+
listenSocket_.reset();
347372
}
348373

349374
} // namespace System::ServiceModel

‎modules/threading/include/System/Threading/Thread.hpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ namespace System::Threading {
6565
// thread receives a distinct ID from the same counter as wrapped threads (#1958).
6666
// Holding the shared_ptr here (rather than a raw Thread*) means CurrentThreadProxy never
6767
// dereferences the (possibly already-destroyed) Thread object itself.
68-
inline static thread_local std::shared_ptr<RunState> currentThreadState_;
68+
static thread_local std::shared_ptr<RunState> currentThreadState_;
6969

7070
// #1958 / SR-AUD-193. A thread NOT started through this class used to report managed id
7171
// 1 -- the main thread's -- so every external thread collided with it and with each

‎modules/threading/src/System/Threading/Thread.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace System::Threading {
1515

16+
thread_local std::shared_ptr<Thread::RunState> Thread::currentThreadState_;
17+
1618
intcs Thread::GetCurrentProcessorId() {
1719
#if defined(_WIN32)
1820
return static_cast<intcs>(GetCurrentProcessorNumber());

‎plan.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Sharp Runtime plan
22

3+
## 2026-10-05 — MSR-030 Windows consumer qualification
4+
5+
The authorized CNA multi-renderer sample campaign cross-linked the complete Windows sample corpus
6+
against sharp-runtime and exposed platform defects that ordinary single-translation-unit builds had
7+
not reached. `ServiceHost` now preserves the unsigned pointer-sized Winsock `SOCKET`, initializes
8+
Winsock once, and passes the listener value into its worker without narrowing. Socket async teardown
9+
uses `SD_BOTH` on Windows. `Thread` and `CultureInfo` retain their per-thread semantics while moving
10+
non-trivial TLS definitions out of public headers, avoiding MinGW's duplicate TLS initializer
11+
symbols in multi-TU applications.
12+
13+
The existing `build/` tree was reused. The full warning-free build ran at the repository's stricter
14+
two-job ceiling. The complete component gate used the unchanged Yacht SOAP fixture on a private
15+
loopback port and passed **18,123/18,123 tests across 41 executables**, zero failures and zero skips.
16+
The Windows seven-renderer CNA/cna-samples cross-build links completely; native Windows execution is
17+
still a separate qualification phase.
18+
319
## 2026-09-30 — human development handoff
420

521
The active handoff and current issue inventory are in

0 commit comments

Comments
 (0)