11import grpc
2+
3+ from dapr .clients .exceptions import StreamInactiveError
4+ from dapr .clients .grpc ._response import TopicEventResponse
25from dapr .proto import api_v1 , appcallback_v1
36import queue
47import threading
@@ -24,9 +27,11 @@ def __init__(self, stub, pubsub_name, topic, metadata=None, dead_letter_topic=No
2427 self .metadata = metadata or {}
2528 self .dead_letter_topic = dead_letter_topic or ''
2629 self ._stream = None
30+ self ._response_thread = None
2731 self ._send_queue = queue .Queue ()
2832 self ._receive_queue = queue .Queue ()
2933 self ._stream_active = False
34+ self ._stream_lock = threading .Lock () # Protects _stream_active
3035
3136 def start (self ):
3237 def request_iterator ():
@@ -38,65 +43,112 @@ def request_iterator():
3843 dead_letter_topic = self .dead_letter_topic or '' ))
3944 yield initial_request
4045
41- while self ._stream_active :
46+ while self ._is_stream_active () :
4247 try :
43- request = self ._send_queue .get ()
44- if request is None :
45- break
46-
47- yield request
48+ yield self ._send_queue .get () # TODO Should I add a timeout?
4849 except queue .Empty :
4950 continue
5051 except Exception as e :
51- print (f"Exception in request_iterator: { e } " )
52- raise e
52+ raise Exception (f"Error in request iterator: { e } " )
5353
5454 # Create the bidirectional stream
5555 self ._stream = self ._stub .SubscribeTopicEventsAlpha1 (request_iterator ())
56- self ._stream_active = True
56+ self ._set_stream_active ()
5757
5858 # Start a thread to handle incoming messages
59- threading .Thread (target = self ._handle_responses , daemon = True ).start ()
59+ self ._response_thread = threading .Thread (target = self ._handle_responses , daemon = True )
60+ self ._response_thread .start ()
6061
6162 def _handle_responses (self ):
6263 try :
6364 # The first message dapr sends on the stream is for signalling only, so discard it
6465 next (self ._stream )
6566
66- for msg in self ._stream :
67- print (f"Received message from dapr on stream: { msg .event_message .id } " ) # SubscribeTopicEventsResponseAlpha1
68- self ._receive_queue .put (msg .event_message )
67+ # Read messages from the stream and put them in the receive queue
68+ for message in self ._stream :
69+ if self ._is_stream_active ():
70+ self ._receive_queue .put (message .event_message )
71+ else :
72+ break
6973 except grpc .RpcError as e :
70- print (f"gRPC error in stream: { e } " )
74+ if e .code () != grpc .StatusCode .CANCELLED :
75+ print (f"gRPC error in stream: { e .details ()} , Status Code: { e .code ()} " )
7176 except Exception as e :
72- print (f"Unexpected error in stream : { e } " )
77+ raise Exception (f"Error while handling responses : { e } " )
7378 finally :
74- self ._stream_active = False
79+ self ._set_stream_inactive ()
7580
76- def next_message (self , timeout = None ):
77- print ("in next_message" )
78- try :
79- return self ._receive_queue .get (timeout = timeout )
80- except queue .Empty as e :
81- print ("queue empty" , e )
82- return None
83- except Exception as e :
84- print (f"Exception in next_message: { e } " )
85- return None
81+ def next_message (self , timeout = 1 ):
82+ """
83+ Gets the next message from the receive queue
84+ @param timeout: Timeout in seconds
85+ @return: The next message
86+ """
87+ return self .read_message_from_queue (self ._receive_queue , timeout = timeout )
8688
87- def respond (self , message , status ):
89+ def _respond (self , message , status ):
8890 try :
8991 status = appcallback_v1 .TopicEventResponse (status = status .value )
9092 response = api_v1 .SubscribeTopicEventsRequestProcessedAlpha1 (id = message .id ,
9193 status = status )
9294 msg = api_v1 .SubscribeTopicEventsRequestAlpha1 (event_processed = response )
9395
94- self ._send_queue . put ( msg )
96+ self .send_message_to_queue ( self . _send_queue , msg )
9597 except Exception as e :
9698 print (f"Exception in send_message: { e } " )
9799
100+ def respond_success (self , message ):
101+ self ._respond (message , TopicEventResponse ('success' ).status )
102+
103+ def respond_retry (self , message ):
104+ self ._respond (message , TopicEventResponse ('retry' ).status )
105+
106+ def respond_drop (self , message ):
107+ self ._respond (message , TopicEventResponse ('drop' ).status )
108+
109+ def send_message_to_queue (self , q , message ):
110+ if not self ._is_stream_active ():
111+ raise StreamInactiveError ("Stream is not active" )
112+ q .put (message )
113+
114+ def read_message_from_queue (self , q , timeout ):
115+ if not self ._is_stream_active ():
116+ raise StreamInactiveError ("Stream is not active" )
117+ try :
118+ return q .get (timeout = timeout )
119+ except queue .Empty :
120+ return None
121+
122+ def _set_stream_active (self ):
123+ with self ._stream_lock :
124+ self ._stream_active = True
125+
126+ def _set_stream_inactive (self ):
127+ with self ._stream_lock :
128+ self ._stream_active = False
129+
130+ def _is_stream_active (self ):
131+ with self ._stream_lock :
132+ return self ._stream_active
133+
98134 def close (self ):
99- self ._stream_active = False
100- self ._send_queue .put (None )
135+ if not self ._is_stream_active ():
136+ return
137+
138+ self ._set_stream_inactive ()
139+
140+ # Cancel the stream
101141 if self ._stream :
102- self ._stream .cancel ()
142+ try :
143+ self ._stream .cancel ()
144+ except grpc .RpcError as e :
145+ if e .code () != grpc .StatusCode .CANCELLED :
146+ raise Exception (f"Error while closing stream: { e } " )
147+ except Exception as e :
148+ raise Exception (f"Error while closing stream: { e } " )
149+
150+ # Join the response-handling thread to ensure it has finished
151+ if self ._response_thread :
152+ self ._response_thread .join ()
153+ self ._response_thread = None
154+
0 commit comments