From d76b74efab128e9d99aac71888af276c3efa5b23 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 10:51:42 +0100 Subject: [PATCH 01/16] Debug startup errors on Windows --- .github/workflows/test.yml | 2 +- mypy/build.py | 9 ++++++++- mypy/build_worker/worker.py | 3 +++ mypy/ipc.py | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5242739f8f84..05c709fab67f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,7 +116,7 @@ jobs: python: '3.14' os: windows-latest toxenv: py - tox_extra_args: "-n 2 --mypy-num-workers=2 mypy/test/testcheck.py -k 'incremental or modules or classes'" + tox_extra_args: "-n 4 --mypy-num-workers=2 mypy/test/testcheck.py" - name: Type check our own code (py310-ubuntu) python: '3.10' diff --git a/mypy/build.py b/mypy/build.py index ca7e6d486822..46d3eda1b45f 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -283,6 +283,7 @@ def __init__(self, status_file: str, options_data: str, env: Mapping[str, str]) ] # Return early without waiting, caller must call connect() before using the client. self.proc = subprocess.Popen(command, env=env) + self.connected = False def connect(self) -> None: end_time = time.time() + WORKER_START_TIMEOUT @@ -303,12 +304,12 @@ def connect(self) -> None: # verify PIDs reliably. assert pid == self.proc.pid, f"PID mismatch: {pid} vs {self.proc.pid}" self.conn = IPCClient(connection_name, WORKER_CONNECTION_TIMEOUT) + self.connected = True return except Exception as exc: last_exception = exc break print("Failed to establish connection with worker:", last_exception) - sys.exit(2) def close(self) -> None: self.conn.close() @@ -394,6 +395,8 @@ def default_flush_errors( def connect(wc: WorkerClient, data: bytes) -> None: # Start loading sources in each worker as soon as it is up. wc.connect() + if not wc.connected: + return wc.conn.write_bytes(data) # We don't wait for workers to be ready until they are actually needed. @@ -432,6 +435,8 @@ def connect(wc: WorkerClient, data: bytes) -> None: for thread in connect_threads: thread.join() for worker in workers: + if not worker.connected: + continue try: send(worker.conn, SccRequestMessage(scc_id=None, import_errors={}, mod_data={})) except (OSError, IPCException): @@ -3972,6 +3977,8 @@ def dispatch( # Wait for workers since they may be needed at this point. for thread in connect_threads: thread.join() + if not all(wc.connected for wc in manager.workers): + raise OSError("Build workers failed to start") process_graph(graph, manager) # Update plugins snapshot. write_plugins_snapshot(manager) diff --git a/mypy/build_worker/worker.py b/mypy/build_worker/worker.py index 6742bd6fde6f..56f90ef14640 100644 --- a/mypy/build_worker/worker.py +++ b/mypy/build_worker/worker.py @@ -94,6 +94,9 @@ def main(argv: list[str]) -> None: status_file = args.status_file server = IPCServer(CONNECTION_NAME, WORKER_CONNECTION_TIMEOUT) + if sys.platform == "win32": + print("Writing status file:", status_file) + try: with open(status_file, "w") as f: json.dump({"pid": os.getpid(), "connection_name": server.connection_name}, f) diff --git a/mypy/ipc.py b/mypy/ipc.py index 607a27668caf..f2640d8e377a 100644 --- a/mypy/ipc.py +++ b/mypy/ipc.py @@ -361,6 +361,8 @@ def read_status(status_file: str) -> dict[str, object]: Raise BadStatus if the status file doesn't exist or contains invalid JSON or the JSON is not a dict. """ + if sys.platform == "win32": + print("Reading status file:", status_file) if not os.path.isfile(status_file): raise BadStatus("No status file found") with open(status_file) as f: From 11313da8d0498b0bdd52abcf55c056cf156ee9ff Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 12:12:19 +0100 Subject: [PATCH 02/16] Add timestamps --- mypy/build_worker/worker.py | 2 +- mypy/ipc.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/build_worker/worker.py b/mypy/build_worker/worker.py index 56f90ef14640..d417bfe89835 100644 --- a/mypy/build_worker/worker.py +++ b/mypy/build_worker/worker.py @@ -95,7 +95,7 @@ def main(argv: list[str]) -> None: server = IPCServer(CONNECTION_NAME, WORKER_CONNECTION_TIMEOUT) if sys.platform == "win32": - print("Writing status file:", status_file) + print(f"Writing status file: {status_file} at {time.time()}") try: with open(status_file, "w") as f: diff --git a/mypy/ipc.py b/mypy/ipc.py index f2640d8e377a..dffd0d5f30bd 100644 --- a/mypy/ipc.py +++ b/mypy/ipc.py @@ -12,6 +12,7 @@ import struct import sys import tempfile +import time from abc import abstractmethod from collections.abc import Callable, Sequence from select import select @@ -362,7 +363,7 @@ def read_status(status_file: str) -> dict[str, object]: invalid JSON or the JSON is not a dict. """ if sys.platform == "win32": - print("Reading status file:", status_file) + print(f"Reading status file: {status_file} at {time.time()}") if not os.path.isfile(status_file): raise BadStatus("No status file found") with open(status_file) as f: From 21779798ca604dd7a1b1032edfe18c3464ffecb2 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 12:12:51 +0100 Subject: [PATCH 03/16] Try making it more stressful --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 05c709fab67f..be6ec3199123 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,7 +116,7 @@ jobs: python: '3.14' os: windows-latest toxenv: py - tox_extra_args: "-n 4 --mypy-num-workers=2 mypy/test/testcheck.py" + tox_extra_args: "-n 4 --mypy-num-workers=4 mypy/test/testcheck.py" - name: Type check our own code (py310-ubuntu) python: '3.10' From a39acac794d7838624a8355751454ce57c60c2ea Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 12:30:54 +0100 Subject: [PATCH 04/16] Fail fast on Windows --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index be6ec3199123..7c41f592e53a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,7 +116,7 @@ jobs: python: '3.14' os: windows-latest toxenv: py - tox_extra_args: "-n 4 --mypy-num-workers=4 mypy/test/testcheck.py" + tox_extra_args: "-n 4 --mypy-num-workers=4 mypy/test/testcheck.py --maxfail=1" - name: Type check our own code (py310-ubuntu) python: '3.10' From 25d7ea9c79d69ee96b7d3fb749ec886184e50993 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 13:49:55 +0100 Subject: [PATCH 05/16] Add build id to status file --- mypy/build.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mypy/build.py b/mypy/build.py index 46d3eda1b45f..b27a68a8ec5c 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -379,12 +379,16 @@ def default_flush_errors( workers = [] connect_threads = [] + # A quasi-unique ID for this specific mypy invocation. + build_id = os.urandom(4).hex() if options.num_workers > 0: # TODO: switch to something more efficient than pickle (also in the daemon). pickled_options = pickle.dumps(options.snapshot()) options_data = b64encode(pickled_options).decode() workers = [ - WorkerClient(f".mypy_worker.{idx}.json", options_data, worker_env or os.environ) + WorkerClient( + f".mypy_worker.{build_id}.{idx}.json", options_data, worker_env or os.environ + ) for idx in range(options.num_workers) ] sources_message = SourcesDataMessage(sources=sources) @@ -396,6 +400,7 @@ def connect(wc: WorkerClient, data: bytes) -> None: # Start loading sources in each worker as soon as it is up. wc.connect() if not wc.connected: + # Caller should detect this and fail gracefully. return wc.conn.write_bytes(data) From 2559df70b036e2d208bef7e79899b6520d77cc97 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 13:52:53 +0100 Subject: [PATCH 06/16] Try getting fail faster --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7c41f592e53a..2b30d21333c4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,7 +116,7 @@ jobs: python: '3.14' os: windows-latest toxenv: py - tox_extra_args: "-n 4 --mypy-num-workers=4 mypy/test/testcheck.py --maxfail=1" + tox_extra_args: "-n 1 --mypy-num-workers=8 mypy/test/testcheck.py --maxfail=1" - name: Type check our own code (py310-ubuntu) python: '3.10' From dfb8f00a9f60e82e3ca2751764eed4919fb040f3 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 14:18:40 +0100 Subject: [PATCH 07/16] More logging --- mypy/build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/build.py b/mypy/build.py index b27a68a8ec5c..e1652ec4d4ba 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1787,6 +1787,7 @@ def exclude_from_backups(target_dir: str) -> None: def create_metastore(options: Options, parallel_worker: bool) -> MetadataStore: """Create the appropriate metadata store.""" if options.sqlite_cache: + print(f"Create metastore: {parallel_worker}") mds: MetadataStore = SqliteMetadataStore( _cache_dir_prefix(options), set_journal_mode=not parallel_worker ) From 65aff5c208f81504da8711ce73ce0eb759090fd9 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 14:28:51 +0100 Subject: [PATCH 08/16] Even more logging --- mypy/build.py | 2 +- mypy/metastore.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/build.py b/mypy/build.py index e1652ec4d4ba..64fee2629586 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1787,7 +1787,7 @@ def exclude_from_backups(target_dir: str) -> None: def create_metastore(options: Options, parallel_worker: bool) -> MetadataStore: """Create the appropriate metadata store.""" if options.sqlite_cache: - print(f"Create metastore: {parallel_worker}") + print(f"Create metastore: {parallel_worker} at {time.time()}") mds: MetadataStore = SqliteMetadataStore( _cache_dir_prefix(options), set_journal_mode=not parallel_worker ) diff --git a/mypy/metastore.py b/mypy/metastore.py index 23ca8e921a33..18e5681dc46f 100644 --- a/mypy/metastore.py +++ b/mypy/metastore.py @@ -161,8 +161,10 @@ def connect_db(db_file: str, set_journal_mode: bool) -> sqlite3.Connection: # This is a bit unfortunate (as we may get corrupt cache after e.g. Ctrl + C), # but without this flag, commits are *very* slow, especially when using HDDs, # see https://www.sqlite.org/faq.html#q19 for details. + print(f"Execute sync off at {time.time()}") db.execute("PRAGMA synchronous=OFF") if set_journal_mode: + print(f"Execute journal wal at {time.time()}") db.execute("PRAGMA journal_mode=WAL") db.executescript(SCHEMA) return db From d7636c2b6a0cc756fc825328be6073ba54f943ee Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 14:54:17 +0100 Subject: [PATCH 09/16] Pre-create metastore --- mypy/build.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mypy/build.py b/mypy/build.py index 64fee2629586..219fa3b22644 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -377,6 +377,8 @@ def default_flush_errors( stderr = stderr or sys.stderr extra_plugins = extra_plugins or [] + # Create metastore before workers to avoid race conditions. + metastore = create_metastore(options, parallel_worker=False) workers = [] connect_threads = [] # A quasi-unique ID for this specific mypy invocation. @@ -422,6 +424,7 @@ def connect(wc: WorkerClient, data: bytes) -> None: extra_plugins, workers, connect_threads, + metastore, ) result.errors = messages return result @@ -461,6 +464,7 @@ def build_inner( extra_plugins: Sequence[Plugin], workers: list[WorkerClient], connect_threads: list[Thread], + metastore: MetadataStore, ) -> BuildResult: if platform.python_implementation() == "CPython": # Run gc less frequently, as otherwise we can spend a large fraction of @@ -509,6 +513,7 @@ def build_inner( fscache=fscache, stdout=stdout, stderr=stderr, + metastore=metastore, ) manager.workers = workers if manager.verbosity() >= 2: @@ -826,6 +831,7 @@ def __init__( stderr: TextIO, error_formatter: ErrorFormatter | None = None, parallel_worker: bool = False, + metastore: MetadataStore | None = None, ) -> None: self.stats: dict[str, Any] = {} # Values are ints or floats # Use in cases where we need to prevent race conditions in stats reporting. @@ -913,7 +919,9 @@ def __init__( ] ) - self.metastore = create_metastore(options, parallel_worker=parallel_worker) + if metastore is None: + metastore = create_metastore(options, parallel_worker=parallel_worker) + self.metastore = metastore # a mapping from source files to their corresponding shadow files # for efficient lookup From 0d96cd03a3ef644e39077c560efa5be0f6fb1e19 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 15:20:41 +0100 Subject: [PATCH 10/16] Remove metastore debug prints --- mypy/build.py | 1 - mypy/metastore.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 219fa3b22644..3d74f20791a8 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1795,7 +1795,6 @@ def exclude_from_backups(target_dir: str) -> None: def create_metastore(options: Options, parallel_worker: bool) -> MetadataStore: """Create the appropriate metadata store.""" if options.sqlite_cache: - print(f"Create metastore: {parallel_worker} at {time.time()}") mds: MetadataStore = SqliteMetadataStore( _cache_dir_prefix(options), set_journal_mode=not parallel_worker ) diff --git a/mypy/metastore.py b/mypy/metastore.py index 18e5681dc46f..23ca8e921a33 100644 --- a/mypy/metastore.py +++ b/mypy/metastore.py @@ -161,10 +161,8 @@ def connect_db(db_file: str, set_journal_mode: bool) -> sqlite3.Connection: # This is a bit unfortunate (as we may get corrupt cache after e.g. Ctrl + C), # but without this flag, commits are *very* slow, especially when using HDDs, # see https://www.sqlite.org/faq.html#q19 for details. - print(f"Execute sync off at {time.time()}") db.execute("PRAGMA synchronous=OFF") if set_journal_mode: - print(f"Execute journal wal at {time.time()}") db.execute("PRAGMA journal_mode=WAL") db.executescript(SCHEMA) return db From 88c63af167f586f407d1c9a1e7dc73559623047d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 15:21:56 +0100 Subject: [PATCH 11/16] Use arangement favourable for other flake --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b30d21333c4..e962d73aea04 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,7 +116,7 @@ jobs: python: '3.14' os: windows-latest toxenv: py - tox_extra_args: "-n 1 --mypy-num-workers=8 mypy/test/testcheck.py --maxfail=1" + tox_extra_args: "-n 4 --mypy-num-workers=2 mypy/test/testcheck.py --maxfail=1" - name: Type check our own code (py310-ubuntu) python: '3.10' From 374e86610b32d070dbd49def1642235316671ba2 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 15:58:24 +0100 Subject: [PATCH 12/16] Fix unstable error order; Minor test cleanup --- test-data/unit/check-final.test | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/test-data/unit/check-final.test b/test-data/unit/check-final.test index 10943515688e..9165aae3a45e 100644 --- a/test-data/unit/check-final.test +++ b/test-data/unit/check-final.test @@ -60,8 +60,6 @@ reveal_type(C().x) # N: Revealed type is "builtins.float" [out] [case testFinalInvalidDefinitions] - -# Errors are shown in a different order with the new analyzer. from typing import Final, Any x = y = 1 # type: Final[float] # E: Invalid final declaration @@ -432,7 +430,6 @@ y: Final = 3 # E: Cannot redefine an existing name as final [case testFinalReassignModuleVar3] # flags: --disallow-redefinition -# Error formatting is subtly different with new analyzer. from typing import Final x: Final = 1 @@ -459,16 +456,14 @@ z: Final = 2 # E: Cannot redefine an existing name as final z = 3 # E: Cannot assign to final name "z" [case testFinalReassignModuleReexport] - -# Error formatting is subtly different with the new analyzer. from typing import Final from lib import X from lib.mod import ID -X = 1 # Error! -ID: Final = 1 # Two errors! -ID = 1 # Error! +X = 1 # E: Cannot assign to final name "X" +ID: Final = 1 # E: Cannot redefine an existing name as final +ID = 1 # E: Cannot assign to final name "ID" [file lib/__init__.pyi] from lib.const import X as X @@ -478,13 +473,8 @@ from lib.const import * [file lib/const.pyi] from typing import Final -ID: Final # Error! +ID: Final # E: Type in Final[...] can only be omitted if there is an initializer X: Final[int] -[out] -tmp/lib/const.pyi:3: error: Type in Final[...] can only be omitted if there is an initializer -main:8: error: Cannot assign to final name "X" -main:9: error: Cannot redefine an existing name as final -main:10: error: Cannot assign to final name "ID" [case testFinalReassignFuncScope] from typing import Final From f3175c204fc7b4584f8203b4cbeb0030ffdc74a2 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 16:48:06 +0100 Subject: [PATCH 13/16] One more graceful check; more debug --- mypy/build.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 3d74f20791a8..a851a2081f28 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -288,6 +288,7 @@ def __init__(self, status_file: str, options_data: str, env: Mapping[str, str]) def connect(self) -> None: end_time = time.time() + WORKER_START_TIMEOUT last_exception: Exception | None = None + print(f"Reading until: {end_time}") while time.time() < end_time: try: data = read_status(self.status_file) @@ -309,10 +310,11 @@ def connect(self) -> None: except Exception as exc: last_exception = exc break - print("Failed to establish connection with worker:", last_exception) + print(f"Failed to establish connection with worker: {last_exception} at {time.time()}") def close(self) -> None: - self.conn.close() + if self.connected: + self.conn.close() # Technically we don't need to wait, but otherwise we will get ResourceWarnings. try: self.proc.wait(timeout=1) From 54ca5b075bf3ebf33318991f5f30480c964bf191 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 17:23:18 +0100 Subject: [PATCH 14/16] A bit more helpful message --- mypy/build.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index a851a2081f28..fb2a001e783c 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -3992,8 +3992,9 @@ def dispatch( # Wait for workers since they may be needed at this point. for thread in connect_threads: thread.join() - if not all(wc.connected for wc in manager.workers): - raise OSError("Build workers failed to start") + not_connected = [str(idx) for idx, wc in enumerate(manager.workers) if not wc.connected] + if not_connected: + raise OSError(f"Cannot connect to build worker(s): {', '.join(not_connected)}") process_graph(graph, manager) # Update plugins snapshot. write_plugins_snapshot(manager) From 1618c953d13fbe2f3c1c5b179cff53e60b6593a7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 18:31:25 +0100 Subject: [PATCH 15/16] Try something more extreme --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e962d73aea04..e4ce37d79200 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,7 +116,7 @@ jobs: python: '3.14' os: windows-latest toxenv: py - tox_extra_args: "-n 4 --mypy-num-workers=2 mypy/test/testcheck.py --maxfail=1" + tox_extra_args: "-n 8 --mypy-num-workers=2 mypy/test/testcheck.py --maxfail=1" - name: Type check our own code (py310-ubuntu) python: '3.10' From fdd9908473b549b62588896b102feb02acd15399 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 14 Apr 2026 20:29:25 +0100 Subject: [PATCH 16/16] Increase process-related timeouts on Windows --- .github/workflows/test.yml | 2 +- mypy/build.py | 11 +++++++---- mypy/build_worker/worker.py | 3 --- mypy/defaults.py | 9 +++++++-- mypy/ipc.py | 3 --- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e4ce37d79200..5242739f8f84 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,7 +116,7 @@ jobs: python: '3.14' os: windows-latest toxenv: py - tox_extra_args: "-n 8 --mypy-num-workers=2 mypy/test/testcheck.py --maxfail=1" + tox_extra_args: "-n 2 --mypy-num-workers=2 mypy/test/testcheck.py -k 'incremental or modules or classes'" - name: Type check our own code (py310-ubuntu) python: '3.10' diff --git a/mypy/build.py b/mypy/build.py index fb2a001e783c..2ad8a625fec9 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -91,6 +91,7 @@ from mypy.defaults import ( WORKER_CONNECTION_TIMEOUT, WORKER_DONE_TIMEOUT, + WORKER_SHUTDOWN_TIMEOUT, WORKER_START_INTERVAL, WORKER_START_TIMEOUT, ) @@ -288,7 +289,6 @@ def __init__(self, status_file: str, options_data: str, env: Mapping[str, str]) def connect(self) -> None: end_time = time.time() + WORKER_START_TIMEOUT last_exception: Exception | None = None - print(f"Reading until: {end_time}") while time.time() < end_time: try: data = read_status(self.status_file) @@ -310,14 +310,14 @@ def connect(self) -> None: except Exception as exc: last_exception = exc break - print(f"Failed to establish connection with worker: {last_exception} at {time.time()}") + print(f"Failed to establish connection with worker: {last_exception}") def close(self) -> None: if self.connected: self.conn.close() # Technically we don't need to wait, but otherwise we will get ResourceWarnings. try: - self.proc.wait(timeout=1) + self.proc.wait(timeout=WORKER_SHUTDOWN_TIMEOUT) except subprocess.TimeoutExpired: pass if os.path.isfile(self.status_file): @@ -349,7 +349,7 @@ def build( If a flush_errors callback is provided, all error messages will be passed to it and the errors and messages fields of BuildResult and - CompileError (respectively) will be empty. Otherwise those fields will + CompileError (respectively) will be empty. Otherwise, those fields will report any error messages. Args: @@ -359,6 +359,9 @@ def build( (takes precedence over other directories) flush_errors: optional function to flush errors after a file is processed fscache: optionally a file-system cacher + stdout: Output stream to use instead of `sys.stdout` + stderr: Error stream to use instead of `sys.stderr` + extra_plugins: Plugins to use in addition to those loaded from config worker_env: An environment to start parallel build workers (used for tests) """ # If we were not given a flush_errors, we use one that will populate those diff --git a/mypy/build_worker/worker.py b/mypy/build_worker/worker.py index d417bfe89835..6742bd6fde6f 100644 --- a/mypy/build_worker/worker.py +++ b/mypy/build_worker/worker.py @@ -94,9 +94,6 @@ def main(argv: list[str]) -> None: status_file = args.status_file server = IPCServer(CONNECTION_NAME, WORKER_CONNECTION_TIMEOUT) - if sys.platform == "win32": - print(f"Writing status file: {status_file} at {time.time()}") - try: with open(status_file, "w") as f: json.dump({"pid": os.getpid(), "connection_name": server.connection_name}, f) diff --git a/mypy/defaults.py b/mypy/defaults.py index 749879861fbf..129d8ad7f479 100644 --- a/mypy/defaults.py +++ b/mypy/defaults.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import sys from typing import Final # Earliest fully supported Python 3.x version. Used as the default Python @@ -45,8 +46,12 @@ RECURSION_LIMIT: Final = 2**14 -WORKER_START_INTERVAL: Final = 0.01 -WORKER_START_TIMEOUT: Final = 3 +# It looks like Windows is slow with processes, causing test flakiness even +# with our generous timeouts, so we set them higher. +WORKER_START_INTERVAL: Final = 0.01 if sys.platform != "win32" else 0.03 +WORKER_START_TIMEOUT: Final = 3 if sys.platform != "win32" else 10 +WORKER_SHUTDOWN_TIMEOUT: Final = 1 if sys.platform != "win32" else 3 + WORKER_CONNECTION_TIMEOUT: Final = 10 WORKER_IDLE_TIMEOUT: Final = 600 WORKER_DONE_TIMEOUT: Final = 600 diff --git a/mypy/ipc.py b/mypy/ipc.py index dffd0d5f30bd..607a27668caf 100644 --- a/mypy/ipc.py +++ b/mypy/ipc.py @@ -12,7 +12,6 @@ import struct import sys import tempfile -import time from abc import abstractmethod from collections.abc import Callable, Sequence from select import select @@ -362,8 +361,6 @@ def read_status(status_file: str) -> dict[str, object]: Raise BadStatus if the status file doesn't exist or contains invalid JSON or the JSON is not a dict. """ - if sys.platform == "win32": - print(f"Reading status file: {status_file} at {time.time()}") if not os.path.isfile(status_file): raise BadStatus("No status file found") with open(status_file) as f: