def process_queue(self, worker_id): """Continuously processes tasks on the queue.""" # Create a new logs instance (with its own httplib2 instance) so that # there is a separate one for each thread. logs = Logs("twitter-listener-worker-%s" % worker_id, to_cloud=self.logs_to_cloud) logs.debug("Started worker thread: %s" % worker_id) while not self.stop_event.is_set(): try: data = self.queue.get(block=True, timeout=QUEUE_TIMEOUT_S) start_time = time() self.handle_data(logs, data) self.queue.task_done() end_time = time() qsize = self.queue.qsize() logs.debug("Worker %s took %.f ms with %d tasks remaining." % (worker_id, end_time - start_time, qsize)) except Empty: logs.debug("Worker %s timed out on an empty queue." % worker_id) continue except Exception: # The main loop doesn't catch and report exceptions from # background threads, so do that here. logs.catch() logs.debug("Stopped worker thread: %s" % worker_id)
class Main: """A wrapper for the main application logic and retry loop.""" def __init__(self): self.logs = Logs(name="main", to_cloud=LOGS_TO_CLOUD) self.twitter = Twitter(logs_to_cloud=LOGS_TO_CLOUD) self.logs.info("I'm running") self.twitter.test_tweet() def twitter_callback(self, tweet): """Analyzes tweets, trades stocks, and tweets about it.""" # Initialize the Analysis, Logs, Trading, and Twitter instances inside # the callback to create separate httplib2 instances per thread. analysis = Analysis(logs_to_cloud=LOGS_TO_CLOUD) logs = Logs(name="main-callback", to_cloud=LOGS_TO_CLOUD) # Analyze the tweet. companies = analysis.find_companies(tweet) logs.info("Using companies: %s" % companies) if not companies: return # Trade stocks. trading = Trading(logs_to_cloud=LOGS_TO_CLOUD) trading.make_trades(companies) # Tweet about it. twitter = Twitter(logs_to_cloud=LOGS_TO_CLOUD) twitter.tweet(companies, tweet) def run_session(self): """Runs a single streaming session. Logs and cleans up after exceptions. """ self.logs.info("Starting new session.") try: self.twitter.start_streaming(self.twitter_callback) except: self.logs.catch() finally: self.twitter.stop_streaming() self.logs.info("Ending session.") def backoff(self, tries): """Sleeps an exponential number of seconds based on the number of tries. """ delay = BACKOFF_STEP_S * pow(2, tries) self.logs.warn("Waiting for %.1f seconds." % delay) sleep(delay) def run(self): """Runs the main retry loop with exponential backoff.""" tries = 0 while True: # The session blocks until an error occurs. self.run_session() # Remember the first time a backoff sequence starts. now = datetime.now() if tries == 0: self.logs.debug("Starting first backoff sequence.") backoff_start = now # Reset the backoff sequence if the last error was long ago. if (now - backoff_start).total_seconds() > BACKOFF_RESET_S: self.logs.debug("Starting new backoff sequence.") tries = 0 backoff_start = now # Give up after the maximum number of tries. if tries >= MAX_TRIES: self.logs.warn("Exceeded maximum retry count.") break # Wait according to the progression of the backoff sequence. self.backoff(tries) # Increment the number of tries for the next error. tries += 1