bvolpato opened a new pull request, #40507:
URL: https://github.com/apache/beam/pull/40507

   `AsyncWrapper.teardown()` can close the wrapped DoFn's resources while its 
background work is still using them. A normal bounded DirectRunner pipeline 
reproduces this: the pipeline reports `DONE`, then a delayed file write fails 
with `ValueError: I/O operation on closed file.` The output file is empty.
   
   Wait for work owned by that wrapper instance before forwarding teardown. 
Threadpool futures and asyncio tasks are tracked per instance, so teardown does 
not wait for another clone's work or shut down the shared executor. The asyncio 
path waits for actual coroutine cleanup, including cancellation initiated by 
the existing timer path.
   
   ## Runner-level reproduction
   
   The following uses an eager `process()` method that returns no outputs, so 
the failure is independent of the generator handling addressed by #40000. Run 
with the SDK being compared installed:
   
   ```python
   #!/usr/bin/env -S uv run --script
   # /// script
   # requires-python = ">=3.12"
   # dependencies = ["apache-beam"]
   # ///
   from pathlib import Path
   import tempfile
   import time
   
   import apache_beam as beam
   from apache_beam.transforms.async_dofn import AsyncWrapper
   
   
   class FileWriter(beam.DoFn):
       def __init__(self, directory):
           self.directory = directory
   
       def setup(self):
           self.file = open(Path(self.directory) / "output.txt", "w")
   
       def process(self, element):
           time.sleep(0.5)
           self.file.write(element[1])
           self.file.flush()
   
       def teardown(self):
           self.file.close()
   
   
   with tempfile.TemporaryDirectory() as directory:
       wrapper = AsyncWrapper(FileWriter(directory), parallelism=1)
       pipeline = beam.Pipeline(runner="DirectRunner")
       pipeline | beam.Create([("orders", "order-123")]) | beam.ParDo(wrapper)
       print("Pipeline state:", pipeline.run().wait_until_finish())
       for _, future in 
AsyncWrapper._processing_elements[wrapper._uuid].values():
           try:
               future.result(timeout=5)
           except Exception as error:
               print("Background failure:", type(error).__name__ + ":", error)
       print("Written file:", repr((Path(directory) / 
"output.txt").read_text()))
   AsyncWrapper.reset_state()
   ```
   
   Current upstream:
   
   ```text
   Pipeline state: DONE
   Background failure: ValueError: I/O operation on closed file.
   Written file: ''
   ```
   
   With this fix:
   
   ```text
   Pipeline state: DONE
   Written file: 'order-123'
   ```
   
   The same resource-lifetime failure and successful fix were reproduced with 
`use_asyncio=True`. The normal DirectRunner end-of-bundle teardown initiates 
the failure; the reproducer does not call teardown manually.
   
   ## Validation
   
   Focused regressions cover runner-level file lifetime, instance ownership, 
cancelled queued threadpool work, and running or queued asyncio cancellation. 
They fail against unchanged upstream and pass with the fix. The fix also passed 
independent review and a separate runner reproduction.
   
   From `sdks/python`, with the SDK test dependencies installed:
   
   ```sh
   python -m pytest apache_beam/transforms/async_dofn_test.py -q \
     -k 'not test_load_correctness'
   ```
   
   The final-source non-random async suite, YAPF, pycodestyle, and whitespace 
checks pass. Local validation loaded the complete selected AsyncWrapper module 
over the existing SDK dependency environment. The relevant DirectRunner 
implementation files are identical to current upstream; this was not a full SDK 
rebuild.
   
   ## Tradeoff
   
   Teardown now waits for the instance's active work to finish, so a hung 
operation can delay teardown. This preserves the resource lifetime required by 
that work. The shared pool and event loop remain available to other instances. 
The change does not alter pending-output delivery or runner timer behavior.
   
   ------------------------
   
   - [ ] If this contribution is large, please file an Apache [Individual 
Contributor License Agreement](https://www.apache.org/licenses/icla.pdf).
   - [x] Replace these instructions with a description of your change and why 
you have made it.
   - [ ] Mention the appropriate issue in your description (for example: 
`addresses #123` or `fixes #456`).
   - [x] Update `CHANGES.md` with noteworthy changes.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to