Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility with PyPy #2

Merged
merged 2 commits into from
Jan 27, 2017
Merged

Compatibility with PyPy #2

merged 2 commits into from
Jan 27, 2017

Conversation

jamadden
Copy link
Contributor

I notice that setup.py specifies both RelStorage[postgresql] and also psycopg2. The former will work correctly on PyPy, but the latter will not (you want psycopg2cffi).

There doesn't seem to be any actual usage of psycopg2 in the code itself, only in a test case:

newtdb$ ack psycopg2
setup.py
3:install_requires = ['setuptools', 'RelStorage[postgresql]', 'psycopg2', 'six']

src/newt/db/_search.py
6:import psycopg2

src/newt/db/tests/base.py
1:import psycopg2
13:        self.base_conn = psycopg2.connect('')

If you need to connect to a database in a test case, you could try this:

import relstorage.adapters.postgresql.adapter as adapter
self.base_conn = adapter.select_driver().connect('')

Then in theory you should Just Work with any supported driver, assuming you stick to the DB-API. Or at least, psyc2pg2 and psycopg2cffi pretty much implement the same extensions.

Adding - pypy-5.4.1 to .travis.yml should run your tests on a new-ish pypy.

@jamadden
Copy link
Contributor Author

Something in the test suite is leaking an open connection (that gets reclaimed by garbage collection), hence the failure. I'm trying to track it down, but it's proving elusive.

@jamadden
Copy link
Contributor Author

ZODB.tests.TransactionalUndoStorage definitely leaks open databases and connections but I don't see how that could be related

@jamadden
Copy link
Contributor Author

I'm pretty sure I've tracked the leaked objects that cause this failure down to relstorage.tests.RecoveryStorage:UndoableRecoveryStorage.checkRestoreAcrossPack (and possibly checkRestoreWithMultipleObjectsInUndoRedo). It takes some weird manipulations but I can reproduce the errors now.

Running the GC here will be the simplest solution, I think, until there's an updated RelStorage release that closes things up properly.

jamadden added a commit to zodb/relstorage that referenced this pull request Jan 25, 2017
I notice that setup.py specifies both `RelStorage[postgresql]` and also `psycopg2`. The former will work correctly on PyPy, but the latter will not (you want `psycopg2cffi`).

There doesn't seem to be any actual usage of psycopg2 in the code itself, only in a test case:
```
newtdb$ ack psycopg2
setup.py
3:install_requires = ['setuptools', 'RelStorage[postgresql]', 'psycopg2', 'six']

src/newt/db/_search.py
6:import psycopg2

src/newt/db/tests/base.py
1:import psycopg2
13:        self.base_conn = psycopg2.connect('')
```

If you need to connect to a database in a test case, you could try this:
```
import relstorage.adapters.postgresql.adapter as adapter
self.base_conn = adapter.select_driver().connect('')
```

Then in theory you should Just Work with any supported driver, assuming you stick to the DB-API. Or at least, psyc2pg2 and psycopg2cffi pretty much implement the same extensions.

Adding `- pypy-5.4.1` to `.travis.yml` should run your tests on a new-ish pypy.
@jamadden
Copy link
Contributor Author

Thoughts @jimfulton ?

@jimfulton
Copy link
Contributor

Hm, I wasn't notified of this PR. I wonder what I need to do to fix that... sigh

@jimfulton
Copy link
Contributor

Thanks for this! I knew I had some debt in this area. :)

I'll look at this shortly.

@jimfulton
Copy link
Contributor

Oh, I needed to watch this repo. :)

@@ -173,6 +173,10 @@ def create_text_index(conn, fname, D, C=None, B=None, A=None):
conn.commit()
finally:
try:
cursor.close()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really? Closing the connection doesn't close the cursor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most DBAPI implementations I've looked at (far too many 😄 ), the cursor has a reference to the connection, but the connection does not have references to the cursors that are opened.

Now, closing the connection will close the socket, it's true. And that would probably be enough to solve this particular case. In general, however, it's best to be sure to close the cursors, I've found. A buffered cursor (most of them are by default) will have the last row set in memory and it won't get rid of it until its closed (or GCd), so closing it allows resources to be cleaned up sooner and deterministically. With unbuffered cursors, if you don't close the cursor then when you close the connection it will detect that it was in the middle of a result set and complain either by logging a warning (pymysql) or raising an error (Oracle's official driver). I haven't tried unbuffered cursors on postgres so I don't know what they do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the leak check code that I use in RelStorage complains when both cursors and connections are left open to be GCd. That's how I found this.

@jimfulton jimfulton merged commit 902700f into newtdb:master Jan 27, 2017
@jamadden
Copy link
Contributor Author

Thanks. Sorry about the unpleasantness of needing to call gc.collect() due to the leaking test case in RelStorage. But since there's also a leaking test case in ZODB itself, I'm not too sorry 😉 I'll try to submit a PR against ZODB.

@jimfulton
Copy link
Contributor

Gaaaa, it looks like pypy and cpython eggs get named the same thing. This is a pain for buildlut because of it's shared eggs directory. whimper.

@jamadden
Copy link
Contributor Author

There's no binary (native) code here, so why does it matter? Don't both interpreters ignore bad .pyc files if there's a .py file too?

Which reminds me, there should probably be a setup.cfg file with

[bdist_wheel]
universal = 1

So that creating wheels and uploading them to PyPI does the right thing.

@jamadden
Copy link
Contributor Author

Oh, or is it the native code in the RelStorage/persistent/BTree eggs that causing the problems? That I've definitely run into before and had to resort to separate egg directories.

@jimfulton
Copy link
Contributor

idk what's going on. I have to remove RelStorage and cffi eggs from my egg cache when switching between cpython and pypy.

For example, switching to pypy without removing those eggs:

ImportError: unable to load extension module '/Users/jim/.buildout/eggs/cffi-1.9.1-py2.7-macosx-10.10-x86_64.egg/_cffi_backend.so': dlopen(/Users/jim/.buildout/eggs/cffi-1.9.1-py2.7-macosx-10.10-x86_64.egg/_cffi_backend.so, 6): Symbol not found: _PyBuffer_Type
  Referenced from: /Users/jim/.buildout/eggs/cffi-1.9.1-py2.7-macosx-10.10-x86_64.egg/_cffi_backend.so
  Expected in: flat namespace
 in /Users/jim/.buildout/eggs/cffi-1.9.1-py2.7-macosx-10.10-x86_64.egg/_cffi_backend.so

Also, RelStorage's egg name is: RelStorage-2.0.0-py2.7-macosx-10.10-x86_64.egg, which suggests something thinks it has binary bits :) Oh, cache_ring.

If I remove the cffi egg and rerun buildout, I get:

ImportError: unable to load extension module '/Users/jim/.buildout/eggs/RelStorage-2.0.0-py2.7-macosx-10.10-x86_64.egg/relstorage/cache/_cache_ring.so': dlopen(/Users/jim/.buildout/eggs/RelStorage-2.0.0-py2.7-macosx-10.10-x86_64.egg/relstorage/cache/_cache_ring.so, 6): Symbol not found: __Py_NoneStruct
  Referenced from: /Users/jim/.buildout/eggs/RelStorage-2.0.0-py2.7-macosx-10.10-x86_64.egg/relstorage/cache/_cache_ring.so
  Expected in: flat namespace
 in /Users/jim/.buildout/eggs/RelStorage-2.0.0-py2.7-macosx-10.10-x86_64.egg/relstorage/cache/_cache_ring.so

@jimfulton
Copy link
Contributor

I don't really understand how pypy handles extensions. When building with pypy, I get the same egg names as with cpython. I wonder if there's something else going on here.

@jamadden
Copy link
Contributor Author

jamadden commented Jan 27, 2017

No, that's what's happening. I've seen the same thing with persistent and other things that use CFFI or optional native code. Wheels use a different wheel "ABI tag" for PyPy and CPython (pypy_41 and cp27-cp27m[u] respectively), but traditional setuptools doesn't know about ABI tags, just the architecture...you'd have the same problem, for example, switching between wide and narrow unicode builds of CPython 2.

@jimfulton
Copy link
Contributor

Yup. But I'd have no reason to switch back and forth between those. sigh

@jamadden
Copy link
Contributor Author

jamadden commented Jan 27, 2017

This is a large part of why I now typically prefer to avoid buildout in favor of virtualenvs for individual projects (reserving buildout for integration of applications). I work on a lot of projects that use CFFI/native code, all of which support PyPy 😄

@jimfulton
Copy link
Contributor

buildout/buildout#325 :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants