0
# Custom Library Support
1
2
Extend threadpoolctl with custom library controllers for additional thread pool libraries not supported out of the box. This capability allows integration of custom or newer thread pool implementations.
3
4
## Capabilities
5
6
### LibController Base Class
7
8
Abstract base class that defines the interface for all library controllers.
9
10
```python { .api }
11
class LibController:
12
"""
13
Abstract base class for individual library controllers.
14
15
Subclasses must define class attributes and implement abstract methods
16
to support a specific thread pool library implementation.
17
18
Class Attributes (required):
19
user_api: str
20
Standardized API name ('blas', 'openmp', or custom)
21
internal_api: str
22
Implementation-specific identifier (unique name)
23
filename_prefixes: tuple[str, ...]
24
Shared library filename prefixes for detection
25
check_symbols: tuple[str, ...] (optional)
26
Symbol names to verify library compatibility
27
"""
28
29
def __init__(self, *, filepath=None, prefix=None, parent=None):
30
"""
31
Initialize library controller (not meant to be overridden).
32
33
Args:
34
filepath: str - Path to shared library
35
prefix: str - Matched filename prefix
36
parent: ThreadpoolController - Parent controller instance
37
"""
38
39
def info(self):
40
"""
41
Return library information dictionary.
42
43
Returns:
44
dict: Library info with standard keys plus any additional attributes
45
"""
46
47
@property
48
def num_threads(self):
49
"""
50
Current thread limit (dynamic property).
51
52
Returns:
53
int: Current maximum thread count
54
"""
55
56
def get_num_threads(self):
57
"""
58
Get current maximum thread count (abstract method).
59
60
Must be implemented by subclasses.
61
62
Returns:
63
int | None: Current thread limit, None if unavailable
64
"""
65
66
def set_num_threads(self, num_threads):
67
"""
68
Set maximum thread count (abstract method).
69
70
Must be implemented by subclasses.
71
72
Args:
73
num_threads: int - New thread limit
74
75
Returns:
76
Any: Implementation-specific return value
77
"""
78
79
def get_version(self):
80
"""
81
Get library version (abstract method).
82
83
Must be implemented by subclasses.
84
85
Returns:
86
str | None: Version string, None if unavailable
87
"""
88
89
def set_additional_attributes(self):
90
"""
91
Set implementation-specific attributes.
92
93
Called during initialization to set custom attributes that will
94
be included in the info() dictionary. Override to add custom fields.
95
"""
96
```
97
98
### Controller Registration
99
100
Register custom controllers with the threadpoolctl system.
101
102
```python { .api }
103
def register(controller):
104
"""
105
Register a new library controller class.
106
107
Adds the controller to the global registry so it will be used
108
during library discovery in future ThreadpoolController instances.
109
110
Args:
111
controller: type[LibController] - LibController subclass to register
112
"""
113
```
114
115
### Creating Custom Controllers
116
117
```python
118
from threadpoolctl import LibController, register
119
import ctypes
120
121
class MyCustomController(LibController):
122
"""Controller for a custom thread pool library."""
123
124
# Required class attributes
125
user_api = "custom" # Or "blas"/"openmp" if it fits those categories
126
internal_api = "mycustomlib"
127
filename_prefixes = ("libmycustom", "mycustomlib")
128
129
# Optional: symbols to check for library compatibility
130
check_symbols = (
131
"mycustom_get_threads",
132
"mycustom_set_threads",
133
"mycustom_get_version"
134
)
135
136
def get_num_threads(self):
137
"""Get current thread count from the library."""
138
get_func = getattr(self.dynlib, "mycustom_get_threads", None)
139
if get_func is not None:
140
return get_func()
141
return None
142
143
def set_num_threads(self, num_threads):
144
"""Set thread count in the library."""
145
set_func = getattr(self.dynlib, "mycustom_set_threads", None)
146
if set_func is not None:
147
return set_func(num_threads)
148
return None
149
150
def get_version(self):
151
"""Get library version string."""
152
version_func = getattr(self.dynlib, "mycustom_get_version", None)
153
if version_func is not None:
154
version_func.restype = ctypes.c_char_p
155
version_bytes = version_func()
156
if version_bytes:
157
return version_bytes.decode('utf-8')
158
return None
159
160
def set_additional_attributes(self):
161
"""Set custom attributes for this library."""
162
# Add any custom attributes that should appear in info()
163
self.custom_attribute = self._get_custom_info()
164
165
def _get_custom_info(self):
166
"""Helper method to get custom library information."""
167
# Implementation-specific logic
168
return "custom_value"
169
170
# Register the controller
171
register(MyCustomController)
172
```
173
174
### Advanced Custom Controller Examples
175
176
#### BLAS-Compatible Controller
177
178
```python
179
from threadpoolctl import LibController, register
180
import ctypes
181
182
class CustomBLASController(LibController):
183
"""Controller for a custom BLAS implementation."""
184
185
user_api = "blas"
186
internal_api = "customblas"
187
filename_prefixes = ("libcustomblas",)
188
check_symbols = (
189
"customblas_get_num_threads",
190
"customblas_set_num_threads",
191
"customblas_get_config"
192
)
193
194
def get_num_threads(self):
195
get_func = getattr(self.dynlib, "customblas_get_num_threads", None)
196
if get_func:
197
threads = get_func()
198
# Handle special return values (like OpenBLAS -1 = sequential)
199
return 1 if threads == -1 else threads
200
return None
201
202
def set_num_threads(self, num_threads):
203
set_func = getattr(self.dynlib, "customblas_set_num_threads", None)
204
if set_func:
205
return set_func(num_threads)
206
return None
207
208
def get_version(self):
209
config_func = getattr(self.dynlib, "customblas_get_config", None)
210
if config_func:
211
config_func.restype = ctypes.c_char_p
212
config = config_func()
213
if config:
214
# Parse version from config string
215
config_str = config.decode('utf-8')
216
# Extract version using custom parsing logic
217
return self._parse_version(config_str)
218
return None
219
220
def set_additional_attributes(self):
221
"""Add BLAS-specific attributes."""
222
self.threading_layer = self._get_threading_layer()
223
self.architecture = self._get_architecture()
224
225
def _parse_version(self, config_str):
226
"""Parse version from config string."""
227
# Custom parsing logic
228
import re
229
match = re.search(r'version\s+(\d+\.\d+\.\d+)', config_str, re.IGNORECASE)
230
return match.group(1) if match else None
231
232
def _get_threading_layer(self):
233
"""Determine threading layer."""
234
# Custom logic to determine threading backend
235
return "openmp" # or "pthreads", "disabled", etc.
236
237
def _get_architecture(self):
238
"""Get target architecture."""
239
# Custom logic to get architecture info
240
return "x86_64"
241
242
register(CustomBLASController)
243
```
244
245
#### Controller with Dynamic Methods
246
247
```python
248
from threadpoolctl import LibController, register
249
import ctypes
250
251
class FlexibleController(LibController):
252
"""Controller that adapts to different library versions."""
253
254
user_api = "custom"
255
internal_api = "flexlib"
256
filename_prefixes = ("libflex",)
257
258
def __init__(self, **kwargs):
259
super().__init__(**kwargs)
260
# Determine available methods during initialization
261
self._detect_capabilities()
262
263
def _detect_capabilities(self):
264
"""Detect which methods are available in this library version."""
265
self.has_v1_api = hasattr(self.dynlib, "flex_get_threads_v1")
266
self.has_v2_api = hasattr(self.dynlib, "flex_get_threads_v2")
267
self.has_version_api = hasattr(self.dynlib, "flex_version")
268
269
def get_num_threads(self):
270
if self.has_v2_api:
271
return self.dynlib.flex_get_threads_v2()
272
elif self.has_v1_api:
273
return self.dynlib.flex_get_threads_v1()
274
return None
275
276
def set_num_threads(self, num_threads):
277
if self.has_v2_api:
278
return self.dynlib.flex_set_threads_v2(num_threads)
279
elif self.has_v1_api:
280
return self.dynlib.flex_set_threads_v1(num_threads)
281
return None
282
283
def get_version(self):
284
if self.has_version_api:
285
version_func = self.dynlib.flex_version
286
version_func.restype = ctypes.c_char_p
287
return version_func().decode('utf-8')
288
return None
289
290
def set_additional_attributes(self):
291
self.api_version = "v2" if self.has_v2_api else ("v1" if self.has_v1_api else "unknown")
292
293
register(FlexibleController)
294
```
295
296
### Using Custom Controllers
297
298
```python
299
from threadpoolctl import ThreadpoolController, threadpool_info
300
301
# After registering custom controllers, they work like built-in ones
302
controller = ThreadpoolController()
303
304
# Custom controllers appear in standard introspection
305
info = threadpool_info()
306
custom_libs = [lib for lib in info if lib['user_api'] == 'custom']
307
print(f"Found {len(custom_libs)} custom thread pool libraries")
308
309
# Custom controllers work with selection and limiting
310
custom_controller = controller.select(internal_api='mycustomlib')
311
if custom_controller:
312
with custom_controller.limit(limits=2):
313
# Custom library limited to 2 threads
314
result = computation_using_custom_library()
315
```
316
317
### Error Handling in Custom Controllers
318
319
```python
320
from threadpoolctl import LibController, register
321
import ctypes
322
323
class RobustController(LibController):
324
"""Controller with comprehensive error handling."""
325
326
user_api = "custom"
327
internal_api = "robustlib"
328
filename_prefixes = ("librobust",)
329
330
def get_num_threads(self):
331
try:
332
get_func = getattr(self.dynlib, "robust_get_threads", None)
333
if get_func:
334
result = get_func()
335
# Validate result
336
if isinstance(result, int) and result > 0:
337
return result
338
except (AttributeError, OSError, ValueError) as e:
339
# Log error but don't crash
340
print(f"Warning: Could not get thread count from {self.internal_api}: {e}")
341
return None
342
343
def set_num_threads(self, num_threads):
344
if not isinstance(num_threads, int) or num_threads < 1:
345
raise ValueError(f"Invalid thread count: {num_threads}")
346
347
try:
348
set_func = getattr(self.dynlib, "robust_set_threads", None)
349
if set_func:
350
return set_func(num_threads)
351
except (AttributeError, OSError) as e:
352
print(f"Warning: Could not set thread count in {self.internal_api}: {e}")
353
return None
354
355
def get_version(self):
356
try:
357
version_func = getattr(self.dynlib, "robust_version", None)
358
if version_func:
359
version_func.restype = ctypes.c_char_p
360
version_bytes = version_func()
361
if version_bytes:
362
return version_bytes.decode('utf-8', errors='ignore')
363
except Exception as e:
364
print(f"Warning: Could not get version from {self.internal_api}: {e}")
365
return None
366
367
register(RobustController)
368
```