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
eggs
parts
testing.log
.dir-locals.el
......@@ -5,6 +5,7 @@ python:
- 3.2
- 3.3
- 3.4
- pypy
before_install:
# Workaround for a permissions issue with Travis virtual machine images
# that breaks Python's multiprocessing:
......
......@@ -19,6 +19,7 @@ ClientStorage -- the main class, implementing the Storage API
"""
import BTrees.IOBTree
import gc
import logging
import os
import re
......@@ -57,6 +58,19 @@ try:
except ImportError:
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):
return str(TimeStamp(tid))
......@@ -571,7 +585,7 @@ class ClientStorage(object):
# TODO: Should we check the protocol version here?
conn._is_read_only = self._is_read_only
stub = self.StorageServerStubClass(conn)
auth = stub.getAuthProtocol()
logger.info("%s Server authentication protocol %r", self.__name__, auth)
if auth:
......@@ -1543,6 +1557,9 @@ class ClientStorage(object):
self._iterator_ids.clear()
return
if _ITERATOR_GC_NEEDS_GC:
gc.collect()
iids = self._iterator_ids - set(self._iterators)
if iids:
try:
......
......@@ -495,7 +495,7 @@ class ZEOStorage:
self.storage.tpc_abort(self.transaction)
self._clear_transaction()
if delay is not None:
delay.error()
delay.error(sys.exc_info())
else:
raise
else:
......@@ -687,7 +687,7 @@ class ZEOStorage:
if PY3:
pickler = Pickler(BytesIO(), 3)
else:
pickler = Pickler()
pickler = Pickler(0) # The pure-python version requires at least one argument (PyPy)
pickler.fast = 1
try:
pickler.dump(error)
......@@ -1631,4 +1631,3 @@ class Serving(ServerEvent):
class Closed(ServerEvent):
pass
......@@ -14,9 +14,11 @@
"""Python versions compatiblity
"""
import sys
import platform
PY3 = sys.version_info[0] >= 3
PY32 = sys.version_info[:2] == (3, 2)
PYPY = getattr(platform, 'python_implementation', lambda: None)() == 'PyPy'
if PY3:
from pickle import Pickler, Unpickler as _Unpickler, dump, dumps, loads
......@@ -55,4 +57,3 @@ try:
from cStringIO import StringIO
except:
from io import StringIO
......@@ -71,14 +71,20 @@ class Database:
def save(self, fd=None):
filename = self.filename
needs_closed = False
if not fd:
fd = open(filename, 'w')
if self.realm:
print("realm", self.realm, file=fd)
needs_closed = True
try:
if self.realm:
print("realm", self.realm, file=fd)
for username in sorted(self._users.keys()):
print("%s: %s" % (username, self._users[username]), file=fd)
for username in sorted(self._users.keys()):
print("%s: %s" % (username, self._users[username]), file=fd)
finally:
if needs_closed:
fd.close()
def load(self):
filename = self.filename
......@@ -88,8 +94,8 @@ class Database:
if not os.path.exists(filename):
return
fd = open(filename)
L = fd.readlines()
with open(filename) as fd:
L = fd.readlines()
if not L:
return
......
......@@ -403,6 +403,7 @@ class InvalidationTests:
self._check_tree(cn, tree)
self._check_threads(tree, *threads)
transaction.abort()
cn.close()
_ = [db.close() for db in dbs]
......
......@@ -13,7 +13,7 @@
##############################################################################
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.log import log, short_repr
......@@ -41,12 +41,23 @@ def encode(*args): # args: (msgid, flags, name, args)
else:
pickler = Pickler(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:
# XXX: Py3: Needs optimization.
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:
def fast_encode():
# Only use in cases where you *know* the data contains only basic
......@@ -63,7 +74,10 @@ def decode(msg):
"""Decodes msg and returns its parts"""
unpickler = Unpickler(BytesIO(msg))
unpickler.find_global = find_global
try:
unpickler.find_class = find_global # PyPy, zodbpickle, the non-c-accelerated version
except AttributeError:
pass
try:
return unpickler.load() # msgid, flags, name, args
except:
......@@ -75,6 +89,10 @@ def server_decode(msg):
"""Decodes msg and returns its parts"""
unpickler = Unpickler(BytesIO(msg))
unpickler.find_global = server_find_global
try:
unpickler.find_class = server_find_global # PyPy, zodbpickle, the non-c-accelerated version
except AttributeError:
pass
try:
return unpickler.load() # msgid, flags, name, args
......
[tox]
envlist =
py26,py27,py32,py33,py34,simple
py26,py27,py32,py33,py34,pypy,simple
[testenv]
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