Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/editors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- run: npx --no-install vsce package --out loaddensity.vsix
- name: The package carries the schema
run: npx --no-install vsce ls | grep -Fx "schemas/loaddensity-action-schema.json"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: loaddensity-vscode
path: editors/vscode/loaddensity.vsix
Expand Down Expand Up @@ -86,7 +86,7 @@ jobs:
with:
gradle-version: "8.10.2"
- run: gradle buildPlugin --no-daemon
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: loaddensity-jetbrains
path: editors/jetbrains/build/distributions/*.zip
6 changes: 6 additions & 0 deletions docs/updates/2026-10.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,9 @@ Query existing PyPI releases when choosing the stable patch version, reusing the
## U-20261011-03 · 2026-10-11 · Include PyModbus serial import dependency · #done #ci

Request pymodbus[serial] in both modbus and all extras for both release channels, preventing PyModbus 3.15 client imports from failing with an undefined pyserial name. Regenerate uv.lock, including synchronization with the existing etcd3gw metadata. The exact PyModbus 3.15 import, request encoding and client construction pass in an isolated environment; 62 parity, matrix, release-helper and workflow tests pass; uv lock --check passes.

## U-20261022-05 · 2026-10-22 · Bound Windows socket shutdown and avoid subprocess pipe hangs · #bugfix #deps #ci

- PR #127: passed the Node DOM fixture through owned files so gevent-patched subprocess communication cannot hang on Windows. Kept the existing timeout and DOM/security assertions.
- Bounded the listening socket accept after stale readiness events: the cooperative server can observe shutdown instead of waiting forever for another client. Accepted connections keep their existing receive timeout policy.
- Validation: dashboard UI and socket child-process tests.
1 change: 1 addition & 0 deletions docs/updates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ In the same commit: delete the item from `progress.md`, add a `#done` entry here

| ID | Date | Title | Tags | Batch |
|---|---|---|---|---|
| U-20261022-05 | 2026-10-22 | Bound Windows socket shutdown and avoid subprocess pipe hangs | #bugfix #deps #ci | [2026-10](2026-10.md) |
| U-20261011-03 | 2026-10-11 | Include PyModbus serial import dependency | #done #ci | [2026-10](2026-10.md) |
| U-20261011-02 | 2026-10-11 | Avoid duplicate stable PyPI versions | #done #ci | [2026-10](2026-10.md) |
| U-20261011-01 | 2026-10-11 | Clarify reviewed socket server analysis findings | #done #ci | [2026-10](2026-10.md) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
)


class _ShutdownAwareTCPServer(ActionTCPServer):
"""Bound an accept after a stale readiness event so shutdown can finish."""

def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.socket.settimeout(0.5)


def _print_info(message: str) -> None:
print(message, flush=True)

Expand Down Expand Up @@ -87,7 +95,9 @@ def start_load_density_socket_server(
if token is None:
token = os.environ.get(TOKEN_ENVIRONMENT_VARIABLE)
settings = socket_server_settings(framed=framed, token=token, certfile=certfile, keyfile=keyfile)
server = start_action_socket_server(host, port, settings, EnvelopeTokenRequestHandler)
server = start_action_socket_server(
host, port, settings, EnvelopeTokenRequestHandler, server_class=_ShutdownAwareTCPServer,
)
print(f"Server started on {host}:{port}", flush=True)
server.close_event.wait()
server.shutdown()
Expand Down
18 changes: 12 additions & 6 deletions test/test_dashboard_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from je_load_density.utils.dashboard.live_dashboard import _HTML


def test_dashboard_renders_bands_and_untrusted_names_as_text():
def test_dashboard_renders_bands_and_untrusted_names_as_text(tmp_path):
node = shutil.which("node")
if node is None:
pytest.skip("Node is required for browser-script DOM contract checks")
Expand Down Expand Up @@ -47,9 +47,15 @@ class EventSource { constructor() { source=this; } }
{**window, "start_time": 2}]}
# Security audit: PATH-resolved Node runs fixed harness code; report/name payloads arrive on stdin, never shell
# code.
# nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit.dangerous-subprocess-use-audit
completed = subprocess.run([node, "-e", harness], input=json.dumps({"script": script, "data": data}),
text=True, encoding="utf-8", capture_output=True, timeout=10, check=False)
assert completed.returncode == 0, completed.stderr
output = json.loads(completed.stdout)
# Locust patches subprocess with gevent. Windows pipe communication can block
# after Node exits, so use owned files for input and output instead of pipes.
source = tmp_path / "input.json"
source.write_text(json.dumps({"script": script, "data": data}), encoding="utf-8")
stdout, stderr = tmp_path / "stdout.json", tmp_path / "stderr.txt"
with source.open("rb") as incoming, stdout.open("wb") as outgoing, stderr.open("wb") as errors:
# nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit.dangerous-subprocess-use-audit
completed = subprocess.run([node, "-e", harness], stdin=incoming, stdout=outgoing,
stderr=errors, timeout=10, check=False)
assert completed.returncode == 0, stderr.read_text(encoding="utf-8")
output = json.loads(stdout.read_text(encoding="utf-8"))
assert output == {"name": name, "bands": 4, "status": "Connected", "failure": "1"}
Loading