FSX / momoko

Wraps (asynchronous) Psycopg2 for Tornado.
http://momoko.61924.nl/
Other
364 stars 73 forks source link

Please help me figure out why this code freezes forever #152

Closed vladiibine closed 7 years ago

vladiibine commented 7 years ago

We're including momoko in my company's code, because we saw it's cool.

However this first interaction with it didn't go so smoothly.

Could you please figure out what's wrong with our code?

I've boiled down our app's code to this:

import json
import os

from tornado.web import Application, RequestHandler
from tornado import gen
from tornado.ioloop import IOLoop
from tornado.testing import AsyncHTTPTestCase

import momoko

class MyApp(Application):
    def __init__(self, handlers, **settings):
        ioloop = IOLoop.instance()
        self.db = momoko.Pool(
            size=int(10),
            ioloop=ioloop,
            dsn="dbname={db_name} user={db_user} host={db_host} port={db_port}".format(
                db_name=os.environ['DBNAME'],
                db_user=os.environ['DBUSER'],
                db_host=os.environ['DBHOST'],
                db_port=int(os.environ['DBPORT']))
        )
        try:
            print("Reaches here - calling pool.connect() - mm85")
            pool_future = self.db.connect()
            ioloop.add_future(pool_future, lambda f: ioloop.stop())
            ioloop.start()
        except Exception as err:
            print("Doesn't reach here - no exception raies from pool.connect() -  3nn45")
            raise err

        super(MyApp, self).__init__(handlers, **settings)

class TestPost(AsyncHTTPTestCase):
    def get_app(self):
        handlers = [(r'/', MyRequestHandler)]

        return MyApp(handlers, **{'autoreload': 'True'})

    def test_200_on_good_data(self):
        print("Just before making the http request - mau5")
        response = self.fetch('/', body='request_body', method='POST', request_timeout=99999)
        print("Never returns from making the request - 99ggui")

        response_json = json.loads(response.body)

        assert response.code == 200, response.body

class MyRequestHandler(RequestHandler):
    @property
    def db(self):
        return self.application.db

    @gen.coroutine
    def post(self, partner_id='mock', *args, **kwargs):
        try:
            print("Before running pool.execute()  - btyg8")
            self.write('Happy text - oblivious to the fact that it will not reach the tests')

            received_obj = self.db.execute('select 1')
            print("db.execute returned this: {}".format(received_obj))

            success = yield received_obj
            print("Never reaches 7hrh")

        except Exception as err:
            print("Never reaches this c52")
            raise err

        except:
            print("Never reaches this 36h")
            raise

        else:
            print("Never reaches this 41d")

        print("Never reaches this 5")
        self.write('Again text that will neveer make it to the test')

The output after running this script is this:

Reaches here - calling pool.connect() - mm85
Just before making the http request - mau5
Before running pool.execute()  - btyg8
db.execute returned this: <tornado.concurrent.Future object at 0x7fa5d24382d0>

The pool.execute('select 1') freezes forever.

While debugging I got to somewhere in the code where there is a future with a connection set as the result. However this never passes.

What I did at first, was to not include those lines

pool_future = self.db.connect()
ioloop.add_future(pool_future, lambda f: ioloop.stop())
ioloop.start()

...but that only gets me an error which says I'm a necromancer or something: "AssertionError: BUG: don't call reanimate when there is no one to reanimate" :grinning:

...So could you please guide me a little? I've lost 2 days of work because of this already.

Thanks a lot!

vladiibine commented 7 years ago

momoko version ==2.2.4. I tried with Momoko==1.1.6 but then I only get a None object after calling db.execute.

haizaar commented 7 years ago

When you hit CTRL-C in a freezed app, what do you see in Traceback?

haizaar commented 7 years ago

One more question: how do you run this script exactly? (there is no main...)

vladiibine commented 7 years ago

I forgot to specify that I'm using py.test to test this, but you probably could tell that.

Anyway, this is the traceback if I run the module with unittest, - the same error still happens:

(venv) 14:37]user ~/work/my-app $ python my_script.py
Reaches here - calling pool.connect() - mm85
Just before making the http request - mau5
Before running pool.execute()  - btyg8
db.execute returned this: <tornado.concurrent.Future object at 0x7f6b159ed7d0>
^CTraceback (most recent call last):
  File "my_script.py", line 88, in <module>
    unittest.main()
  File "/usr/lib/python2.7/unittest/main.py", line 95, in __init__
    self.runTests()
  File "/usr/lib/python2.7/unittest/main.py", line 232, in runTests
    self.result = testRunner.run(self.test)
  File "/usr/lib/python2.7/unittest/runner.py", line 151, in run
    test(result)
  File "/usr/lib/python2.7/unittest/suite.py", line 70, in __call__
    return self.run(*args, **kwds)
  File "/usr/lib/python2.7/unittest/suite.py", line 108, in run
    test(result)
  File "/usr/lib/python2.7/unittest/suite.py", line 70, in __call__
    return self.run(*args, **kwds)
  File "/usr/lib/python2.7/unittest/suite.py", line 108, in run
    test(result)
  File "/usr/lib/python2.7/unittest/case.py", line 395, in __call__
    return self.run(*args, **kwds)
  File "/path/to/site-packages/tornado/testing.py", line 281, in run
    self.__rethrow()
  File "/path/to/site-packages/tornado/testing.py", line 272, in __rethrow
    raise_exc_info(failure)
  File "/path/to/site-packages/tornado/testing.py", line 276, in run
    super(AsyncTestCase, self).run(result)
  File "/usr/lib/python2.7/unittest/case.py", line 331, in run
    testMethod()
  File "/path/to/site-packages/tornado/testing.py", line 136, in __call__
    result = self.orig_method(*args, **kwargs)
  File "my_script.py", line 43, in test_200_on_good_data
    response = self.fetch('/', body='request_body', method='POST', request_timeout=99999)
  File "/path/to/site-packages/tornado/testing.py", line 406, in fetch
    return self.wait()
  File "/path/to/site-packages/tornado/testing.py", line 327, in wait
    self.io_loop.start()
  File "/path/to/site-packages/tornado/ioloop.py", line 862, in start
    event_pairs = self._impl.poll(poll_timeout)
KeyboardInterrupt
vladiibine commented 7 years ago

More info: 2 IOLoop instances are being created in this test. One is created in the app, and another is created by the AsyncHTTPTestCase...I don't know why or whether it helps.

haizaar commented 7 years ago

Yes, that's exactly the problem. When you bootstrap your app, you create an ioloop instance and pass it to db pool. The pool uses this instance. However when you run async test case, it creates its own instance of ioloop and starts it. I.e. db pool add callbacks to its own version of ioloop, that is not running, that's why nothing happens.

I suggest you create db pool during async test setUp method and pass it your app, to make sure that same io loop is used everywhere.

vladiibine commented 7 years ago

That was it. Thanks a lot for the help :)

haizaar commented 7 years ago

Keep enjoying Momoko :)