Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3076dfc
use urllib3 instead of urllib(2)
tsnoam May 29, 2016
b040568
test_bot: fix for urllib3 compatibility
tsnoam May 29, 2016
574fc8c
urllib3: validate https certificate
tsnoam May 29, 2016
57759d8
[drunk] use actual thread pool and queue new functions into the pool …
jh0ker May 30, 2016
dd91ce1
use single queue for thread pool, initialize connection pool with n+3
jh0ker May 30, 2016
41f6591
more sensible logging
jh0ker May 30, 2016
74283bd
use HTTPSConnectionPool instead of PoolManager
jh0ker May 30, 2016
6b457ba
use keepalive for connection pool
jh0ker May 31, 2016
1ff348a
issue warning if connection pool was initialized before Dispatcher
jh0ker May 31, 2016
78f9bdc
dispatcher: pep8 style fix
tsnoam Jun 1, 2016
dd8b621
dispatcher: a little performance improvment
tsnoam Jun 1, 2016
c28763c
dispatcher: cosmetic fix
tsnoam Jun 1, 2016
3608c2b
dispatcher: if connection pool is already initialized raise exception
tsnoam Jun 1, 2016
1f5601d
fix SyntaxWarning
tsnoam Jun 1, 2016
bda0244
updater: fix print in log
tsnoam Jun 17, 2016
cb6ddfd
Merge remote-tracking branch 'origin/master' into urllib3
tsnoam Jun 17, 2016
881d1d0
fix/hack Updater.stop() not working on extreme cases
tsnoam Jun 17, 2016
a30411c
make sure to remove the stopped dispatcher threads from ASYNC_THREADS
tsnoam Jun 17, 2016
e479c7f
type hinting (cosmetic fix)
tsnoam Jun 17, 2016
d37b6d6
make sure to stop Updater after the test_createBot is over
tsnoam Jun 17, 2016
a814e9d
make sure to stop conpool between sensitive unitests
tsnoam Jun 17, 2016
bc77c84
test_updater: make sure that conpool is stopped before setting updater
tsnoam Jun 18, 2016
fc05d3a
switch back to PoolManager
tsnoam Jun 18, 2016
494a7ec
ypaf fixes
tsnoam Jun 18, 2016
5b91194
new yapf version, new cosmetic fixes
tsnoam Jun 18, 2016
05522e4
Merge remote-tracking branch 'origin/master' into urllib3
leandrotoledo Jun 19, 2016
949f4a4
update requirements: minimum versions of urllib3 and future
jh0ker Jun 19, 2016
703bece
set loglevel of urllib3 to WARNING by default
jh0ker Jun 19, 2016
7635bc0
comments, lock thread pool, while 1 and snake_case everywhere
jh0ker Jun 19, 2016
caf72ca
Merge branch 'urllib3' of github.com:python-telegram-bot/python-teleg…
jh0ker Jun 19, 2016
b41f7e3
Code style with latest yapf
leandrotoledo Jun 19, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
future
future>=0.15.2
urllib3>=1.8.3
certifi
85 changes: 48 additions & 37 deletions telegram/ext/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,47 @@

import logging
from functools import wraps
from threading import Thread, BoundedSemaphore, Lock, Event, current_thread
from threading import Thread, Lock, Event, current_thread
from time import sleep
from queue import Queue, Empty

from queue import Empty
from future.builtins import range

from telegram import (TelegramError, NullHandler)
from telegram.utils import request
from telegram.ext.handler import Handler
from telegram.utils.deprecate import deprecate

logging.getLogger(__name__).addHandler(NullHandler())

semaphore = None
async_threads = set()
ASYNC_QUEUE = Queue()
ASYNC_THREADS = set()
""":type: set[Thread]"""
async_lock = Lock()
ASYNC_LOCK = Lock() # guards ASYNC_THREADS
DEFAULT_GROUP = 0


def _pooled():
"""
A wrapper to run a thread in a thread pool
"""
while 1:
try:
func, args, kwargs = ASYNC_QUEUE.get()

# If unpacking fails, the thread pool is being closed from Updater._join_async_threads
except TypeError:
logging.getLogger(__name__).debug("Closing run_async thread %s/%d" %
(current_thread().getName(), len(ASYNC_THREADS)))
break

try:
func(*args, **kwargs)

except:
logging.getLogger(__name__).exception("run_async function raised exception")


def run_async(func):
"""
Function decorator that will run the function in a new thread.
Expand All @@ -53,30 +76,11 @@ def run_async(func):
# set a threading.Event to notify caller thread

@wraps(func)
def pooled(*pargs, **kwargs):
"""
A wrapper to run a thread in a thread pool
"""
try:
result = func(*pargs, **kwargs)
finally:
semaphore.release()

with async_lock:
async_threads.remove(current_thread())
return result

@wraps(func)
def async_func(*pargs, **kwargs):
def async_func(*args, **kwargs):
"""
A wrapper to run a function in a thread
"""
thread = Thread(target=pooled, args=pargs, kwargs=kwargs)
semaphore.acquire()
with async_lock:
async_threads.add(thread)
thread.start()
return thread
ASYNC_QUEUE.put((func, args, kwargs))

return async_func

Expand Down Expand Up @@ -107,11 +111,18 @@ def __init__(self, bot, update_queue, workers=4, exception_event=None):
self.__stop_event = Event()
self.__exception_event = exception_event or Event()

global semaphore
if not semaphore:
semaphore = BoundedSemaphore(value=workers)
else:
self.logger.debug('Semaphore already initialized, skipping.')
with ASYNC_LOCK:
if not ASYNC_THREADS:
if request.is_con_pool_initialized():
raise RuntimeError('Connection Pool already initialized')

request.CON_POOL_SIZE = workers + 3
for i in range(workers):
thread = Thread(target=_pooled, name=str(i))
ASYNC_THREADS.add(thread)
thread.start()
else:
self.logger.debug('Thread pool already initialized, skipping.')

def start(self):
"""
Expand All @@ -131,7 +142,7 @@ def start(self):
self.running = True
self.logger.debug('Dispatcher started')

while True:
while 1:
try:
# Pop update from update queue.
update = self.update_queue.get(True, 1)
Expand All @@ -145,7 +156,7 @@ def start(self):
continue

self.logger.debug('Processing Update: %s' % update)
self.processUpdate(update)
self.process_update(update)

self.running = False
self.logger.debug('Dispatcher thread stopped')
Expand All @@ -160,7 +171,7 @@ def stop(self):
sleep(0.1)
self.__stop_event.clear()

def processUpdate(self, update):
def process_update(self, update):
"""
Processes a single update.

Expand All @@ -170,7 +181,7 @@ def processUpdate(self, update):

# An error happened while polling
if isinstance(update, TelegramError):
self.dispatchError(None, update)
self.dispatch_error(None, update)

else:
for group in self.groups:
Expand All @@ -185,7 +196,7 @@ def processUpdate(self, update):
'Update.')

try:
self.dispatchError(update, te)
self.dispatch_error(update, te)
except Exception:
self.logger.exception('An uncaught error was raised while '
'handling the error')
Expand Down Expand Up @@ -271,7 +282,7 @@ def remove_error_handler(self, callback):
if callback in self.error_handlers:
self.error_handlers.remove(callback)

def dispatchError(self, update, error):
def dispatch_error(self, update, error):
"""
Dispatches an error.

Expand Down
29 changes: 17 additions & 12 deletions telegram/ext/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def _gen_webhook_url(listen, port, url_path):

def _bootstrap(self, max_retries, clean, webhook_url, cert=None):
retries = 0
while True:
while 1:

try:
if clean:
Expand Down Expand Up @@ -346,17 +346,16 @@ def stop(self):

self.job_queue.stop()
with self.__lock:
if self.running:
if self.running or dispatcher.ASYNC_THREADS:
self.logger.debug('Stopping Updater and Dispatcher...')

self.running = False

self._stop_httpd()
self._stop_dispatcher()
self._join_threads()
# async threads must be join()ed only after the dispatcher
# thread was joined, otherwise we can still have new async
# threads dispatched
# async threads must be join()ed only after the dispatcher thread was joined,
# otherwise we can still have new async threads dispatched
self._join_async_threads()

def _stop_httpd(self):
Expand All @@ -372,13 +371,19 @@ def _stop_dispatcher(self):
self.dispatcher.stop()

def _join_async_threads(self):
with dispatcher.async_lock:
threads = list(dispatcher.async_threads)
total = len(threads)
for i, thr in enumerate(threads):
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i, total))
thr.join()
self.logger.debug('async thread {0}/{1} has ended'.format(i, total))
with dispatcher.ASYNC_LOCK:
threads = list(dispatcher.ASYNC_THREADS)
total = len(threads)

# 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)

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))

def _join_threads(self):
for thr in self.__threads:
Expand Down
Loading