@@ -149,15 +149,15 @@ def _thread_wrapper(self, target, *args, **kwargs):
149149 target (* args , ** kwargs )
150150 except Exception :
151151 self .__exception_event .set ()
152- self .logger .exception ('unhandled exception' )
152+ self .logger .exception ('unhandled exception in %s' , thr_name )
153153 raise
154154 self .logger .debug ('{0} - ended' .format (thr_name ))
155155
156156 def start_polling (self ,
157157 poll_interval = 0.0 ,
158158 timeout = 10 ,
159159 clean = False ,
160- bootstrap_retries = 0 ,
160+ bootstrap_retries = - 1 ,
161161 read_latency = 2. ,
162162 allowed_updates = None ):
163163 """Starts polling updates from Telegram.
@@ -171,8 +171,8 @@ def start_polling(self,
171171 bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
172172 `Updater` will retry on failures on the Telegram server.
173173
174- * < 0 - retry indefinitely
175- * 0 - no retries (default)
174+ * < 0 - retry indefinitely (default)
175+ * 0 - no retries
176176 * > 0 - retry up to X times
177177
178178 allowed_updates (List[:obj:`str`], optional): Passed to
@@ -229,8 +229,8 @@ def start_webhook(self,
229229 bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
230230 `Updater` will retry on failures on the Telegram server.
231231
232- * < 0 - retry indefinitely
233- * 0 - no retries (default)
232+ * < 0 - retry indefinitely (default)
233+ * 0 - no retries
234234 * > 0 - retry up to X times
235235
236236 webhook_url (:obj:`str`, optional): Explicitly specify the webhook url. Useful behind
@@ -242,7 +242,6 @@ def start_webhook(self,
242242 :obj:`Queue`: The update queue that can be filled from the main thread.
243243
244244 """
245-
246245 with self .__lock :
247246 if not self .running :
248247 self .running = True
@@ -262,46 +261,72 @@ def _start_polling(self, poll_interval, timeout, read_latency, bootstrap_retries
262261 # updates from Telegram and inserts them in the update queue of the
263262 # Dispatcher.
264263
265- cur_interval = poll_interval
266- self .logger .debug ('Updater thread started' )
264+ self .logger .debug ('Updater thread started (polling)' )
267265
268266 self ._bootstrap (bootstrap_retries , clean = clean , webhook_url = '' , allowed_updates = None )
269267
268+ self .logger .debug ('Bootstrap done' )
269+
270+ def polling_action_cb ():
271+ updates = self .bot .get_updates (
272+ self .last_update_id , timeout = timeout , read_latency = read_latency ,
273+ allowed_updates = allowed_updates )
274+
275+ if updates :
276+ if not self .running :
277+ self .logger .debug ('Updates ignored and will be pulled again on restart' )
278+ else :
279+ for update in updates :
280+ self .update_queue .put (update )
281+ self .last_update_id = updates [- 1 ].update_id + 1
282+
283+ return True
284+
285+ def polling_onerr_cb (exc ):
286+ # Put the error into the update queue and let the Dispatcher
287+ # broadcast it
288+ self .update_queue .put (exc )
289+
290+ self ._network_loop_retry (polling_action_cb , polling_onerr_cb , 'getting Updates' ,
291+ poll_interval )
292+
293+ def _network_loop_retry (self , action_cb , onerr_cb , description , interval ):
294+ """Perform a loop calling `action_cb`, retrying after network errors.
295+
296+ Stop condition for loop: `self.running` evaluates False or return value of `action_cb`
297+ evaluates False.
298+
299+ Args:
300+ action_cb (:obj:`callable`): Network oriented callback function to call.
301+ onerr_cb (:obj:`callable`): Callback to call when TelegramError is caught. Receives the
302+ exception object as a parameter.
303+ description (:obj:`str`): Description text to use for logs and exception raised.
304+ interval (:obj:`float` | :obj:`int`): Interval to sleep between each call to
305+ `action_cb`.
306+
307+ """
308+ self .logger .debug ('Start network loop retry %s' , description )
309+ cur_interval = interval
270310 while self .running :
271311 try :
272- updates = self .bot .get_updates (
273- self .last_update_id ,
274- timeout = timeout ,
275- read_latency = read_latency ,
276- allowed_updates = allowed_updates )
312+ if not action_cb ():
313+ break
277314 except RetryAfter as e :
278- self .logger .info (str ( e ) )
315+ self .logger .info ('%s' , e )
279316 cur_interval = 0.5 + e .retry_after
280317 except TimedOut as toe :
281- self .logger .debug ('Timed out getting Updates : %s' , toe )
282- # If get_updates() failed due to timeout, we should retry asap.
318+ self .logger .debug ('Timed out %s : %s' , description , toe )
319+ # If failure is due to timeout, we should retry asap.
283320 cur_interval = 0
321+ except InvalidToken as pex :
322+ self .logger .error ('Invalid token; aborting' )
323+ raise pex
284324 except TelegramError as te :
285- self .logger .error ('Error while getting Updates: %s' , te )
286-
287- # Put the error into the update queue and let the Dispatcher
288- # broadcast it
289- self .update_queue .put (te )
290-
325+ self .logger .error ('Error while %s: %s' , description , te )
326+ onerr_cb (te )
291327 cur_interval = self ._increase_poll_interval (cur_interval )
292328 else :
293- if not self .running :
294- if len (updates ) > 0 :
295- self .logger .debug ('Updates ignored and will be pulled '
296- 'again on restart.' )
297- break
298-
299- if updates :
300- for update in updates :
301- self .update_queue .put (update )
302- self .last_update_id = updates [- 1 ].update_id + 1
303-
304- cur_interval = poll_interval
329+ cur_interval = interval
305330
306331 if cur_interval :
307332 sleep (cur_interval )
@@ -319,7 +344,7 @@ def _increase_poll_interval(current_interval):
319344
320345 def _start_webhook (self , listen , port , url_path , cert , key , bootstrap_retries , clean ,
321346 webhook_url , allowed_updates ):
322- self .logger .debug ('Updater thread started' )
347+ self .logger .debug ('Updater thread started (webhook) ' )
323348 use_ssl = cert is not None and key is not None
324349 if not url_path .startswith ('/' ):
325350 url_path = '/{0}' .format (url_path )
@@ -370,39 +395,56 @@ def _check_ssl_cert(self, cert, key):
370395 def _gen_webhook_url (listen , port , url_path ):
371396 return 'https://{listen}:{port}{path}' .format (listen = listen , port = port , path = url_path )
372397
373- def _bootstrap (self , max_retries , clean , webhook_url , allowed_updates , cert = None ):
374- retries = 0
375- while 1 :
376-
377- try :
378- if clean :
379- # Disable webhook for cleaning
380- self .bot .delete_webhook ()
381- self ._clean_updates ()
382- sleep (1 )
383-
384- self .bot .set_webhook (
385- url = webhook_url , certificate = cert , allowed_updates = allowed_updates )
386- except (Unauthorized , InvalidToken ):
387- raise
388- except TelegramError :
389- msg = 'error in bootstrap phase; try={0} max_retries={1}' .format (retries ,
390- max_retries )
391- if max_retries < 0 or retries < max_retries :
392- self .logger .warning (msg )
393- retries += 1
394- else :
395- self .logger .exception (msg )
396- raise
398+ def _bootstrap (self , max_retries , clean , webhook_url , allowed_updates , cert = None ,
399+ bootstrap_interval = 5 ):
400+ retries = [0 ]
401+
402+ def bootstrap_del_webhook ():
403+ self .bot .delete_webhook ()
404+ return False
405+
406+ def bootstrap_clean_updates ():
407+ self .logger .debug ('Cleaning updates from Telegram server' )
408+ updates = self .bot .get_updates ()
409+ while updates :
410+ updates = self .bot .get_updates (updates [- 1 ].update_id + 1 )
411+ return False
412+
413+ def bootstrap_set_webhook ():
414+ self .bot .set_webhook (
415+ url = webhook_url , certificate = cert , allowed_updates = allowed_updates )
416+ return False
417+
418+ def bootstrap_onerr_cb (exc ):
419+ if not isinstance (exc , Unauthorized ) and (max_retries < 0 or retries [0 ] < max_retries ):
420+ retries [0 ] += 1
421+ self .logger .warning ('Failed bootstrap phase; try=%s max_retries=%s' ,
422+ retries [0 ], max_retries )
397423 else :
398- break
424+ self .logger .error ('Failed bootstrap phase after %s retries (%s)' , retries [0 ], exc )
425+ raise exc
426+
427+ # Cleaning pending messages is done by polling for them - so we need to delete webhook if
428+ # one is configured.
429+ # We also take this chance to delete pre-configured webhook if this is a polling Updater.
430+ # NOTE: We don't know ahead if a webhook is configured, so we just delete.
431+ if clean or not webhook_url :
432+ self ._network_loop_retry (bootstrap_del_webhook , bootstrap_onerr_cb ,
433+ 'bootstrap del webhook' , bootstrap_interval )
434+ retries [0 ] = 0
435+
436+ # Clean pending messages, if requested.
437+ if clean :
438+ self ._network_loop_retry (bootstrap_clean_updates , bootstrap_onerr_cb ,
439+ 'bootstrap clean updates' , bootstrap_interval )
440+ retries [0 ] = 0
399441 sleep (1 )
400442
401- def _clean_updates ( self ):
402- self . logger . debug ( 'Cleaning updates from Telegram server' )
403- updates = self . bot . get_updates ()
404- while updates :
405- updates = self . bot . get_updates ( updates [ - 1 ]. update_id + 1 )
443+ # Restore/set webhook settings, if needed. Again, we don't know ahead if a webhook is set,
444+ # so we set it anyhow.
445+ if webhook_url :
446+ self . _network_loop_retry ( bootstrap_set_webhook , bootstrap_onerr_cb ,
447+ 'bootstrap set webhook' , bootstrap_interval )
406448
407449 def stop (self ):
408450 """Stops the polling/webhook thread, the dispatcher and the job queue."""
0 commit comments