@@ -247,6 +247,63 @@ def build(_): |
247 | 247 | assert all(_run_in_threads(build, n_threads=8)) |
248 | 248 |
|
249 | 249 |
|
| 250 | +@freethreaded_only |
| 251 | +def test_concurrent_clr_delegate_invocation_from_python(): |
| 252 | + """Python callables wrapped as distinct CLR delegate types, invoked concurrently. |
| 253 | +
|
| 254 | + Real-world: QuantConnect/Lean and similar embedders pass Python callables |
| 255 | + where C# expects a delegate; under FT the dispatcher emit + Invoke run from |
| 256 | + multiple threads. Hits DelegateManager.GetDispatcher (Reflection.Emit lock) |
| 257 | + and Dispatcher.Dispatch (Py.GIL reacquisition). |
| 258 | + """ |
| 259 | + from Python.Test import ( |
| 260 | + PublicDelegate, StringDelegate, BoolDelegate, |
| 261 | + ) |
| 262 | + |
| 263 | + delegates = ( |
| 264 | + PublicDelegate(lambda: None), |
| 265 | + StringDelegate(lambda: "ok"), |
| 266 | + BoolDelegate(lambda: True), |
| 267 | + ) |
| 268 | + |
| 269 | + def fire(i): |
| 270 | + d = delegates[i % len(delegates)] |
| 271 | + for _ in range(200): |
| 272 | + d() |
| 273 | + return True |
| 274 | + |
| 275 | + assert all(_run_in_threads(fire, n_threads=8)) |
| 276 | + |
| 277 | + |
| 278 | +@freethreaded_only |
| 279 | +def test_concurrent_generic_type_binding(): |
| 280 | + """Concurrent `Dictionary[K, V]` with many distinct type-arg pairs. |
| 281 | +
|
| 282 | + Real-world: pythonnet/pythonnet#2269, #1407, #821 — concurrent ToPython / |
| 283 | + GenericByName from N threads. Exercises ClassManager.cache, |
| 284 | + TypeManager.cache, GenericUtil.mapping, and the generic-type binding |
| 285 | + fast path together. |
| 286 | +
|
| 287 | + FT-only: the cumulative state under the full pytest suite trips the same |
| 288 | + pre-existing CPython 3.11/3.12/3.13 GIL-build crash that gates the other |
| 289 | + high-contention tests in this file. |
| 290 | + """ |
| 291 | + from System import Int32, Int64, String, Double, Single, Byte |
| 292 | + from System.Collections.Generic import Dictionary, List |
| 293 | + |
| 294 | + arg_types = (Int32, Int64, String, Double, Single, Byte) |
| 295 | + pairs = [(k, v) for k in arg_types for v in arg_types] |
| 296 | + |
| 297 | + def bind(_): |
| 298 | + for _ in range(50): |
| 299 | + for k, v in pairs: |
| 300 | + _ = Dictionary[k, v] |
| 301 | + _ = List[k] |
| 302 | + return True |
| 303 | + |
| 304 | + assert all(_run_in_threads(bind, n_threads=8)) |
| 305 | + |
| 306 | + |
250 | 307 | @freethreaded_only |
251 | 308 | def test_concurrent_shutdown_handler_register(): |
252 | 309 | """Concurrent AddShutdownHandler/RemoveShutdownHandler — exercises ShutdownHandlers list. |
|