Skip to content

Widen check_multiprocess_shutdown_event type hint via Protocol (closes #247)#356

Open
jbbqqf wants to merge 1 commit into
pgjones:mainfrom
jbbqqf:fix/247-shutdown-event-type-hint
Open

Widen check_multiprocess_shutdown_event type hint via Protocol (closes #247)#356
jbbqqf wants to merge 1 commit into
pgjones:mainfrom
jbbqqf:fix/247-shutdown-event-type-hint

Conversation

@jbbqqf
Copy link
Copy Markdown

@jbbqqf jbbqqf commented May 22, 2026

Summary

check_multiprocess_shutdown_event only reads shutdown_event.is_set(), but its parameter is typed as multiprocessing.synchronize.Event. That rejects the two equally-valid alternatives users routinely reach for:

  • multiprocessing.Manager().Event() — a proxy that wraps threading.Event (Python docs).
  • Plain threading.Event for in-process tests.

mypy reports error: Argument 1 ... has incompatible type "threading.Event"; expected "multiprocessing.synchronize.Event" [arg-type] for both, even though the call is semantically correct.

Fix: introduce a minimal IsSetEvent Protocol exposing is_set() -> bool and annotate the parameter with it. Existing call sites that pass multiprocessing.Event() continue to type-check unchanged (they structurally satisfy the protocol).

Fixes #247.

Changes

  • src/hypercorn/utils.py — add IsSetEvent(Protocol); widen check_multiprocess_shutdown_event signature; drop the now-unused EventType import.
  • tests/test_utils.pytest_check_multiprocess_shutdown_event_accepts_is_set_protocol parametrised over three event-like types (multiprocessing.Event, multiprocessing.Manager().Event(), threading.Event).

Reproduce BEFORE/AFTER yourself (copy-paste)

# --- one-time setup ---
rm -rf /tmp/hc-247 && git clone https://github.com/pgjones/hypercorn.git /tmp/hc-247 && cd /tmp/hc-247
python3.11 -m venv .venv && source .venv/bin/activate
pip install -e . mypy

cat > /tmp/hc-247/repro.py <<'PY'
import asyncio, multiprocessing
from hypercorn.utils import check_multiprocess_shutdown_event

async def main() -> None:
    event = multiprocessing.Manager().Event()      # proxy of threading.Event
    await check_multiprocess_shutdown_event(event, asyncio.sleep)
PY

# --- BEFORE: origin/main, expect mypy error ---
git checkout origin/main
mypy /tmp/hc-247/repro.py
# Expected: error: Argument 1 to "check_multiprocess_shutdown_event" has
#           incompatible type "threading.Event"; expected
#           "multiprocessing.synchronize.Event"  [arg-type]

# --- AFTER: this branch, expect no errors ---
git fetch https://github.com/jbbqqf/hypercorn.git fix/247-shutdown-event-type-hint
git checkout FETCH_HEAD
mypy /tmp/hc-247/repro.py
# Expected: Success: no issues found in 1 source file

What I ran locally

$ pytest tests/test_utils.py -v
========== 14 passed in 0.03s ==========

$ pytest -x --timeout=30 -q
199 passed in 6.17s

$ mypy src/hypercorn/
Found 2 errors in 1 file (checked 39 source files)
# (the 2 errors are pre-existing in src/hypercorn/logging.py:163-164 on main,
#  unrelated to this PR)

$ black --check src/hypercorn/utils.py tests/test_utils.py && \
    isort --check src/hypercorn/utils.py tests/test_utils.py && \
    flake8 src/hypercorn/utils.py tests/test_utils.py
(no output — clean)

Edge cases

# Scenario Event passed mypy on main mypy on branch Runtime
1 Worker uses parent's multiprocessing.Event() multiprocessing.Event() OK OK OK
2 Worker uses Manager().Event() (issue #247) proxy of threading.Event error OK OK
3 In-process test wires a threading.Event threading.Event() error OK OK
4 Hostile caller passes object without is_set() object() error error AttributeError at runtime (unchanged)

Risk / blast radius

The change is purely an annotation widening. Runtime behaviour is identical — the call site only ever invokes .is_set(). No source file's mypy output regressed (pre-existing unused-ignore comments in src/hypercorn/logging.py are unaffected). The Protocol follows the same pattern already used in src/hypercorn/typing.py for H2SyncStream, H2AsyncStream, Event, etc.


PR drafted with assistance from Claude Code (Anthropic). The change was reviewed manually against hypercorn's source. The reproducer block above is the one I used during development; reviewers can paste it verbatim.

The function only calls ``shutdown_event.is_set()``, but it was annotated as
``multiprocessing.synchronize.Event``. mypy therefore rejected the two
equally-valid alternatives that users routinely reach for:

 * ``multiprocessing.Manager().Event()`` — a proxy that wraps
   ``threading.Event`` (per `the docs
   <https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.SyncManager.Event>`_).
 * Plain ``threading.Event`` for in-process testing.

Introduce a minimal ``IsSetEvent`` ``Protocol`` so any object exposing
``is_set()`` is accepted. Existing call sites using
``multiprocessing.Event()`` continue to type-check unchanged.

Closes pgjones#247.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Type hint for check_multiprocess_shutdown_event is confusing for mypy

1 participant