@@ -72,8 +72,8 @@ def __init__(
7272 self .target_prompt = target_prompt
7373 self ._cmd = cmd
7474 self .set_prompt_cmd = set_prompt_cmd
75- self ._re_set_prompt_cmd = re .sub (
76- "['\" ].*['\" ]" , "" , self .set_prompt_cmd .strip ()
75+ self ._re_set_prompt_cmd = re .escape (
76+ re . sub ( "['\" ].*['\" ]" , "" , self .set_prompt_cmd .strip () )
7777 )
7878 self ._terminal_delayafterclose = terminal_delayafterclose
7979 self ._join_timeout = 10
@@ -131,28 +131,35 @@ def close(self) -> None:
131131 Close ThreadedTerminal connection & stop pulling thread.
132132 :return: None
133133 """
134+ pulling_thread_alive = False
134135 if self .pulling_thread :
135136 try :
136137 self .pulling_thread .join (timeout = self ._join_timeout )
137- if self .pulling_thread .is_alive ():
138+ pulling_thread_alive = self .pulling_thread .is_alive ()
139+ if pulling_thread_alive :
138140 self .logger .warning (
139- f"Pulling thread did not finish after { self ._join_timeout } timeout."
141+ f"Pulling thread did not finish after { self ._join_timeout } s timeout."
140142 )
141143 except Exception as ex :
142144 self .logger .warning (f"Exception while joining pulling thread: { ex } " )
143145 self .moler_connection .shutdown ()
144146 super ().close ()
145147
146- if self ._terminal and self ._terminal .isalive ():
148+ terminal = self ._terminal
149+ if terminal and terminal .isalive ():
147150 self ._notify_on_disconnect ()
148151 try :
149- self . _terminal .close (force = True )
152+ terminal .close (force = True )
150153 except Exception as ex :
151154 self .logger .warning (f"Exception while closing terminal: { ex } " )
152- self ._terminal = None
155+ if not pulling_thread_alive :
156+ # Only drop the terminal reference if no thread can still touch it,
157+ # to avoid AttributeError on None inside pull_data.
158+ self ._terminal = None
153159 self ._shell_operable .clear ()
154160 self ._export_sent = False
155- self .pulling_thread = None
161+ if not pulling_thread_alive :
162+ self .pulling_thread = None
156163 self .read_buffer = ""
157164
158165 def send (self , data : str ) -> None :
@@ -184,24 +191,28 @@ def pull_data(self, pulling_done: threading.Event) -> None:
184191 """
185192 logging .getLogger ("moler_threads" ).debug (f"ENTER { self } " )
186193 heartbeat = tracked_thread .report_alive ()
187- reads : List [ int ] = []
194+ reads : List = []
188195
189196 while not pulling_done .is_set ():
190- assert self ._terminal is not None
197+ terminal = self ._terminal
198+ if terminal is None :
199+ pulling_done .set ()
200+ break
191201 if next (heartbeat ):
192202 logging .getLogger ("moler_threads" ).debug (f"ALIVE { self } " )
193203 try :
194204 reads , _ , _ = select .select (
195- [self . _terminal .fd ], [], [], self ._select_timeout
205+ [terminal .fd ], [], [], self ._select_timeout
196206 )
197207 except ValueError as exc :
198208 self .logger .warning (f"'{ exc .__class__ } : { exc } '" )
199209 self ._notify_on_disconnect ()
200210 pulling_done .set ()
211+ continue
201212
202- if self . _terminal .fd in reads :
213+ if terminal .fd in reads :
203214 try :
204- data = self . _terminal .read (self ._read_buffer_size )
215+ data = terminal .read (self ._read_buffer_size )
205216 self ._log_debug_incoming_data (data )
206217 if self ._shell_operable .is_set ():
207218 self .data_received (data = data , recv_time = datetime .datetime .now ())
@@ -224,16 +235,18 @@ def _verify_shell_is_operable(self, data: str) -> None:
224235 for line in lines :
225236 line = remove_all_known_special_chars (line )
226237 if (
227- not re .search (self ._re_set_prompt_cmd , line ) and re . search (
228- self .target_prompt , line ) and not self . _shell_operable . is_set ( )
238+ not re .search (self ._re_set_prompt_cmd , line )
239+ and re . search ( self .target_prompt , line )
229240 ):
230241 self ._notify_on_connect ()
231242 self ._shell_operable .set ()
232243 self .data_received (
233244 data = self .read_buffer , recv_time = datetime .datetime .now ()
234245 )
246+ self .read_buffer = ""
247+ return
235248 elif not self ._export_sent and re .search (
236- self .first_prompt , self .read_buffer , re . MULTILINE
249+ self .first_prompt , self .read_buffer
237250 ):
238251 self .send (self .set_prompt_cmd )
239252 self ._export_sent = True
0 commit comments