6868# Keep a set of completed instructions to discard late received data. The set
6969# can have up to _MAX_CLEANED_INSTRUCTIONS items. See _GrpcDataChannel.
7070_MAX_CLEANED_INSTRUCTIONS = 10000
71+ _DEFAULT_SEND_QUEUE_MAX_ELEMENTS = 10000
72+ _DEFAULT_SEND_QUEUE_MAX_BYTES = 100 << 20 # 100MB
73+ _DEFAULT_RECEIVE_QUEUE_MAX_ELEMENTS = 5
7174
7275# retry on transient UNAVAILABLE grpc error from data channels.
7376_GRPC_SERVICE_CONFIG = json .dumps ({
@@ -459,10 +462,20 @@ def __init__(self, data_buffer_time_limit_ms=0):
459462
460463 self ._data_buffer_time_limit_ms = data_buffer_time_limit_ms
461464 self ._to_send = ByteLimitedQueue (
462- maxsize = 10000 ,
463- maxbytes = 100 << 20 ) # type: ByteLimitedQueue[DataOrTimers]
465+ maxsize = _DEFAULT_SEND_QUEUE_MAX_ELEMENTS ,
466+ maxbytes = _DEFAULT_SEND_QUEUE_MAX_BYTES
467+ ) # type: ByteLimitedQueue[DataOrTimers]
468+ # Staging queue so a full send buffer does not block reading inputs.
469+ self ._pending_send = ByteLimitedQueue (
470+ maxsize = _DEFAULT_SEND_QUEUE_MAX_ELEMENTS ,
471+ maxbytes = _DEFAULT_SEND_QUEUE_MAX_BYTES
472+ ) # type: ByteLimitedQueue[DataOrTimers]
473+ self ._send_forwarder = None # type: Optional[threading.Thread]
474+ self ._start_send_forwarder ()
464475 self ._received = collections .defaultdict (
465- lambda : ByteLimitedQueue (maxsize = 5 , maxbytes = 100 << 20 )
476+ lambda : ByteLimitedQueue (
477+ maxsize = _DEFAULT_RECEIVE_QUEUE_MAX_ELEMENTS ,
478+ maxbytes = _DEFAULT_SEND_QUEUE_MAX_BYTES )
466479 ) # type: DefaultDict[str, ByteLimitedQueue[DataOrTimers]]
467480
468481 # Keep a cache of completed instructions. Data for completed instructions
@@ -478,9 +491,38 @@ def __init__(self, data_buffer_time_limit_ms=0):
478491
479492 def close (self ):
480493 # type: () -> None
481- self ._to_send .put (self ._WRITES_FINISHED , 0 )
494+ self ._enqueue_to_send (self ._WRITES_FINISHED )
495+ if self ._send_forwarder is not None :
496+ self ._send_forwarder .join ()
482497 self ._closed = True
483498
499+ def _start_send_forwarder (self ):
500+ # type: () -> None
501+ forwarder = threading .Thread (
502+ target = self ._forward_pending_to_send ,
503+ name = 'forward_grpc_outputs' )
504+ forwarder .daemon = True
505+ forwarder .start ()
506+ self ._send_forwarder = forwarder
507+
508+ def _enqueue_to_send (self , elem ):
509+ # type: (DataOrTimers) -> None
510+ self ._pending_send .put (elem , self ._get_element_size_bytes (elem ))
511+
512+ def _forward_pending_to_send (self ):
513+ # type: () -> None
514+ try :
515+ while True :
516+ elem = self ._pending_send .get ()
517+ self ._to_send .put (elem , self ._get_element_size_bytes (elem ))
518+ if elem is self ._WRITES_FINISHED :
519+ return
520+ except Exception as e :
521+ if not self ._closed :
522+ _LOGGER .exception ('Failed to forward outputs in the data plane.' )
523+ self ._exception = e
524+ raise
525+
484526 def wait (self , timeout = None ):
485527 # type: (Optional[int]) -> None
486528 self ._reads_finished .wait (timeout )
@@ -591,7 +633,7 @@ def add_to_send_queue(data):
591633 if data :
592634 elem = beam_fn_api_pb2 .Elements .Data (
593635 instruction_id = instruction_id , transform_id = transform_id , data = data )
594- self ._to_send . put (elem , self . _get_element_size_bytes ( elem ) )
636+ self ._enqueue_to_send (elem )
595637
596638 def close_callback (data ):
597639 # type: (bytes) -> None
@@ -601,7 +643,7 @@ def close_callback(data):
601643 instruction_id = instruction_id ,
602644 transform_id = transform_id ,
603645 is_last = True )
604- self ._to_send . put (elem , self . _get_element_size_bytes ( elem ) )
646+ self ._enqueue_to_send (elem )
605647
606648 return ClosableOutputStream .create (
607649 close_callback , add_to_send_queue , self ._data_buffer_time_limit_ms )
@@ -622,7 +664,7 @@ def add_to_send_queue(timer):
622664 timer_family_id = timer_family_id ,
623665 timers = timer ,
624666 is_last = False )
625- self ._to_send . put (elem , self . _get_element_size_bytes ( elem ) )
667+ self ._enqueue_to_send (elem )
626668
627669 def close_callback (timer ):
628670 # type: (bytes) -> None
@@ -632,7 +674,7 @@ def close_callback(timer):
632674 transform_id = transform_id ,
633675 timer_family_id = timer_family_id ,
634676 is_last = True )
635- self ._to_send . put (elem , self . _get_element_size_bytes ( elem ) )
677+ self ._enqueue_to_send (elem )
636678
637679 return ClosableOutputStream .create (
638680 close_callback , add_to_send_queue , self ._data_buffer_time_limit_ms )
0 commit comments