Skip to content

Commit 5f14d48

Browse files
Add option to limit the number of workers
python/cpython#89240 Any use of mp.Pool with a number of workers greater than 60 fails on Windows machines. Twofold solution is introduced: - add option for users to explicitly change number of workers through -w/--workers, this is similar to solution used in black - add limitation in the code for Windows machines Co-Authored-By: Blank Spruce <[email protected]>
1 parent 96e25a1 commit 5f14d48

9 files changed

+75
-12
lines changed

.gersemirc.example

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ line_length: 80
66
list_expansion: favour-inlining
77
quiet: false
88
unsafe: false
9+
workers: 8

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [0.11.0] 2024-10-11
4+
### Added
5+
- Number of workers spawned for formatting multiple files can be changed with `-w/--workers`. By default it will be number of CPUs available in the system but limited to 60 for Windows machines due to [this](https://github.com/python/cpython/issues/89240).
6+
37
## [0.10.0] 2023-12-22
48
### Added
59
- configuration schema that can be used with yaml LSP server, see: [JSON Schema](https://json-schema.org/) and #12

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ configuration:
6262
entities will be formatted in such way that sublists will be
6363
completely expanded once expansion becomes necessary at all.
6464
[default: favour-inlining]
65+
-w INTEGER, --workers INTEGER
66+
Number of workers used to format multiple files in parallel.
67+
[default: number of CPUs on this system]
6568
```
6669

6770
### [pre-commit](https://pre-commit.com/) hook
@@ -71,7 +74,7 @@ You can use gersemi with a pre-commit hook by adding the following to `.pre-comm
7174
```yaml
7275
repos:
7376
- repo: https://github.com/BlankSpruce/gersemi
74-
rev: 0.10.0
77+
rev: 0.11.0
7578
hooks:
7679
- id: gersemi
7780
```

gersemi/__main__.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,17 @@ def create_argparser():
132132
[default: {Configuration.list_expansion.value}]
133133
""",
134134
)
135+
configuration_group.add_argument(
136+
"-w",
137+
"--workers",
138+
metavar="INTEGER",
139+
dest="workers",
140+
type=int,
141+
help=f"""
142+
{conf_doc['workers']}
143+
[default: number of CPUs on this system]
144+
""",
145+
)
135146

136147
parser.add_argument(
137148
dest="sources",
@@ -171,7 +182,7 @@ def main():
171182
configuration = make_configuration(args)
172183
mode = get_mode(args)
173184

174-
sys.exit(run(mode, configuration, args.sources))
185+
sys.exit(run(mode, configuration, args.workers, args.sources))
175186

176187

177188
if __name__ == "__main__":

gersemi/__version__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
__license__ = "MPL 2.0"
55
__title__ = "gersemi"
66
__url__ = "https://github.com/BlankSpruce/gersemi"
7-
__version__ = "0.10.0"
7+
__version__ = "0.11.0"

gersemi/configuration.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,24 @@
33
from enum import Enum
44
from hashlib import sha1
55
from itertools import chain
6+
import multiprocessing
67
import os
78
from pathlib import Path
9+
import sys
810
import textwrap
911
from typing import Iterable, Optional
1012
import yaml
1113

1214

15+
def number_of_workers():
16+
result = multiprocessing.cpu_count()
17+
if sys.platform == "win32":
18+
# https://bugs.python.org/issue26903
19+
# https://github.com/python/cpython/issues/89240
20+
return min(result, 60)
21+
return result
22+
23+
1324
def doc(text: str) -> str:
1425
return " ".join(textwrap.dedent(text).splitlines()).strip()
1526

@@ -111,15 +122,22 @@ class Configuration:
111122
),
112123
)
113124

125+
workers: int = field(
126+
default=number_of_workers(),
127+
metadata=dict(
128+
title="Workers",
129+
description="Number of workers used to format multiple files in parallel.",
130+
),
131+
)
132+
114133
def summary(self):
115134
hasher = sha1()
116135
hasher.update(repr(self).encode("utf-8"))
117136
return hasher.hexdigest()
118137

119138

120-
def make_default_configuration_file():
121-
default_configuration = Configuration()
122-
d = asdict(default_configuration)
139+
def make_configuration_file(configuration):
140+
d = asdict(configuration)
123141
d["list_expansion"] = d["list_expansion"].value
124142
result = f"""
125143
# yaml-language-server: $schema=https://raw.githubusercontent.com/BlankSpruce/gersemi/master/gersemi/configuration.schema.json
@@ -129,6 +147,10 @@ def make_default_configuration_file():
129147
return result.strip()
130148

131149

150+
def make_default_configuration_file():
151+
return make_configuration_file(Configuration())
152+
153+
132154
def find_common_parent_path(paths: Iterable[Path]) -> Path:
133155
return Path(os.path.commonpath([path.absolute() for path in paths]))
134156

@@ -168,6 +190,10 @@ def sanitize_list_expansion(list_expansion):
168190
)
169191

170192

193+
def sanitize_workers(workers):
194+
return max(1, workers)
195+
196+
171197
def load_configuration_from_file(configuration_file_path: Path) -> Configuration:
172198
with enter_directory(configuration_file_path.parent):
173199
with open(configuration_file_path, "r", encoding="utf-8") as f:
@@ -178,6 +204,8 @@ def load_configuration_from_file(configuration_file_path: Path) -> Configuration
178204
config["list_expansion"] = sanitize_list_expansion(
179205
config["list_expansion"]
180206
)
207+
if "workers" in config:
208+
config["workers"] = sanitize_workers(config["workers"])
181209
return Configuration(**config)
182210

183211

@@ -193,6 +221,8 @@ def override_configuration_with_args(
193221
value = normalize_definitions(value)
194222
if param == "list_expansion":
195223
value = sanitize_list_expansion(value)
224+
if param == "workers":
225+
value = sanitize_workers(value)
196226
setattr(configuration, param, value)
197227
return configuration
198228

gersemi/configuration.schema.json

+6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@
5454
"description": "Skip default sanity checks.",
5555
"title": "Unsafe",
5656
"type": "boolean"
57+
},
58+
"workers": {
59+
"minimum": 1,
60+
"description": "Number of workers used to format multiple files in parallel.",
61+
"title": "Workers",
62+
"type": "integer"
5763
}
5864
},
5965
"title": "Configuration",

gersemi/runner.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,10 @@ def consume_task_result(task_result: TaskResult) -> Tuple[Path, int]:
137137
return path, return_code
138138

139139

140-
def create_pool(is_stdin_in_sources):
140+
def create_pool(is_stdin_in_sources, num_workers):
141141
if is_stdin_in_sources:
142142
return mp_dummy.Pool
143-
return partial(mp.Pool, processes=mp.cpu_count())
143+
return partial(mp.Pool, processes=num_workers)
144144

145145

146146
def filter_already_formatted_files(
@@ -163,12 +163,14 @@ def store_files_in_cache(
163163
cache.store_files(configuration_summary, files)
164164

165165

166-
def run(mode: Mode, configuration: Configuration, sources: Iterable[Path]):
166+
def run(
167+
mode: Mode, configuration: Configuration, num_workers: int, sources: Iterable[Path]
168+
):
167169
configuration_summary = configuration.summary()
168170
requested_files = get_files(sources)
169171
task = select_task(mode, configuration)
170172

171-
pool_cm = create_pool(Path("-") in requested_files)
173+
pool_cm = create_pool(Path("-") in requested_files, num_workers)
172174
with create_cache() as cache, pool_cm() as pool:
173175
files_to_format = list(
174176
filter_already_formatted_files(

tests/test_configuration.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from gersemi.configuration import (
77
Configuration,
88
ListExpansion,
9-
make_default_configuration_file,
9+
make_configuration_file,
1010
)
1111

1212
try:
@@ -44,6 +44,10 @@ def generate(self, schema, mode=None):
4444
}
4545
result["properties"]["list_expansion"]["$ref"] = "#/$defs/ListExpansion"
4646
del result["properties"]["list_expansion"]["allOf"]
47+
48+
del result["properties"]["workers"]["default"]
49+
result["properties"]["workers"]["minimum"] = 1
50+
4751
return result
4852

4953

@@ -65,4 +69,6 @@ def test_example_file_in_repository_is_consistent_with_configuration_definition(
6569
with open(example_path, "r", encoding="utf-8") as f:
6670
example = f.read().strip()
6771

68-
assert example == make_default_configuration_file()
72+
configuration = Configuration()
73+
configuration.workers = 8
74+
assert example == make_configuration_file(configuration)

0 commit comments

Comments
 (0)