Skip to content

Instantly share code, notes, and snippets.

@ORESoftware ORESoftware/
Last active Mar 19, 2019

What would you like to do?
Python swallows an error

Run this container (it's a server):

docker run -p 6970:6970 -it oresoftware/live-mutex-broker:latest

then run this python script with Python 3.6.7:

import concurrent.futures
import time
from threading import Timer
import socket
import json
import uuid
import math
import sys
import logging
import atexit

executor = concurrent.futures.ThreadPoolExecutor(max_workers=3)

class LMXClient:

    def __init__(self, port, host):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) = host
        self.connected = False
        self.port = port
        self.future = None
        self.resolutions = {}
        self.timeouts = {}
        # atexit.register(self.handle_exit)

    def handle_exit(self):

    def disconnect(self):
        self.connected = False
        print ("closed the socket.")

    def send_message(self, d):
        data = (json.dumps(d) + '\n').encode()

    def connect(self):
        self.s.connect(('localhost', 6970))
        self.connected = True
        self.future = executor.submit(self.listen_for_messages)
        return self

    def lock(self, key, cb):
        call_id = uuid.uuid4()

        lock_data = {
            'keepLocksAfterDeath': False,
            'retryCount': 2,
            'uuid': str(call_id),
            'key': key,
            'type': 'lock',
            'ttl': 3000,
            'rwStatus': None,
            'max': 1

        self.timeouts[str(call_id)] = None
        self.resolutions[str(call_id)] = self.make_lock_acquired

    def make_lock_acquired(self, cb, bar):
        print('lock was acquired.')

    def unlock(self, trick):

    def on_data(self, d):

        print ('json load', d)

        uuid = d['uuid']

        print ('here is uuid:', uuid)

        if uuid is None:
            logging.warning('warning', 'Function and timeout both exist => Live-Mutex implementation error.')


        fn = self.resolutions[uuid]

        print ('after fn:', fn)
        to = self.timeouts[uuid]

        print ('after to:', to)
        self.resolutions.pop(uuid, None)
        self.timeouts.pop(uuid, None)

        print('here is fn:', fn)
        print ('here is to:', to)

        if fn is not None and to is not None:
            logging.warning('warning', 'Function and timeout both exist => Live-Mutex implementation error.')

        if to is not None:
            logging.warning('warning', 'Client side lock/unlock request timed-out.')

        if fn is not None:
            print('ok we are running the func')

            if "error" not in d:

            setattr(d,'error',None)  # this causes the error, this error gets swallowed

                print('about to run the func')
                fn(d['error'], d)
                print('ran the func 1')
                print("Unexpected error:", sys.exc_info())
            print('ran the func 2')

        print("nothing matched, hmmm")

    def listen_for_messages(self):
        print('listening for socket messages...')
        rec = ''
        while self.connected is True:
            data = self.s.recv(10)
            if not data:
                logging.warning('no data :(')
            rec += data.decode('utf-8')
  'Received', repr(data))
            lines = rec.split("\n")
            rec = ''
            size = len(lines)
            # logging.warning('SIZE SIZE SIZE:',size)
            i = 0
            for line in lines:
                json_str = None
                    # logging.warning('line:', str(line))
                    json_str = json.loads(line)
                    if i < size - 1:
                        logging.warning('warning, could not parse line:', line)
                    if i == size - 1:
                        # if it is the last element, we can keep it, since it might not be complete
                        rec += line
                    if json_str is not None:
                    i += 1

client = LMXClient(6970, 'localhost')

def on_lock_acquired():
    print ('lock was acquired 2.')
    # client.disconnect()

client.lock('foo', on_lock_acquired)

this code crashes the execution:

setattr(d,'error',None)  # this causes an error, and this error gets swallowed

if we comment that line out, then the process continues. There is no error or stack trace when the line is uncommented though. Very strange. A case of Python swallowing errors?


This comment has been minimized.

Copy link

commented Mar 13, 2019

Futures store any result and exception until someone fetches them. You must call future.result() to get the result/exception of the future. Instead, you just throw away the future, thereby ignoring any output.


This comment has been minimized.

Copy link
Owner Author

commented Mar 13, 2019

@maxfischer2781 Ah yes that might explain it, makes perfect sense. I haven't worked with threads in a while since I mostly work with node.js. The way node.js would do it - if there was no error handler attached before the end of the current tick of the event loop, then upon the next tick of the event loop it would print a trace for you, which is really nice behavior, and it would say something like "add an error handler to x to hide this error trace".


This comment has been minimized.

Copy link

commented Mar 19, 2019

There is an error handler, in the form of the future. If you do not want that, use bare threads.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.