@@ -74,52 +74,104 @@ def process_chunk(chunk: list[In]) -> list[Out]:
74
74
75
75
Args:
76
76
chunk: The data chunk to process.
77
- shared_context: The shared context for processing.
78
77
79
78
Returns:
80
79
The processed chunk.
81
80
"""
82
- return transformer (chunk , shared_context ) # type: ignore
81
+ return transformer (chunk , shared_context )
83
82
84
83
def _ordered_generator (chunks_iter : Iterator [list [In ]], executor : ThreadPoolExecutor ) -> Iterator [list [Out ]]:
85
84
"""Generate results in their original order."""
86
85
futures : deque [Future [list [Out ]]] = deque ()
86
+ executor_shutdown = False
87
87
88
88
# Pre-submit initial batch of futures
89
- for _ in range (min (self .max_workers , 10 )): # Limit initial submissions
89
+ for _ in range (min (self .max_workers , 10 )):
90
+ if executor_shutdown :
91
+ break
90
92
try :
91
93
chunk = next (chunks_iter )
92
94
futures .append (executor .submit (process_chunk , chunk ))
93
95
except StopIteration :
94
96
break
97
+ except RuntimeError as e :
98
+ if "cannot schedule new futures after shutdown" in str (e ):
99
+ executor_shutdown = True
100
+ break
101
+ raise
95
102
96
103
while futures :
97
- # Get the next result and submit the next chunk
98
- result = futures .popleft ().result ()
99
- yield result
100
-
101
104
try :
102
- chunk = next (chunks_iter )
103
- futures .append (executor .submit (process_chunk , chunk ))
104
- except StopIteration :
105
- continue
105
+ # Get the next result
106
+ result = futures .popleft ().result ()
107
+ yield result
108
+
109
+ # Try to submit the next chunk only if executor is not shutdown
110
+ if not executor_shutdown :
111
+ try :
112
+ chunk = next (chunks_iter )
113
+ futures .append (executor .submit (process_chunk , chunk ))
114
+ except StopIteration :
115
+ continue
116
+ except RuntimeError as e :
117
+ if "cannot schedule new futures after shutdown" in str (e ):
118
+ executor_shutdown = True
119
+ continue
120
+ raise
121
+ except Exception :
122
+ # Cancel remaining futures and re-raise
123
+ for future in futures :
124
+ try :
125
+ future .cancel ()
126
+ except Exception :
127
+ pass # Ignore cancellation errors
128
+ futures .clear ()
129
+ raise
106
130
107
131
def _unordered_generator (chunks_iter : Iterator [list [In ]], executor : ThreadPoolExecutor ) -> Iterator [list [Out ]]:
108
132
"""Generate results as they complete."""
133
+ futures = set ()
134
+ executor_shutdown = False
135
+
109
136
# Pre-submit initial batch
110
- futures = {
111
- executor .submit (process_chunk , chunk ) for chunk in itertools .islice (chunks_iter , min (self .max_workers , 10 ))
112
- }
137
+ for chunk in itertools .islice (chunks_iter , min (self .max_workers , 10 )):
138
+ if executor_shutdown :
139
+ break
140
+ try :
141
+ futures .add (executor .submit (process_chunk , chunk ))
142
+ except RuntimeError as e :
143
+ if "cannot schedule new futures after shutdown" in str (e ):
144
+ executor_shutdown = True
145
+ break
146
+ raise
113
147
114
148
while futures :
115
- done , futures = wait (futures , return_when = FIRST_COMPLETED )
116
- for future in done :
117
- yield future .result ()
118
- try :
119
- chunk = next (chunks_iter )
120
- futures .add (executor .submit (process_chunk , chunk ))
121
- except StopIteration :
122
- continue
149
+ try :
150
+ done , futures = wait (futures , return_when = FIRST_COMPLETED )
151
+ for future in done :
152
+ yield future .result ()
153
+
154
+ # Try to submit next chunk only if executor is not shutdown
155
+ if not executor_shutdown :
156
+ try :
157
+ chunk = next (chunks_iter )
158
+ futures .add (executor .submit (process_chunk , chunk ))
159
+ except StopIteration :
160
+ continue
161
+ except RuntimeError as e :
162
+ if "cannot schedule new futures after shutdown" in str (e ):
163
+ executor_shutdown = True
164
+ continue
165
+ raise
166
+ except Exception :
167
+ # Cancel remaining futures and re-raise
168
+ for future in futures :
169
+ try :
170
+ future .cancel ()
171
+ except Exception :
172
+ pass # Ignore cancellation errors
173
+ futures .clear ()
174
+ raise
123
175
124
176
# Use the reusable thread pool instead of creating a new one
125
177
executor = self ._get_thread_pool (self .max_workers )
@@ -129,10 +181,3 @@ def _unordered_generator(chunks_iter: Iterator[list[In]], executor: ThreadPoolEx
129
181
# Process chunks using the reusable executor
130
182
for result_chunk in gen_func (chunks_to_process , executor ):
131
183
yield from result_chunk
132
-
133
- def __del__ (self ) -> None :
134
- """Shutdown all cached thread pools. Call this during application cleanup."""
135
- with self ._pool_lock :
136
- for pool in self ._thread_pools .values ():
137
- pool .shutdown (wait = True )
138
- self ._thread_pools .clear ()
0 commit comments