Skip to content

Commit 83ea956

Browse files
committed
Debugging a windows issue
1 parent 9d542ec commit 83ea956

2 files changed

Lines changed: 111 additions & 18 deletions

File tree

.github/workflows/build.yml

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,11 @@ jobs:
1616
CMAKE_GENERATOR: Ninja
1717
strategy:
1818
matrix:
19-
os: [ubuntu-latest, windows-latest, macos-latest]
19+
# Temporary windows-debug branch matrix: run only the Windows
20+
# reproducer for the DSL/miniexpr shutdown access violation.
21+
os: [windows-latest]
2022
python-version: ["3.12"]
2123
numpy-version: [null]
22-
include:
23-
- os: ubuntu-latest
24-
python-version: "3.12"
25-
numpy-version: "1.26"
26-
- os: ubuntu-latest
27-
python-version: "3.14"
28-
numpy-version: null
2924

3025
steps:
3126
- uses: actions/checkout@v6
@@ -86,14 +81,6 @@ jobs:
8681
id: build_non_windows
8782
run: pip install -e . --group test
8883

89-
- name: Test (Windows)
84+
- name: Test Windows DSL shutdown reproducer only
9085
if: runner.os == 'Windows'
91-
run: python -m pytest -m "not heavy and (network or not network)"
92-
# env:
93-
# BLOSC_NTHREADS: "1"
94-
# NUMEXPR_NUM_THREADS: "1"
95-
# OMP_NUM_THREADS: "1"
96-
97-
- name: Test (non-Windows)
98-
if: runner.os != 'Windows'
99-
run: python -m pytest -m "not heavy and (network or not network)"
86+
run: python -m pytest -s tests/ndarray/test_windows_dsl_shutdown_repro.py
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""Minimal Windows reproducer for DSL/miniexpr shutdown access violations.
2+
3+
This file is intentionally small and subprocess-based so CI can isolate the
4+
0xC0000005 crash that happens after a scalar-only DSL kernel using _flat_idx
5+
has already produced correct results.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import subprocess
11+
import sys
12+
import tempfile
13+
import textwrap
14+
from pathlib import Path
15+
16+
import pytest
17+
18+
import blosc2
19+
20+
21+
@pytest.mark.parametrize(
22+
("case_name", "lazyudf_kwargs"),
23+
[
24+
("default_jit_policy", ""),
25+
("jit_false", ", jit=False"),
26+
("jit_true", ", jit=True"),
27+
],
28+
)
29+
def test_windows_dsl_scalar_only_flat_idx_shutdown_repro(case_name, lazyudf_kwargs):
30+
"""Exercise the suspected crash path in a child process.
31+
32+
The child prints milestones with flush=True. If Windows returns
33+
0xC0000005 after printing ``script-end``, the crash is happening during
34+
interpreter/module teardown rather than during computation or explicit GC.
35+
"""
36+
if blosc2.IS_WASM:
37+
pytest.skip("subprocess is not supported on emscripten/wasm32")
38+
39+
code = textwrap.dedent(
40+
f"""
41+
import faulthandler
42+
import gc
43+
import sys
44+
45+
faulthandler.enable(all_threads=True)
46+
47+
import importlib
48+
import numpy as np
49+
import blosc2
50+
lazyexpr_mod = importlib.import_module("blosc2.lazyexpr")
51+
from blosc2.dsl_kernel import specialize_miniexpr_inputs
52+
53+
print("case={case_name}", flush=True)
54+
print("platform=" + sys.platform, flush=True)
55+
print("try_miniexpr=" + repr(lazyexpr_mod.try_miniexpr), flush=True)
56+
57+
@blosc2.dsl_kernel
58+
def kernel(start, stop, nitems):
59+
step = (float(stop) - float(start)) / float(nitems)
60+
return float(start) + _flat_idx * step # noqa: F821
61+
62+
shape = (10, 100)
63+
operands = dict(zip(kernel.input_names, (-10, 10, 999), strict=True))
64+
specialized_source, specialized_operands = specialize_miniexpr_inputs(kernel.dsl_source, operands)
65+
print("dsl-source-start", flush=True)
66+
print(kernel.dsl_source, flush=True)
67+
print("dsl-source-end", flush=True)
68+
print("specialized-source-start", flush=True)
69+
print(specialized_source, flush=True)
70+
print("specialized-source-end", flush=True)
71+
print("specialized-operands=" + repr(tuple(specialized_operands.keys())), flush=True)
72+
73+
expr = blosc2.lazyudf(kernel, (-10, 10, 999), dtype=np.float32, shape=shape{lazyudf_kwargs})
74+
print("lazy-created", flush=True)
75+
arr = expr.compute()
76+
print("compute-ok", flush=True)
77+
exp = np.linspace(-10, 10, np.prod(shape), dtype=np.float32).reshape(shape)
78+
np.testing.assert_allclose(arr, exp, rtol=1e-6, atol=1e-6)
79+
print("assert-ok", flush=True)
80+
81+
del exp
82+
print("del-exp", flush=True)
83+
gc.collect()
84+
print("gc-after-exp", flush=True)
85+
del arr
86+
print("del-arr", flush=True)
87+
gc.collect()
88+
print("gc-after-arr", flush=True)
89+
del expr
90+
print("del-expr", flush=True)
91+
gc.collect()
92+
print("gc-after-expr", flush=True)
93+
print("script-end", flush=True)
94+
"""
95+
)
96+
97+
with tempfile.TemporaryDirectory() as tmpdir:
98+
script = Path(tmpdir) / f"dsl_shutdown_repro_{case_name}.py"
99+
script.write_text(code, encoding="utf-8")
100+
result = subprocess.run([sys.executable, str(script)], capture_output=True, text=True, check=False)
101+
102+
assert result.returncode == 0, (
103+
f"subprocess failed for case {case_name!r}: returncode={result.returncode}\n"
104+
f"stdout:\n{result.stdout}\n"
105+
f"stderr:\n{result.stderr}"
106+
)

0 commit comments

Comments
 (0)