Skip to content

Commit bc657d4

Browse files
committed
FEATURE-ci-v3-runtime-green: bind Windows allocation faults to static proof
1 parent c1865c7 commit bc657d4

7 files changed

Lines changed: 114 additions & 4 deletions

File tree

‎ChangeLog‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
Version 3.0.0 - 2026-10-10
2+
Run Windows allocation-failure injection against the installed static
3+
library and require ordinary readiness cases in both linkage lanes.
24
Keep PSK cancellation lookup held until owner release and avoid artificial
35
per-fragment scheduling delays in the TLS test transport.
46
Avoid Windows DLL shutdown hangs by retaining the default timer service

‎scripts/lib/package_integration.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ def run_suites(inventory, host, mode, linkage, directory, prefix, source, build,
3838
receipt = dict(name=name, tls=mode, linkage=linkage, execution=definition.get('execution', 'installed-library'))
3939
try:
4040
flags = cflags + ['-DHTTPSERVER_COMPILATION', '-I' + str(source / 'src'), '-I' + str(source / 'test')]
41+
if host == 'Windows' and linkage == 'shared' and definition.get('windows_static_cases'):
42+
# An executable's operator new cannot interpose PE DLL imports.
43+
# The installed static lane retains every fault-injection case.
44+
flags += ['-DLHS_TEST_DLL_LINKAGE']
45+
receipt['windows_static_cases'] = definition['windows_static_cases']
4146
for macro, value in (('TLS_TEST_DIR', source / 'test'), ('PARITY_TRANSCRIPT_DIR', source / 'test/parity/transcripts'),
4247
('PARITY_DATA_ROOT', source / 'test'), ('HTTP2_CORPUS_DIR', source / 'test/conformance/http2')):
4348
flags += ['-D' + macro + '="' + value.as_posix() + '"']
@@ -61,9 +66,7 @@ def run_suites(inventory, host, mode, linkage, directory, prefix, source, build,
6166
started = time.monotonic()
6267
output = command([str(executable)], source / 'test', env, timeout=180, allow_failure=True)
6368
receipt.update(matrix.suite_result(output, command.records[-1]['exit']))
64-
missing = set(definition.get('required_cases', [])) - set(receipt['case_names'])
65-
if missing:
66-
raise ValueError('required suite cases absent: ' + ', '.join(sorted(missing)))
69+
matrix.check_suite_cases(definition, host, linkage, receipt['case_names'])
6770
receipt.update(duration=time.monotonic() - started, log=command.records[-1]['log'])
6871
if name in ('tls_io_loopback', 'native_http1_tls_parity'):
6972
matrix.check_tls_witnesses(output, inventory['platforms'][host])

‎scripts/lib/package_matrix.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,18 @@ def suites(inventory, platform, tls):
150150
return {name: inventory['suites'][name] for name in names}
151151

152152

153+
def check_suite_cases(definition, host, linkage, observed):
154+
required = set(definition.get('required_cases', []))
155+
windows_static = set(definition.get('windows_static_cases', []))
156+
if not windows_static <= required:
157+
raise ValueError('Windows static cases must also be required suite cases')
158+
if host == 'Windows' and linkage == 'shared':
159+
required -= windows_static
160+
missing = required - set(observed)
161+
if missing:
162+
raise ValueError('required suite cases absent: ' + ', '.join(sorted(missing)))
163+
164+
153165
def suite_result(output, status):
154166
output = output.replace('\r\n', '\n')
155167
tests = re.findall(r'^(\d+) tests executed$', output, re.M)

‎scripts/test-v3-installed-consumer.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,5 +1580,32 @@ def test_nonzero_shutdown_is_failure(self):
15801580
self.assertEqual(8, json.loads(receipt.read_text())['exit'])
15811581

15821582

1583+
class SuiteCaseCoverageTest(unittest.TestCase):
1584+
def setUp(self):
1585+
self.definition = dict(required_cases=['ordinary', 'allocation_failure'],
1586+
windows_static_cases=['allocation_failure'])
1587+
1588+
def test_windows_dll_requires_all_ordinary_cases(self):
1589+
matrix.check_suite_cases(self.definition, 'Windows', 'shared', ['ordinary'])
1590+
with self.assertRaisesRegex(ValueError, 'ordinary'):
1591+
matrix.check_suite_cases(self.definition, 'Windows', 'shared', [])
1592+
1593+
def test_static_proof_requires_all_allocation_failure_cases(self):
1594+
for host in ('Windows', 'Linux', 'Darwin', 'FreeBSD'):
1595+
with self.subTest(host=host), self.assertRaisesRegex(ValueError, 'allocation_failure'):
1596+
matrix.check_suite_cases(self.definition, host, 'static', ['ordinary'])
1597+
matrix.check_suite_cases(self.definition, host, 'static', ['ordinary', 'allocation_failure'])
1598+
1599+
def test_other_shared_platforms_retain_allocation_failure_coverage(self):
1600+
for host in ('Linux', 'Darwin', 'FreeBSD'):
1601+
with self.subTest(host=host), self.assertRaisesRegex(ValueError, 'allocation_failure'):
1602+
matrix.check_suite_cases(self.definition, host, 'shared', ['ordinary'])
1603+
1604+
def test_windows_static_cases_must_also_be_required(self):
1605+
self.definition['required_cases'] = ['ordinary']
1606+
with self.assertRaisesRegex(ValueError, 'static.*required'):
1607+
matrix.check_suite_cases(self.definition, 'Windows', 'static', ['ordinary'])
1608+
1609+
15831610
if __name__ == '__main__':
15841611
unittest.main()

‎specs/tasks/FEATURE-ci-v3-runtime-green.md‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# FEATURE-ci-v3-runtime-green
22

3-
Status: Stages 1 and 3 implemented with focused proof; complete CI and remaining supported runtime repairs are pending. Groundwork validation is explicitly prohibited until FULL CI IS GREEN.
3+
Status: Stages 1, 3, 4 and 5 implemented with focused proof; complete CI and remaining supported runtime repairs are pending. Groundwork validation is explicitly prohibited until FULL CI IS GREEN.
44
Base: `v3` at `8f3083977f49706e66cefbb3ee8b6f6685469719`.
55
Working branch: `task/FEATURE-ci-v3-runtime-green`. User requested integration into `v3` before full CI completion; retain incomplete verification explicitly.
66
Plan: primary checkout `.groundwork-plans/FEATURE-ci-v3-runtime-green-plan.md`.
@@ -91,3 +91,24 @@ The diagnostic Windows four-test program failed one of 52 checks in 0.741 second
9191
Fixture root cause: the cancellation rescue unconditionally released after250ms, before the owner could assert cancellation under slower scheduling. The transport also slept after every41/73-byte fragment, charging scheduler delays to real handshake deadlines. A minimal real TLS1.3 certificate fixture with1ms sleeps delayed to15ms failed at2089ms/outcome8(timeout); draining progress first passed at625ms. An intermediate full coarse-scheduling check still failed two held/capacity assertions because idle sleeps could expire the400ms pre-lookup deadline. Yielding while idle preserved that deadline and passed all313 checks in1.029seconds, with zero skips. The100ms owner-responsiveness assertion, cancellation/timeout/capacity/late-result assertions, bounded drains and all protocol settings remain unchanged. Only test source changed; production TLS is unchanged.
9292

9393
Setup evidence is retained honestly: system OpenSSL was rejected before selecting the existing3.5.9 provider; the first suite launch used build-root cwd instead of test cwd and failed fixture path lookup. Correcting cwd, as required by the generated relative TLS_TEST_DIR, produced the meaningful behavioral check. Neither setup failure justifies a source workaround. No broad baseline or Groundwork validation was run.
94+
95+
## Stage 5: Windows allocator fault-injection fixture linkage
96+
97+
- [x] Identify the current PR383 failed program from one bounded selected artifact assessment (3,891,726 range bytes; no full ZIP or original execution).
98+
- [x] Prove that executable operator-new replacement does not affect Windows DLL allocation, and that static linkage delivers the intended fault.
99+
- [x] Keep all 22 ordinary readiness cases against the installed Windows DLL; require all 27 cases, including the five unchanged allocation-failure cases, against the installed static archive.
100+
- [x] Make the required case names explicit in the inventory and reject every missing static fault case. Other platforms retain their original fault coverage.
101+
- [x] Pass native Windows focused DLL22/438 and static27/486 gates, Linux28/497, 28 package coverage/matrix checks, changed-file lint and diff checks.
102+
- [ ] Full exact-source CI with the corrected installed linkage profiles.
103+
104+
Current PR383 package job122630892885 passed native62 in4.812276s and external4 in0.572681s, then failed external_readiness_adapter27/486 with13 failures, all inside five allocation-failure cases. The assertion expecting bad_alloc was false and regular work completed. The PSK change cannot affect these allocation boundaries; this repair is within the accepted overall CI goal. Member CRC/SHA are verified; the full212MB ZIP digest is metadata-only. Assessment is closed.
105+
106+
A tiny native PE fixture reproduces the cause: executable allocation throws under its replacement operator new, DLL-owned allocation does not (exit1/0.362s). Linking the same owner object into the executable makes both throw (exit0/0.309s). Microsoft documents per-module DLL import binding: https://learn.microsoft.com/en-us/cpp/build/linking-an-executable-to-a-dll?view=msvc-170 . The chosen correction scopes allocation injection to linkage where it can actually reach the owner. Artificially throwing in the caller would not exercise the backend; a new production allocator hook or loader patch is unnecessary.
107+
108+
Only Windows shared compilation selects LHS_TEST_DLL_LINKAGE; this omits allocator replacement and the five fault registrations from that executable. All five original assertion bodies remain unchanged and mandatory in the installed static lane. The inventory names all27 ordinary/static cases, and the receipt validator requires the22 DLL cases or all27 static cases. No test skip or failed/empty receipt is accepted. Every original C++ assertion line is preserved. The other platforms keep all five fault cases in both linkage lanes.
109+
110+
Fresh current-source focused Windows backend DLL (16 owning translation units) passed22/438 in0.899s; matching static executable passed27/486 in1.237s, both zero skips and retired. Linux passes28/497 including its descriptor-zero case. Removing any one of the five fault names from the static receipt is rejected. Python coverage/matrix tests pass28 cases. Native setup initially missed net_address.cpp at link; adding that existing owning translation unit corrected the test build without a production workaround. Context is qualified local Windows11/cached LLVM23.1.4/GCC15.2 headers and retained runtime DLLs, not Server2022/MSYS2 CI. Evidence: primary `.debug/v3-ci-native-repairs-PJFEkn/allocator-linkage/`.
111+
112+
## Approved Windows capture closure
113+
114+
User approved exact430fa17e publication and ONE1/60 Windows original external-loop capture. Run40635371672 completed once:4tests/52checks/zero skips, exit0 in0.4701966s, retirement confirmed, stderr empty. Budget is CLOSED; no retry or remaining-time authority. This establishes nonreproduction in that context, not a fix or independence of the earlier WSAPoll10038 failure. The diagnostic branch remains NEVER MERGE. Only a proven repair may change production behavior.

‎test/installed/v3-package-matrix.json‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,42 @@
7373
"external_readiness_adapter": {
7474
"sources": [
7575
"unit/external_readiness_adapter_test.cpp"
76+
],
77+
"required_cases": [
78+
"host_drives_read_write_and_snapshot_is_owned",
79+
"spurious_unknown_and_wrong_generation_do_not_consume",
80+
"omitted_then_republished_registration_rejects_old_callbacks",
81+
"reopen_same_id_and_descriptor_rejects_every_old_flag_combination",
82+
"detached_read_cannot_rearm_into_reopened_id",
83+
"detached_accept_cannot_rearm_into_reopened_id",
84+
"exclusive_retirement_expires_real_timer_without_host_dispatch",
85+
"timers_use_host_clock_order_and_reject_decreasing_samples",
86+
"timer_cancel_expiry_race_is_exactly_once",
87+
"wake_is_doorbell_and_post_snapshot_work_survives_acknowledgement",
88+
"overlapping_dispatch_rejects_without_consuming_valid_read",
89+
"host_accept_and_close_deliver_once",
90+
"wake_published_during_dispatch_survives_and_recursive_overlap_reject",
91+
"release_during_completion_defers_close_and_cannot_step_replacement",
92+
"hard_wake_failure_stops_and_unwinds_pending_work",
93+
"decreasing_clock_rejects_a_valid_socket_event_before_io",
94+
"exhausted_identity_never_wraps_and_closes_unregistered_socket",
95+
"buffered_read_precedes_combined_close_error_and_duplicate_callback",
96+
"complete_snapshots_prune_retired_connection_records",
97+
"allocation_failure_read_batch_remains_owned_until_close",
98+
"allocation_failure_write_batch_remains_owned_until_close",
99+
"allocation_failure_accept_batch_remains_owned_until_close",
100+
"allocation_failure_second_direction_keeps_first_direction_owned",
101+
"allocation_failure_due_timers_remain_owned_until_close",
102+
"exhausted_generation_fails_snapshot_and_shutdown_completes_once",
103+
"successful_accept_after_listener_retirement_closes_fresh_handle",
104+
"successful_accept_after_backend_close_closes_fresh_handle"
105+
],
106+
"windows_static_cases": [
107+
"allocation_failure_read_batch_remains_owned_until_close",
108+
"allocation_failure_write_batch_remains_owned_until_close",
109+
"allocation_failure_accept_batch_remains_owned_until_close",
110+
"allocation_failure_second_direction_keeps_first_direction_owned",
111+
"allocation_failure_due_timers_remain_owned_until_close"
76112
]
77113
},
78114
"readiness_contract": {

‎test/unit/external_readiness_adapter_test.cpp‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "./io_backend_contract.hpp"
4040
#include "./littletest.hpp"
4141

42+
#ifndef LHS_TEST_DLL_LINKAGE
4243
// Fault injection is confined to this test executable and the dispatching
4344
// thread. Restore allocation before asserting or shutting down the backend.
4445
namespace {
@@ -79,6 +80,7 @@ void* operator new(std::size_t size) {
7980
void operator delete(void* memory) noexcept { std::free(memory); }
8081
void operator delete(void* memory, std::size_t) noexcept { std::free(memory); }
8182
#endif
83+
#endif // LHS_TEST_DLL_LINKAGE
8284

8385
namespace srv = httpserver::server;
8486
namespace {
@@ -203,6 +205,7 @@ void detached_reopen(littletest::test_runner* __lt_tr__, const char* __lt_name__
203205
LT_CHECK_EQ(fx.backend.pending_count(), std::size_t{0});
204206
}
205207

208+
#ifndef LHS_TEST_DLL_LINKAGE
206209
void failed_direction_collection(littletest::test_runner* __lt_tr__,
207210
const char* __lt_name__, hd::io_op_kind kind) {
208211
fixture fx;
@@ -253,6 +256,8 @@ void failed_direction_collection(littletest::test_runner* __lt_tr__,
253256
LT_CHECK_EQ(second.delivered.load(), 1);
254257
}
255258

259+
#endif // LHS_TEST_DLL_LINKAGE
260+
256261
void accepted_after_teardown(littletest::test_runner* __lt_tr__,
257262
const char* __lt_name__, bool shutdown) {
258263
fixture fx;
@@ -855,6 +860,9 @@ LT_BEGIN_AUTO_TEST(external_adapter_suite, complete_snapshots_prune_retired_conn
855860
}
856861
LT_END_AUTO_TEST(complete_snapshots_prune_retired_connection_records)
857862

863+
#ifndef LHS_TEST_DLL_LINKAGE
864+
// PE DLL allocations bind their own allocator imports. These cases run against
865+
// the installed static library, where this executable's override reaches them.
858866
LT_BEGIN_AUTO_TEST(external_adapter_suite, allocation_failure_read_batch_remains_owned_until_close)
859867
failed_direction_collection(__lt_tr__, __lt_name__, hd::io_op_kind::read);
860868
LT_END_AUTO_TEST(allocation_failure_read_batch_remains_owned_until_close)
@@ -923,6 +931,7 @@ LT_BEGIN_AUTO_TEST(external_adapter_suite, allocation_failure_due_timers_remain_
923931
LT_CHECK_EQ(first.delivered.load(), 1);
924932
LT_CHECK_EQ(second.delivered.load(), 1);
925933
LT_END_AUTO_TEST(allocation_failure_due_timers_remain_owned_until_close)
934+
#endif // LHS_TEST_DLL_LINKAGE
926935

927936
LT_BEGIN_AUTO_TEST(external_adapter_suite, exhausted_generation_fails_snapshot_and_shutdown_completes_once)
928937
fixture fx;

0 commit comments

Comments
 (0)