Repository: python-telegram-bot/python-telegram-bot

Issue:
Race condition in Dispatcher.start/stop()
### Steps to reproduce
Run slighly modified example from https://python-telegram-bot.org/
```python
from telegram.ext import Updater, CommandHandler

def start(bot, update):
    update.message.reply_text('Hello World!')

def hello(bot, update):
    update.message.reply_text(
        'Hello {}'.format(update.message.from_user.first_name))

updater = Updater('YOUR TOKEN HERE')

updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CommandHandler('hello', hello))

updater.start_polling()
try:
    # this call to non-existing function may cause dispatcher.stop() to be called
    # before dispatcher.start() had time to set self.running=True,
    # thus stop() does nothing and dispatcher continues to run.
    CALL_TO_NON_EXISTING_FUNC()
finally:
    updater.stop()
```

### Expected behaviour
Application should stop

### Actual behaviour
Application continues to run and does not reacting to CTRL+C

### Configuration
**Operating System:**
Linux

**Version of Python, python-telegram-bot & dependencies:**

``$ python -m telegram``
python-telegram-bot 8.1.1
certifi 2017.07.27.1
future 0.15.2
Python 2.7.13 (default, Jan 19 2017, 14:48:08)  [GCC 6.3.0 20170118]


### Logs
Insert logs here (if necessary)
Not necessary
Race condition in Dispatcher.start/stop()
### Steps to reproduce
Run slighly modified example from https://python-telegram-bot.org/
```python
from telegram.ext import Updater, CommandHandler

def start(bot, update):
    update.message.reply_text('Hello World!')

def hello(bot, update):
    update.message.reply_text(
        'Hello {}'.format(update.message.from_user.first_name))

updater = Updater('YOUR TOKEN HERE')

updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CommandHandler('hello', hello))

updater.start_polling()
try:
    # this call to non-existing function may cause dispatcher.stop() to be called
    # before dispatcher.start() had time to set self.running=True,
    # thus stop() does nothing and dispatcher continues to run.
    CALL_TO_NON_EXISTING_FUNC()
finally:
    updater.stop()
```

### Expected behaviour
Application should stop

### Actual behaviour
Application continues to run and does not reacting to CTRL+C

### Configuration
**Operating System:**
Linux

**Version of Python, python-telegram-bot & dependencies:**

``$ python -m telegram``
python-telegram-bot 8.1.1
certifi 2017.07.27.1
future 0.15.2
Python 2.7.13 (default, Jan 19 2017, 14:48:08)  [GCC 6.3.0 20170118]


### Logs
Insert logs here (if necessary)
Not necessary


Discussion:
@subdir Hmm I'm on a phone right now and can't test your example, but it seems to me like the running flag is set synchronously so I don't think that it is the cause of this problem https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/updater.py#L198
@subdir Scratch that I misread. Dispatcher != Updater
@subdir Hmm I'm on a phone right now and can't test your example, but it seems to me like the running flag is set synchronously so I don't think that it is the cause of this problem https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/updater.py#L198
@subdir Scratch that I misread. Dispatcher != Updater

Resolving patch:
diff --git a/telegram/ext/dispatcher.py b/telegram/ext/dispatcher.py
--- a/telegram/ext/dispatcher.py
+++ b/telegram/ext/dispatcher.py
@@ -183,14 +183,20 @@ def run_async(self, func, *args, **kwargs):
         self.__async_queue.put(promise)
         return promise
 
-    def start(self):
+    def start(self, ready=None):
         """Thread target of thread 'dispatcher'.
 
         Runs in background and processes the update queue.
 
+        Args:
+            ready (:obj:`threading.Event`, optional): If specified, the event will be set once the
+                dispatcher is ready.
+
         """
         if self.running:
             self.logger.warning('already running')
+            if ready is not None:
+                ready.set()
             return
 
         if self.__exception_event.is_set():
@@ -202,6 +208,9 @@ def start(self):
         self.running = True
         self.logger.debug('Dispatcher started')
 
+        if ready is not None:
+            ready.set()
+
         while 1:
             try:
                 # Pop update from update queue.
diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py
--- a/telegram/ext/updater.py
+++ b/telegram/ext/updater.py
@@ -199,10 +199,13 @@ def start_polling(self,
 
                 # Create & start threads
                 self.job_queue.start()
-                self._init_thread(self.dispatcher.start, "dispatcher")
+                dispatcher_ready = Event()
+                self._init_thread(self.dispatcher.start, "dispatcher", ready=dispatcher_ready)
                 self._init_thread(self._start_polling, "updater", poll_interval, timeout,
                                   read_latency, bootstrap_retries, clean, allowed_updates)
 
+                dispatcher_ready.wait()
+
                 # Return the update queue so the main thread can insert updates
                 return self.update_queue
 


Test patch: