tvalentyn opened a new issue, #40673:
URL: https://github.com/apache/beam/issues/40673

   ### What happened?
   
   Beam's MulitProcessShared and allows creating an object in a dedicated 
process, and interact with the object in other processes through proxy objects. 
Beam uses Python multiprocessing package to orchestrate this.
   
   It appears that when an SDK harness process interacts with the shared handle 
owned by the same process, a deadlock can happen if
   - there is a cyclic  reference that includes a proxy object.
   - GC happens while a thread running a portion of `multiprocessing` inner 
machinery is active.
   - The nature of the deadlock is: a lifecycle method on the proxy is 
initiating a synchronous action that has to be served by a thread responsible 
for some portion of `multiprocessing` machinery, but that thread won't schedule 
until GC is finished, hence deadlock.  
   
   Still figuring the details out,  but 1) I encountered this on an inference 
pipeline running on Dataflow and 2) there is a repro.
   
   ```
   import gc
   import multiprocessing.connection
   import threading
   import uuid
   
   from apache_beam.utils import multi_process_shared
   
   
   class Counter:
     def __init__(self):
       self._n = 0
   
     def incr(self):
       self._n += 1
       return self._n
   
   
   _original_accept = multiprocessing.connection.Listener.accept
   
   
   def _accept_then_gc(self):
     conn = _original_accept(self)
     if threading.current_thread().name.endswith('(accepter)'):
       gc.collect()  # Stand-in for a GC pass triggered by an allocation here.
     return conn
   
   
   def acquire_and_call(tag, result):
     shared = multi_process_shared.MultiProcessShared(
         Counter, tag=tag, always_proxy=True)
     result.append(shared.acquire().incr())
   
   
   def main():
     multiprocessing.connection.Listener.accept = _accept_then_gc
     tag = 'mps_gc_deadlock_' + uuid.uuid4().hex
   
     result = []
     acquire_and_call(tag, result)  # Starts the in-process server.
     print('before: acquire()+incr() ->', result)
   
     # A proxy to the server becomes cyclic garbage, so only the cyclic GC can
     # reclaim it, on whatever thread the GC happens to run.
     gc.disable()
     victim = multi_process_shared.MultiProcessShared(
         Counter, tag=tag, always_proxy=True).acquire()
     cycle = {'proxy': victim}
     cycle['self'] = cycle
     del victim, cycle
   
     result = []
     t = threading.Thread(target=acquire_and_call, args=(tag, result), 
daemon=True)
     t.start()
     t.join(timeout=15)
     if t.is_alive():
       print('after: acquire() still blocked after 15s -> accepter deadlocked')
     else:
       print('after: acquire()+incr() ->', result)
   
   
   if __name__ == '__main__':
     main()
   ```
   
   
   
     
   
   ### Issue Priority
   
   Priority: 2 (default / most bugs should be filed as P2)
   
   ### Issue Components
   
   - [x] Component: Python SDK
   - [ ] Component: Java SDK
   - [ ] Component: Go SDK
   - [ ] Component: Typescript SDK
   - [ ] Component: IO connector
   - [ ] Component: Beam YAML
   - [ ] Component: Beam examples
   - [ ] Component: Beam playground
   - [ ] Component: Beam katas
   - [ ] Component: Website
   - [ ] Component: Infrastructure
   - [ ] Component: Spark Runner
   - [ ] Component: Flink Runner
   - [ ] Component: Prism Runner
   - [ ] Component: Twister2 Runner
   - [ ] Component: Hazelcast Jet Runner
   - [ ] Component: Google Cloud Dataflow Runner


-- 
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