2015-11-05 13:52:33 +01:00
|
|
|
#!/usr/bin/env python
|
2015-12-21 21:18:53 +01:00
|
|
|
#
|
|
|
|
# A library that provides a Python interface to the Telegram Bot API
|
2016-01-05 14:12:03 +01:00
|
|
|
# Copyright (C) 2015-2016
|
|
|
|
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
2015-12-21 21:18:53 +01:00
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Lesser Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Lesser Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Lesser Public License
|
|
|
|
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
2016-01-13 17:09:35 +01:00
|
|
|
"""This module contains the class Updater, which tries to make creating
|
|
|
|
Telegram bots intuitive."""
|
|
|
|
|
2015-11-15 17:36:38 +01:00
|
|
|
import logging
|
2015-11-22 16:46:17 +01:00
|
|
|
import os
|
2015-11-16 13:05:57 +01:00
|
|
|
import ssl
|
2016-02-06 11:38:56 +01:00
|
|
|
from threading import Thread, Lock, current_thread, Event
|
2015-11-16 20:35:27 +01:00
|
|
|
from time import sleep
|
2015-11-22 01:03:29 +01:00
|
|
|
import subprocess
|
2015-11-23 03:45:47 +01:00
|
|
|
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
2016-04-25 09:01:21 +02:00
|
|
|
from queue import Queue
|
2016-04-14 23:57:40 +02:00
|
|
|
|
2016-03-14 14:50:12 +01:00
|
|
|
from telegram import Bot, TelegramError, NullHandler
|
2016-03-22 02:48:56 +01:00
|
|
|
from telegram.ext import dispatcher, Dispatcher, JobQueue
|
2016-03-13 20:44:59 +01:00
|
|
|
from telegram.error import Unauthorized, InvalidToken
|
2015-11-16 13:05:57 +01:00
|
|
|
from telegram.utils.webhookhandler import (WebhookServer, WebhookHandler)
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-03-15 02:56:20 +01:00
|
|
|
logging.getLogger(__name__).addHandler(NullHandler())
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2015-11-06 00:24:01 +01:00
|
|
|
|
2016-04-15 16:20:37 +02:00
|
|
|
class Updater(object):
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2015-11-22 19:15:17 +01:00
|
|
|
This class, which employs the Dispatcher class, provides a frontend to
|
2016-03-23 15:23:38 +01:00
|
|
|
telegram.Bot to the programmer, so they can focus on coding the bot. Its
|
2015-11-22 19:15:17 +01:00
|
|
|
purpose is to receive the updates from Telegram and to deliver them to said
|
|
|
|
dispatcher. It also runs in a separate thread, so the user can interact
|
|
|
|
with the bot, for example on the command line. The dispatcher supports
|
|
|
|
handlers for different kinds of data: Updates from Telegram, basic text
|
2015-11-05 13:52:33 +01:00
|
|
|
commands and even arbitrary types.
|
2015-11-22 19:15:17 +01:00
|
|
|
The updater can be started as a polling service or, for production, use a
|
|
|
|
webhook to receive updates. This is achieved using the WebhookServer and
|
|
|
|
WebhookHandler classes.
|
2015-11-17 15:57:22 +01:00
|
|
|
|
2015-11-21 16:04:06 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Attributes:
|
2015-11-17 15:57:22 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2015-12-26 18:32:36 +01:00
|
|
|
token (Optional[str]): The bot's token given by the @BotFather
|
2015-11-05 13:52:33 +01:00
|
|
|
base_url (Optional[str]):
|
2015-11-15 17:36:38 +01:00
|
|
|
workers (Optional[int]): Amount of threads in the thread pool for
|
|
|
|
functions decorated with @run_async
|
2015-12-26 18:32:36 +01:00
|
|
|
bot (Optional[Bot]):
|
2016-03-01 19:20:51 +01:00
|
|
|
job_queue_tick_interval(Optional[float]): The interval the queue should
|
|
|
|
be checked for new tasks. Defaults to 1.0
|
2015-12-26 18:32:36 +01:00
|
|
|
|
|
|
|
Raises:
|
|
|
|
ValueError: If both `token` and `bot` are passed or none of them.
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2015-11-17 15:57:22 +01:00
|
|
|
|
2016-05-25 23:57:29 +02:00
|
|
|
def __init__(self, token=None, base_url=None, workers=4, bot=None):
|
2015-12-26 18:32:36 +01:00
|
|
|
if (token is None) and (bot is None):
|
|
|
|
raise ValueError('`token` or `bot` must be passed')
|
|
|
|
if (token is not None) and (bot is not None):
|
|
|
|
raise ValueError('`token` and `bot` are mutually exclusive')
|
2015-11-22 13:30:23 +01:00
|
|
|
|
2015-12-26 18:32:36 +01:00
|
|
|
if bot is not None:
|
|
|
|
self.bot = bot
|
|
|
|
else:
|
|
|
|
self.bot = Bot(token, base_url)
|
2016-04-14 23:57:40 +02:00
|
|
|
self.update_queue = Queue()
|
2016-05-25 22:51:13 +02:00
|
|
|
self.job_queue = JobQueue(self.bot)
|
2016-02-06 11:38:56 +01:00
|
|
|
self.__exception_event = Event()
|
2016-05-26 14:39:11 +02:00
|
|
|
self.dispatcher = Dispatcher(self.bot,
|
|
|
|
self.update_queue,
|
|
|
|
job_queue=self.job_queue,
|
|
|
|
workers=workers,
|
|
|
|
exception_event=self.__exception_event)
|
2015-11-22 13:30:23 +01:00
|
|
|
self.last_update_id = 0
|
2015-11-15 17:36:38 +01:00
|
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
self.running = False
|
2015-11-23 03:45:47 +01:00
|
|
|
self.is_idle = False
|
2015-11-16 13:05:57 +01:00
|
|
|
self.httpd = None
|
2015-12-31 14:52:28 +01:00
|
|
|
self.__lock = Lock()
|
2016-02-09 22:08:27 +01:00
|
|
|
self.__threads = []
|
|
|
|
""":type: list[Thread]"""
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-05-10 23:58:55 +02:00
|
|
|
def _init_thread(self, target, name, *args, **kwargs):
|
2016-05-15 03:46:40 +02:00
|
|
|
thr = Thread(target=self._thread_wrapper, name=name, args=(target,) + args, kwargs=kwargs)
|
2016-05-10 23:58:55 +02:00
|
|
|
thr.start()
|
|
|
|
self.__threads.append(thr)
|
|
|
|
|
|
|
|
def _thread_wrapper(self, target, *args, **kwargs):
|
|
|
|
thr_name = current_thread().name
|
|
|
|
self.logger.debug('{0} - started'.format(thr_name))
|
|
|
|
try:
|
|
|
|
target(*args, **kwargs)
|
|
|
|
except Exception:
|
|
|
|
self.__exception_event.set()
|
|
|
|
self.logger.exception('unhandled exception')
|
|
|
|
raise
|
|
|
|
self.logger.debug('{0} - ended'.format(thr_name))
|
|
|
|
|
2016-05-15 03:46:40 +02:00
|
|
|
def start_polling(self,
|
|
|
|
poll_interval=0.0,
|
|
|
|
timeout=10,
|
2016-05-28 18:34:16 +02:00
|
|
|
network_delay=5.,
|
2016-05-15 03:46:40 +02:00
|
|
|
clean=False,
|
|
|
|
bootstrap_retries=0):
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2015-11-17 15:57:22 +01:00
|
|
|
Starts polling updates from Telegram.
|
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2016-05-28 18:34:16 +02:00
|
|
|
poll_interval (Optional[float]): Time to wait between polling updates from Telegram in
|
|
|
|
seconds. Default is 0.0
|
|
|
|
|
2015-11-11 13:33:03 +01:00
|
|
|
timeout (Optional[float]): Passed to Bot.getUpdates
|
2016-05-28 18:34:16 +02:00
|
|
|
|
2015-11-11 13:33:03 +01:00
|
|
|
network_delay (Optional[float]): Passed to Bot.getUpdates
|
2016-05-28 18:34:16 +02:00
|
|
|
|
|
|
|
clean (Optional[bool]): Whether to clean any pending updates on Telegram servers before
|
|
|
|
actually starting to poll. Default is False.
|
|
|
|
|
|
|
|
bootstrap_retries (Optional[int]): Whether the bootstrapping phase of the `Updater`
|
|
|
|
will retry on failures on the Telegram server.
|
2016-04-18 18:13:54 +02:00
|
|
|
|
|
|
|
| < 0 - retry indefinitely
|
|
|
|
| 0 - no retries (default)
|
|
|
|
| > 0 - retry up to X times
|
2015-11-05 13:52:33 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
Queue: The update queue that can be filled from the main thread
|
|
|
|
|
2016-03-01 20:40:04 +01:00
|
|
|
"""
|
2015-12-31 14:52:28 +01:00
|
|
|
with self.__lock:
|
|
|
|
if not self.running:
|
|
|
|
self.running = True
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-02-06 11:38:56 +01:00
|
|
|
# Create & start threads
|
|
|
|
self._init_thread(self.dispatcher.start, "dispatcher")
|
2016-05-15 03:46:40 +02:00
|
|
|
self._init_thread(self._start_polling, "updater", poll_interval, timeout,
|
|
|
|
network_delay, bootstrap_retries, clean)
|
2015-11-17 15:57:22 +01:00
|
|
|
|
2015-12-31 14:52:28 +01:00
|
|
|
# Return the update queue so the main thread can insert updates
|
|
|
|
return self.update_queue
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2015-12-01 15:16:04 +01:00
|
|
|
def start_webhook(self,
|
|
|
|
listen='127.0.0.1',
|
|
|
|
port=80,
|
|
|
|
url_path='',
|
|
|
|
cert=None,
|
2016-03-01 20:40:04 +01:00
|
|
|
key=None,
|
2016-03-14 00:33:09 +01:00
|
|
|
clean=False,
|
|
|
|
bootstrap_retries=0,
|
|
|
|
webhook_url=None):
|
2015-11-16 13:05:57 +01:00
|
|
|
"""
|
2015-11-30 23:00:32 +01:00
|
|
|
Starts a small http server to listen for updates via webhook. If cert
|
|
|
|
and key are not provided, the webhook will be started directly on
|
2015-12-01 15:16:04 +01:00
|
|
|
http://listen:port/url_path, so SSL can be handled by another
|
|
|
|
application. Else, the webhook will be started on
|
|
|
|
https://listen:port/url_path
|
2015-11-16 13:05:57 +01:00
|
|
|
|
|
|
|
Args:
|
|
|
|
listen (Optional[str]): IP-Address to listen on
|
2015-12-01 14:11:27 +01:00
|
|
|
port (Optional[int]): Port the bot should be listening on
|
|
|
|
url_path (Optional[str]): Path inside url
|
2015-11-30 23:00:32 +01:00
|
|
|
cert (Optional[str]): Path to the SSL certificate file
|
|
|
|
key (Optional[str]): Path to the SSL key file
|
2016-03-01 20:40:04 +01:00
|
|
|
clean (Optional[bool]): Whether to clean any pending updates on
|
|
|
|
Telegram servers before actually starting the webhook. Default
|
|
|
|
is False.
|
2016-03-14 00:33:09 +01:00
|
|
|
bootstrap_retries (Optional[int[): Whether the bootstrapping phase
|
|
|
|
of the `Updater` will retry on failures on the Telegram server.
|
2016-04-18 18:13:54 +02:00
|
|
|
|
|
|
|
| < 0 - retry indefinitely
|
|
|
|
| 0 - no retries (default)
|
|
|
|
| > 0 - retry up to X times
|
2016-03-14 00:33:09 +01:00
|
|
|
webhook_url (Optional[str]): Explicitly specifiy the webhook url.
|
|
|
|
Useful behind NAT, reverse proxy, etc. Default is derived from
|
|
|
|
`listen`, `port` & `url_path`.
|
2015-11-16 13:05:57 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
Queue: The update queue that can be filled from the main thread
|
|
|
|
|
2016-03-14 00:33:09 +01:00
|
|
|
"""
|
2015-12-31 14:52:28 +01:00
|
|
|
with self.__lock:
|
|
|
|
if not self.running:
|
|
|
|
self.running = True
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2016-02-09 22:05:50 +01:00
|
|
|
# Create & start threads
|
|
|
|
self._init_thread(self.dispatcher.start, "dispatcher"),
|
2016-05-15 03:46:40 +02:00
|
|
|
self._init_thread(self._start_webhook, "updater", listen, port, url_path, cert,
|
|
|
|
key, bootstrap_retries, clean, webhook_url)
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2015-12-31 14:52:28 +01:00
|
|
|
# Return the update queue so the main thread can insert updates
|
|
|
|
return self.update_queue
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2016-05-15 03:46:40 +02:00
|
|
|
def _start_polling(self, poll_interval, timeout, network_delay, bootstrap_retries, clean):
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2015-11-22 19:15:17 +01:00
|
|
|
Thread target of thread 'updater'. Runs in background, pulls
|
2015-11-05 13:52:33 +01:00
|
|
|
updates from Telegram and inserts them in the update queue of the
|
2015-11-22 14:47:38 +01:00
|
|
|
Dispatcher.
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-03-13 20:44:59 +01:00
|
|
|
"""
|
2016-01-23 23:08:53 +01:00
|
|
|
cur_interval = poll_interval
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Updater thread started')
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-05-10 23:58:55 +02:00
|
|
|
self._bootstrap(bootstrap_retries, clean=clean, webhook_url='')
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2015-11-15 17:36:38 +01:00
|
|
|
while self.running:
|
2015-11-05 13:52:33 +01:00
|
|
|
try:
|
2015-11-11 13:33:03 +01:00
|
|
|
updates = self.bot.getUpdates(self.last_update_id,
|
|
|
|
timeout=timeout,
|
|
|
|
network_delay=network_delay)
|
2016-01-23 23:08:53 +01:00
|
|
|
except TelegramError as te:
|
2016-05-15 03:46:40 +02:00
|
|
|
self.logger.error("Error while getting Updates: {0}".format(te))
|
2016-01-23 23:08:53 +01:00
|
|
|
|
|
|
|
# Put the error into the update queue and let the Dispatcher
|
|
|
|
# broadcast it
|
|
|
|
self.update_queue.put(te)
|
|
|
|
|
|
|
|
cur_interval = self._increase_poll_interval(cur_interval)
|
|
|
|
else:
|
2015-11-15 17:36:38 +01:00
|
|
|
if not self.running:
|
|
|
|
if len(updates) > 0:
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Updates ignored and will be pulled '
|
|
|
|
'again on restart.')
|
2015-11-15 17:36:38 +01:00
|
|
|
break
|
|
|
|
|
2016-01-23 23:08:53 +01:00
|
|
|
if updates:
|
|
|
|
for update in updates:
|
|
|
|
self.update_queue.put(update)
|
|
|
|
self.last_update_id = updates[-1].update_id + 1
|
2015-11-17 15:57:22 +01:00
|
|
|
|
2016-01-23 23:08:53 +01:00
|
|
|
cur_interval = poll_interval
|
|
|
|
|
|
|
|
sleep(cur_interval)
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2016-01-23 23:08:53 +01:00
|
|
|
@staticmethod
|
|
|
|
def _increase_poll_interval(current_interval):
|
|
|
|
# increase waiting times on subsequent errors up to 30secs
|
|
|
|
if current_interval == 0:
|
|
|
|
current_interval = 1
|
|
|
|
elif current_interval < 30:
|
|
|
|
current_interval += current_interval / 2
|
|
|
|
elif current_interval > 30:
|
|
|
|
current_interval = 30
|
|
|
|
return current_interval
|
|
|
|
|
2016-05-15 03:46:40 +02:00
|
|
|
def _start_webhook(self, listen, port, url_path, cert, key, bootstrap_retries, clean,
|
|
|
|
webhook_url):
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Updater thread started')
|
2015-11-30 23:00:32 +01:00
|
|
|
use_ssl = cert is not None and key is not None
|
2016-03-23 10:11:10 +01:00
|
|
|
if not url_path.startswith('/'):
|
2016-03-23 10:11:10 +01:00
|
|
|
url_path = '/{0}'.format(url_path)
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2015-11-30 23:00:32 +01:00
|
|
|
# Create and start server
|
2016-05-15 03:46:40 +02:00
|
|
|
self.httpd = WebhookServer((listen, port), WebhookHandler, self.update_queue, url_path)
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2015-11-30 23:00:32 +01:00
|
|
|
if use_ssl:
|
2016-03-23 10:11:10 +01:00
|
|
|
self._check_ssl_cert(cert, key)
|
|
|
|
|
2016-04-24 14:14:00 +02:00
|
|
|
# DO NOT CHANGE: Only set webhook if SSL is handled by library
|
|
|
|
if not webhook_url:
|
|
|
|
webhook_url = self._gen_webhook_url(listen, port, url_path)
|
2016-03-23 10:11:10 +01:00
|
|
|
|
2016-05-15 03:46:40 +02:00
|
|
|
self._bootstrap(max_retries=bootstrap_retries,
|
|
|
|
clean=clean,
|
|
|
|
webhook_url=webhook_url,
|
|
|
|
cert=open(cert, 'rb'))
|
2016-05-10 23:58:55 +02:00
|
|
|
elif clean:
|
|
|
|
self.logger.warning("cleaning updates is not supported if "
|
|
|
|
"SSL-termination happens elsewhere; skipping")
|
2015-11-30 23:00:32 +01:00
|
|
|
|
|
|
|
self.httpd.serve_forever(poll_interval=1)
|
2015-11-16 13:05:57 +01:00
|
|
|
|
2016-03-23 10:11:10 +01:00
|
|
|
def _check_ssl_cert(self, cert, key):
|
|
|
|
# Check SSL-Certificate with openssl, if possible
|
|
|
|
try:
|
2016-05-15 03:46:40 +02:00
|
|
|
exit_code = subprocess.call(
|
|
|
|
["openssl", "x509", "-text", "-noout", "-in", cert],
|
|
|
|
stdout=open(os.devnull, 'wb'),
|
|
|
|
stderr=subprocess.STDOUT)
|
2016-03-23 10:11:10 +01:00
|
|
|
except OSError:
|
|
|
|
exit_code = 0
|
|
|
|
if exit_code is 0:
|
|
|
|
try:
|
|
|
|
self.httpd.socket = ssl.wrap_socket(self.httpd.socket,
|
|
|
|
certfile=cert,
|
|
|
|
keyfile=key,
|
|
|
|
server_side=True)
|
|
|
|
except ssl.SSLError as error:
|
|
|
|
self.logger.exception('Failed to init SSL socket')
|
|
|
|
raise TelegramError(str(error))
|
|
|
|
else:
|
|
|
|
raise TelegramError('SSL Certificate invalid')
|
|
|
|
|
2016-05-10 23:58:55 +02:00
|
|
|
@staticmethod
|
|
|
|
def _gen_webhook_url(listen, port, url_path):
|
2016-05-15 03:46:40 +02:00
|
|
|
return 'https://{listen}:{port}{path}'.format(listen=listen, port=port, path=url_path)
|
2016-03-14 00:33:09 +01:00
|
|
|
|
2016-05-10 23:58:55 +02:00
|
|
|
def _bootstrap(self, max_retries, clean, webhook_url, cert=None):
|
|
|
|
retries = 0
|
2016-06-19 23:46:34 +02:00
|
|
|
while 1:
|
2016-05-10 23:58:55 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
if clean:
|
|
|
|
# Disable webhook for cleaning
|
|
|
|
self.bot.setWebhook(webhook_url='')
|
|
|
|
self._clean_updates()
|
|
|
|
|
2016-05-15 03:46:40 +02:00
|
|
|
self.bot.setWebhook(webhook_url=webhook_url, certificate=cert)
|
2016-05-10 23:58:55 +02:00
|
|
|
except (Unauthorized, InvalidToken):
|
|
|
|
raise
|
|
|
|
except TelegramError:
|
2016-05-22 18:26:57 +02:00
|
|
|
msg = 'error in bootstrap phase; try={0} max_retries={1}'.format(retries,
|
|
|
|
max_retries)
|
2016-05-10 23:58:55 +02:00
|
|
|
if max_retries < 0 or retries < max_retries:
|
|
|
|
self.logger.warning(msg)
|
|
|
|
retries += 1
|
|
|
|
else:
|
|
|
|
self.logger.exception(msg)
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
sleep(1)
|
|
|
|
|
2016-03-01 20:40:04 +01:00
|
|
|
def _clean_updates(self):
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Cleaning updates from Telegram server')
|
2016-03-01 20:40:04 +01:00
|
|
|
updates = self.bot.getUpdates()
|
|
|
|
while updates:
|
|
|
|
updates = self.bot.getUpdates(updates[-1].update_id + 1)
|
|
|
|
|
2015-11-15 17:36:38 +01:00
|
|
|
def stop(self):
|
|
|
|
"""
|
2016-01-05 13:32:19 +01:00
|
|
|
Stops the polling/webhook thread, the dispatcher and the job queue
|
2015-11-15 17:36:38 +01:00
|
|
|
"""
|
2015-11-21 21:21:09 +01:00
|
|
|
|
2016-01-05 13:32:19 +01:00
|
|
|
self.job_queue.stop()
|
2015-12-31 14:52:28 +01:00
|
|
|
with self.__lock:
|
2016-06-17 22:53:18 +02:00
|
|
|
if self.running or dispatcher.ASYNC_THREADS:
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Stopping Updater and Dispatcher...')
|
2016-02-09 22:08:27 +01:00
|
|
|
|
|
|
|
self.running = False
|
|
|
|
|
|
|
|
self._stop_httpd()
|
|
|
|
self._stop_dispatcher()
|
|
|
|
self._join_threads()
|
2016-06-19 23:46:34 +02:00
|
|
|
# async threads must be join()ed only after the dispatcher thread was joined,
|
|
|
|
# otherwise we can still have new async threads dispatched
|
2016-02-09 22:08:27 +01:00
|
|
|
self._join_async_threads()
|
|
|
|
|
|
|
|
def _stop_httpd(self):
|
|
|
|
if self.httpd:
|
2016-05-15 03:46:40 +02:00
|
|
|
self.logger.debug('Waiting for current webhook connection to be '
|
|
|
|
'closed... Send a Telegram message to the bot to exit '
|
|
|
|
'immediately.')
|
2016-02-09 22:08:27 +01:00
|
|
|
self.httpd.shutdown()
|
|
|
|
self.httpd = None
|
|
|
|
|
|
|
|
def _stop_dispatcher(self):
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Requesting Dispatcher to stop...')
|
2016-02-09 22:08:27 +01:00
|
|
|
self.dispatcher.stop()
|
|
|
|
|
|
|
|
def _join_async_threads(self):
|
2016-06-01 20:05:34 +02:00
|
|
|
with dispatcher.ASYNC_LOCK:
|
|
|
|
threads = list(dispatcher.ASYNC_THREADS)
|
2016-06-19 23:46:34 +02:00
|
|
|
total = len(threads)
|
2016-05-30 13:09:23 +02:00
|
|
|
|
2016-06-19 23:46:34 +02:00
|
|
|
# Stop all threads in the thread pool by put()ting one non-tuple per thread
|
|
|
|
for i in range(total):
|
|
|
|
dispatcher.ASYNC_QUEUE.put(None)
|
2016-05-30 13:09:23 +02:00
|
|
|
|
2016-06-19 23:46:34 +02:00
|
|
|
for i, thr in enumerate(threads):
|
|
|
|
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i + 1, total))
|
|
|
|
thr.join()
|
|
|
|
dispatcher.ASYNC_THREADS.remove(thr)
|
|
|
|
self.logger.debug('async thread {0}/{1} has ended'.format(i + 1, total))
|
2016-02-09 22:08:27 +01:00
|
|
|
|
|
|
|
def _join_threads(self):
|
|
|
|
for thr in self.__threads:
|
2016-05-15 03:46:40 +02:00
|
|
|
self.logger.debug('Waiting for {0} thread to end'.format(thr.name))
|
2016-02-09 22:08:27 +01:00
|
|
|
thr.join()
|
|
|
|
self.logger.debug('{0} thread has ended'.format(thr.name))
|
|
|
|
self.__threads = []
|
2015-11-22 19:15:17 +01:00
|
|
|
|
2015-11-23 03:45:47 +01:00
|
|
|
def signal_handler(self, signum, frame):
|
|
|
|
self.is_idle = False
|
2016-04-18 19:21:57 +02:00
|
|
|
if self.running:
|
|
|
|
self.stop()
|
|
|
|
else:
|
|
|
|
self.logger.warning('Exiting immediately!')
|
|
|
|
import os
|
|
|
|
os._exit(1)
|
2015-11-23 03:45:47 +01:00
|
|
|
|
2015-11-23 17:40:39 +01:00
|
|
|
def idle(self, stop_signals=(SIGINT, SIGTERM, SIGABRT)):
|
|
|
|
"""
|
2016-01-04 01:32:47 +01:00
|
|
|
Blocks until one of the signals are received and stops the updater
|
2015-11-23 17:40:39 +01:00
|
|
|
|
|
|
|
Args:
|
|
|
|
stop_signals: Iterable containing signals from the signal module
|
|
|
|
that should be subscribed to. Updater.stop() will be called on
|
2015-12-16 16:18:48 +01:00
|
|
|
receiving one of those signals. Defaults to (SIGINT, SIGTERM,
|
|
|
|
SIGABRT)
|
2015-11-23 17:40:39 +01:00
|
|
|
"""
|
|
|
|
for sig in stop_signals:
|
|
|
|
signal(sig, self.signal_handler)
|
2015-11-23 03:45:47 +01:00
|
|
|
|
|
|
|
self.is_idle = True
|
|
|
|
|
|
|
|
while self.is_idle:
|
|
|
|
sleep(1)
|