Commit b13c4694 authored by Jason Madden's avatar Jason Madden

Support for PyPy. All unit and functional tests pass.

parent 96b1702a
...@@ -8,3 +8,4 @@ develop-eggs ...@@ -8,3 +8,4 @@ develop-eggs
eggs eggs
parts parts
testing.log testing.log
.dir-locals.el
...@@ -5,6 +5,7 @@ python: ...@@ -5,6 +5,7 @@ python:
- 3.2 - 3.2
- 3.3 - 3.3
- 3.4 - 3.4
- pypy
before_install: before_install:
# Workaround for a permissions issue with Travis virtual machine images # Workaround for a permissions issue with Travis virtual machine images
# that breaks Python's multiprocessing: # that breaks Python's multiprocessing:
......
...@@ -19,6 +19,7 @@ ClientStorage -- the main class, implementing the Storage API ...@@ -19,6 +19,7 @@ ClientStorage -- the main class, implementing the Storage API
""" """
import BTrees.IOBTree import BTrees.IOBTree
import gc
import logging import logging
import os import os
import re import re
...@@ -57,6 +58,19 @@ try: ...@@ -57,6 +58,19 @@ try:
except ImportError: except ImportError:
ResolvedSerial = 'rs' ResolvedSerial = 'rs'
# ClientStorage keeps track of open iterators in a
# WeakValueDictionary. Under non-refcounted implementations,
# like PyPy, the weak references are only cleared when
# a GC runs. To make sure they are cleared when requested,
# we request a GC in that case.
# XXX: Is this needed? Do we have to dispose of them in a timely
# fashion? There are a few tests in IterationTests.py that
# directly check the length of the internal data structures, but
# if they stick around a bit longer is that visible to real clients,
# or could cause any problems (E.g., reuse of ids?)
_ITERATOR_GC_NEEDS_GC = not hasattr(sys, 'getrefcount')
def tid2time(tid): def tid2time(tid):
return str(TimeStamp(tid)) return str(TimeStamp(tid))
...@@ -1543,6 +1557,9 @@ class ClientStorage(object): ...@@ -1543,6 +1557,9 @@ class ClientStorage(object):
self._iterator_ids.clear() self._iterator_ids.clear()
return return
if _ITERATOR_GC_NEEDS_GC:
gc.collect()
iids = self._iterator_ids - set(self._iterators) iids = self._iterator_ids - set(self._iterators)
if iids: if iids:
try: try:
......
...@@ -495,7 +495,7 @@ class ZEOStorage: ...@@ -495,7 +495,7 @@ class ZEOStorage:
self.storage.tpc_abort(self.transaction) self.storage.tpc_abort(self.transaction)
self._clear_transaction() self._clear_transaction()
if delay is not None: if delay is not None:
delay.error() delay.error(sys.exc_info())
else: else:
raise raise
else: else:
...@@ -687,7 +687,7 @@ class ZEOStorage: ...@@ -687,7 +687,7 @@ class ZEOStorage:
if PY3: if PY3:
pickler = Pickler(BytesIO(), 3) pickler = Pickler(BytesIO(), 3)
else: else:
pickler = Pickler() pickler = Pickler(0) # The pure-python version requires at least one argument (PyPy)
pickler.fast = 1 pickler.fast = 1
try: try:
pickler.dump(error) pickler.dump(error)
...@@ -1631,4 +1631,3 @@ class Serving(ServerEvent): ...@@ -1631,4 +1631,3 @@ class Serving(ServerEvent):
class Closed(ServerEvent): class Closed(ServerEvent):
pass pass
...@@ -14,9 +14,11 @@ ...@@ -14,9 +14,11 @@
"""Python versions compatiblity """Python versions compatiblity
""" """
import sys import sys
import platform
PY3 = sys.version_info[0] >= 3 PY3 = sys.version_info[0] >= 3
PY32 = sys.version_info[:2] == (3, 2) PY32 = sys.version_info[:2] == (3, 2)
PYPY = getattr(platform, 'python_implementation', lambda: None)() == 'PyPy'
if PY3: if PY3:
from pickle import Pickler, Unpickler as _Unpickler, dump, dumps, loads from pickle import Pickler, Unpickler as _Unpickler, dump, dumps, loads
...@@ -55,4 +57,3 @@ try: ...@@ -55,4 +57,3 @@ try:
from cStringIO import StringIO from cStringIO import StringIO
except: except:
from io import StringIO from io import StringIO
...@@ -71,14 +71,20 @@ class Database: ...@@ -71,14 +71,20 @@ class Database:
def save(self, fd=None): def save(self, fd=None):
filename = self.filename filename = self.filename
needs_closed = False
if not fd: if not fd:
fd = open(filename, 'w') fd = open(filename, 'w')
needs_closed = True
try:
if self.realm: if self.realm:
print("realm", self.realm, file=fd) print("realm", self.realm, file=fd)
for username in sorted(self._users.keys()): for username in sorted(self._users.keys()):
print("%s: %s" % (username, self._users[username]), file=fd) print("%s: %s" % (username, self._users[username]), file=fd)
finally:
if needs_closed:
fd.close()
def load(self): def load(self):
filename = self.filename filename = self.filename
...@@ -88,7 +94,7 @@ class Database: ...@@ -88,7 +94,7 @@ class Database:
if not os.path.exists(filename): if not os.path.exists(filename):
return return
fd = open(filename) with open(filename) as fd:
L = fd.readlines() L = fd.readlines()
if not L: if not L:
......
...@@ -403,6 +403,7 @@ class InvalidationTests: ...@@ -403,6 +403,7 @@ class InvalidationTests:
self._check_tree(cn, tree) self._check_tree(cn, tree)
self._check_threads(tree, *threads) self._check_threads(tree, *threads)
transaction.abort()
cn.close() cn.close()
_ = [db.close() for db in dbs] _ = [db.close() for db in dbs]
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
import logging import logging
from ZEO._compat import Unpickler, Pickler, BytesIO, PY3 from ZEO._compat import Unpickler, Pickler, BytesIO, PY3, PYPY
from ZEO.zrpc.error import ZRPCError from ZEO.zrpc.error import ZRPCError
from ZEO.zrpc.log import log, short_repr from ZEO.zrpc.log import log, short_repr
...@@ -41,12 +41,23 @@ def encode(*args): # args: (msgid, flags, name, args) ...@@ -41,12 +41,23 @@ def encode(*args): # args: (msgid, flags, name, args)
else: else:
pickler = Pickler(1) pickler = Pickler(1)
pickler.fast = 1 pickler.fast = 1
return pickler.dump(args, 1) # Only CPython's cPickle supports dumping
# and returning in one operation:
# return pickler.dump(args, 1)
# For PyPy we must return the value; fortunately this
# works the same on CPython and is no more expensive
pickler.dump(args)
return pickler.getvalue()
if PY3: if PY3:
# XXX: Py3: Needs optimization. # XXX: Py3: Needs optimization.
fast_encode = encode fast_encode = encode
elif PYPY:
# can't use the python-2 branch, need a new pickler
# every time, getvalue() only works once
fast_encode = encode
else: else:
def fast_encode(): def fast_encode():
# Only use in cases where you *know* the data contains only basic # Only use in cases where you *know* the data contains only basic
...@@ -63,7 +74,10 @@ def decode(msg): ...@@ -63,7 +74,10 @@ def decode(msg):
"""Decodes msg and returns its parts""" """Decodes msg and returns its parts"""
unpickler = Unpickler(BytesIO(msg)) unpickler = Unpickler(BytesIO(msg))
unpickler.find_global = find_global unpickler.find_global = find_global
try:
unpickler.find_class = find_global # PyPy, zodbpickle, the non-c-accelerated version
except AttributeError:
pass
try: try:
return unpickler.load() # msgid, flags, name, args return unpickler.load() # msgid, flags, name, args
except: except:
...@@ -75,6 +89,10 @@ def server_decode(msg): ...@@ -75,6 +89,10 @@ def server_decode(msg):
"""Decodes msg and returns its parts""" """Decodes msg and returns its parts"""
unpickler = Unpickler(BytesIO(msg)) unpickler = Unpickler(BytesIO(msg))
unpickler.find_global = server_find_global unpickler.find_global = server_find_global
try:
unpickler.find_class = server_find_global # PyPy, zodbpickle, the non-c-accelerated version
except AttributeError:
pass
try: try:
return unpickler.load() # msgid, flags, name, args return unpickler.load() # msgid, flags, name, args
......
[tox] [tox]
envlist = envlist =
py26,py27,py32,py33,py34,simple py26,py27,py32,py33,py34,pypy,simple
[testenv] [testenv]
commands = commands =
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment