77from ..buffer import Buffer
88from ..sentry import Sentry
99from ..streammsgbuilder import StreamMsgBuilder
10- from ..interfaces import ISession , IWebStream
10+ from ..interfaces import ISession , IWebStream , IWebStreamHelper
1111from ..debugprofiler import DebugProfiler , DebugProfilerFeatures
1212
1313from ..Proto import WebStreamMsg
@@ -36,9 +36,7 @@ def __init__(self, group:Any=None, target:Any=None, name:Any=None, args:Any=(),
3636 self .HasSentCloseMessage = False
3737 self .StateLock = threading .Lock ()
3838 self .MsgQueue :queue .Queue [Optional [WebStreamMsg .WebStreamMsg ]] = queue .Queue ()
39- self .HttpHelper :Optional [WebStreamHttpHelper ] = None
40- self .WsHelper :Optional [WebStreamWsHelper ] = None
41- self .IsHelperClosed = False
39+ self .WebStreamHelper :Optional [IWebStreamHelper ] = None
4240 self .OpenedTime = time .time ()
4341 self .ClosedDueToRequestConnectionError = False
4442
@@ -95,31 +93,21 @@ def OnIncomingServerMessage(self, webStreamMsg:WebStreamMsg.WebStreamMsg) -> Non
9593 # This is called from the main socket receive thread, so it should
9694 # execute as quickly as possible.
9795 def Close (self ) -> None :
98- # Check the state and set the flag. Only allow this code to run
99- # once.
100- localHttpHelper :Optional [WebStreamHttpHelper ] = None
101- localWsHelper :Optional [WebStreamWsHelper ] = None
96+ streamHelper :Optional [IWebStreamHelper ] = None
10297
98+ # Check the state and set the flag. Only allow this code to run once.
10399 with self .StateLock :
104100 # If we are already closed, there's nothing to do.
105101 if self .IsClosed is True :
106102 return
107- # We will close now, so set the flag.
108103 self .IsClosed = True
109104
110- # While under lock, exists, and if so, has it been closed.
111- # Note it's possible that this helper is being crated on a different
112- # thread and will be set just after we exit the lock. In that case
113- # the creator logic will notice that the stream is closed and call close on it.
114- # So if the http helper doesn't exist yet, we can't set the isClosed flag to false.
115- if self .HttpHelper is not None or self .WsHelper is not None :
116- if self .IsHelperClosed is False :
117- self .IsHelperClosed = True
118- localHttpHelper = self .HttpHelper
119- localWsHelper = self .WsHelper
120- # Important! Ensure these are set to None so we don't have a circular ref.
121- self .HttpHelper = None
122- self .WsHelper = None
105+ # While under lock, after the self.IsClosed flag has been set, check if there's a helper.
106+ # The helper will only be set if self.IsClosed is false, once it's set, it's this function's responsibility to close.
107+ if self .WebStreamHelper is not None :
108+ # Grab a local ref and clear the member var, which indicates we are going to close it.
109+ streamHelper = self .WebStreamHelper
110+ self .WebStreamHelper = None
123111
124112 # Remove ourselves from the session map
125113 self .Session .WebStreamClosed (self .Id )
@@ -137,10 +125,8 @@ def Close(self) -> None:
137125 # If we got a ref to the helper, we need to call close on it.
138126 # NOTE - It's very important that these don't block or they will block the entire main websocket connection.
139127 try :
140- if localHttpHelper is not None :
141- localHttpHelper .Close ()
142- if localWsHelper is not None :
143- localWsHelper .Close ()
128+ if streamHelper is not None :
129+ streamHelper .Close ()
144130 except Exception as e :
145131 Sentry .OnException ("Web stream " + str (self .Id )+ " helper threw an exception during close" , e )
146132
@@ -161,77 +147,84 @@ def run(self) -> None:
161147
162148
163149 def mainThread (self ) -> None :
164- # Loop until we are closed.
165- # Check under lock to avoid race condition with Close()
166- while True :
167- with self .StateLock :
168- if self .IsClosed :
169- return
150+ # Once created, we need to hold a ref so we can use it for cleanup.
151+ streamHelper :Optional [IWebStreamHelper ] = None
152+ try :
153+ # Loop until we are closed.
154+ # Check under lock to avoid race condition with Close()
155+ while True :
156+ with self .StateLock :
157+ if self .IsClosed :
158+ return
170159
171- # Wait on incoming messages
172- # Timeout after 60 seconds just to check that we aren't closed.
173- # It's important to set this value to None, otherwise on loops it will hold it's old value
174- # which can accidentally re-process old messages.
175- webStreamMsg :Optional [WebStreamMsg .WebStreamMsg ] = None
176- try :
177- webStreamMsg = self .MsgQueue .get (timeout = 60 )
178- except Exception as _ :
179- # We get this exception on the timeout.
180- pass
160+ # Wait on incoming messages
161+ # Timeout after 60 seconds just to check that we aren't closed.
162+ # It's important to set this value to None, otherwise on loops it will hold it's old value
163+ # which can accidentally re-process old messages.
164+ webStreamMsg :Optional [WebStreamMsg .WebStreamMsg ] = None
165+ try :
166+ webStreamMsg = self .MsgQueue .get (timeout = 60 )
167+ except Exception as _ :
168+ # We get this exception on the timeout.
169+ pass
170+
171+ # Check that we aren't closed (under lock for thread safety)
172+ with self .StateLock :
173+ if self .IsClosed :
174+ return
181175
182- # Check that we aren't closed (under lock for thread safety)
183- with self .StateLock :
184- if self .IsClosed :
185- return
176+ # Check that we got a message and this wasn't just a timeout
177+ if webStreamMsg is None :
178+ continue
186179
187- # Check that we got a message and this wasn't just a timeout
188- if webStreamMsg is None :
189- continue
190-
191- # Handle the message.
192- if webStreamMsg .IsOpenMsg ():
193- self .initFromOpenMessage (webStreamMsg )
194-
195- # Ensure we have an open message.
196- if self .OpenWebStreamMsg is None :
197- # Throw so we reset the connection.
198- raise Exception ("Web stream [" + str (self .Id )+ "] got a non open message before it's open message." )
199-
200- # Don't pass it to the helper if there's nothing more.
201- if webStreamMsg .IsControlFlagsOnly ():
202- continue
203-
204- # Allow the helper to process the message
205- # We should only ever have one, but just for safety, check both.
206- # We need to take a local reference, since they are cleared under lock on close.
207- returnValue = True
208- httpHelper = self .HttpHelper
209- wsHelper = self .WsHelper
210- if httpHelper is not None :
211- returnValue = httpHelper .IncomingServerMessage (webStreamMsg )
212- if wsHelper is not None :
213- returnValue = wsHelper .IncomingServerMessage (webStreamMsg )
214-
215- # If process server message returns true, we should close the stream.
216- if returnValue is True :
217- self .Close ()
218- return
180+ # Handle the message.
181+ if webStreamMsg .IsOpenMsg ():
182+ # This will return the webstream helper if it was created.
183+ # The only way it wouldn't be created is if the stream was closed before it could be created.
184+ streamHelper = self .initFromOpenMessage (webStreamMsg )
219185
220- # When the http helper sends messages, it can indicate that the close flag has been set.
221- # In such a case, self.HasSentCloseMessage will be true. We don't want to rely on the client
222- # returning the correct returnValue, so if we see that we will call close to make sure things
223- # are going down. Since Close() is guarded against multiple entries, this is totally fine.
224- # Check under lock for thread safety.
225- with self .StateLock :
226- shouldClose = self .HasSentCloseMessage is True and self .IsClosed is False
227- if shouldClose :
228- self .Logger .warning ("Web stream " + str (self .Id )+ " processed a message and has sent a close message, but didn't call close on the web stream. Closing now." )
229- self .Close ()
230- return
186+ # Ensure we have an open message.
187+ if self .OpenWebStreamMsg is None :
188+ # Throw so we reset the connection.
189+ raise Exception ("Web stream [" + str (self .Id )+ "] got a non open message before it's open message." )
231190
191+ # Don't pass it to the helper if there's nothing more.
192+ if webStreamMsg .IsControlFlagsOnly ():
193+ continue
232194
233- def initFromOpenMessage (self , webStreamMsg :WebStreamMsg .WebStreamMsg ) -> None :
234- # Sanity check.
195+ # As long as we aren't closed and we have a helper, process the message.
196+ closeRequested = True
197+ if self .IsClosed is False and streamHelper is not None :
198+ closeRequested = streamHelper .IncomingServerMessage (webStreamMsg )
199+
200+ # If process server message returns true, we should close the stream.
201+ if closeRequested is True :
202+ self .Close ()
203+ return
204+
205+ # When the http helper sends messages, it can indicate that the close flag has been set.
206+ # In such a case, self.HasSentCloseMessage will be true. We don't want to rely on the client
207+ # returning the correct returnValue, so if we see that we will call close to make sure things
208+ # are going down. Since Close() is guarded against multiple entries, this is totally fine.
209+ # Check under lock for thread safety.
210+ with self .StateLock :
211+ shouldClose = self .HasSentCloseMessage is True and self .IsClosed is False
212+ if shouldClose :
213+ self .Logger .warning ("Web stream " + str (self .Id )+ " processed a message and has sent a close message, but didn't call close on the web stream. Closing now." )
214+ self .Close ()
215+ return
216+ finally :
217+ # Be sure to call this cleanup function before the thread exists.
218+ try :
219+ if streamHelper is not None :
220+ streamHelper .OnWebStreamThreadExit ()
221+ except Exception as e :
222+ self .Logger .error ("Exception in OnWebStreamThreadExit: " + str (e ))
223+
224+
225+ # If the web stream helper was created AND SET, it must be returned here.
226+ def initFromOpenMessage (self , webStreamMsg :WebStreamMsg .WebStreamMsg ) -> Optional [IWebStreamHelper ]:
227+ # Ensure we haven't already received an open message.
235228 if self .OpenWebStreamMsg is not None :
236229 # Throw so we reset the connection.
237230 raise Exception ("Web stream [" + str (self .Id )+ "] already have an open message and we got another." )
@@ -248,36 +241,35 @@ def initFromOpenMessage(self, webStreamMsg:WebStreamMsg.WebStreamMsg) -> None:
248241 # Create the helper out of lock and then set it.
249242 # WE MUST ALWAYS SET THE HTTP HELPER OBJECT since down stream logic depends on it existing.
250243 # But, if the stream has closed since we created this object, we must call close on it.
251- httpHelper = None
252- wsHelper = None
244+ webStreamHelper :Optional [IWebStreamHelper ] = None
253245 if webStreamMsg .IsWebsocketStream ():
254- wsHelper = WebStreamWsHelper (self .Id , self .Logger , self , self .OpenWebStreamMsg , self .OpenedTime )
246+ webStreamHelper = WebStreamWsHelper (self .Id , self .Logger , self , self .OpenWebStreamMsg , self .OpenedTime )
255247 else :
256- httpHelper = WebStreamHttpHelper (self .Id , self .Logger , self , self .OpenWebStreamMsg , self .OpenedTime )
248+ webStreamHelper = WebStreamHttpHelper (self .Id , self .Logger , self , self .OpenWebStreamMsg , self .OpenedTime )
257249
258250 needsToCallCloseOnHelper = False
259251 with self .StateLock :
260- # Set the helper, which ever we made.
261- self .HttpHelper = httpHelper
262- self .WsHelper = wsHelper
263-
264- # If the stream is now closed...
265- if self .IsClosed is True :
266- # and the http helper didn't get closed called yet...
267- if self .IsHelperClosed is False :
268- # We need to call it now.
269- self .IsHelperClosed = True
270- needsToCallCloseOnHelper = True
271- # Important! Ensure these are set to None so we don't have a circular ref.
272- self .HttpHelper = None
273- self .WsHelper = None
252+ # Ensure we haven't already done the init.
253+ if self .WebStreamHelper is not None :
254+ raise Exception ("Web stream [" + str (self .Id )+ "] already has a helper created but we got another init message. This means we got two init messages." )
255+
256+ if self .IsClosed is False :
257+ # If we are open, set the webstream helper now.
258+ # Once it's set, the Close function must call Close on it.
259+ self .WebStreamHelper = webStreamHelper
260+ else :
261+ # If the stream is now closed, DO NOT set the helper and close it now.
262+ needsToCallCloseOnHelper = True
274263
275264 # Outside of lock, if we need to close this helper, do it.
276265 if needsToCallCloseOnHelper is True :
277- if httpHelper is not None :
278- httpHelper .Close ()
279- if wsHelper is not None :
280- wsHelper .Close ()
266+ webStreamHelper .Close ()
267+ webStreamHelper .OnWebStreamThreadExit ()
268+ # If we closed it, we should NOT return it.
269+ return None
270+ else :
271+ # This is set and we opened it, so we must return it.
272+ return webStreamHelper
281273
282274
283275 # Called by the helpers to send messages to the server.
0 commit comments