diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5d8bb9b..6803c0d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -102,7 +102,7 @@ jobs: - "3.9" - "3.10" - "3.11" - - "3.12.0-alpha.6" + - "3.12.0-alpha.7" os: [ubuntu-20.04, macos-11] exclude: - os: macos-11 @@ -179,15 +179,15 @@ jobs: python setup.py build_ext -i python setup.py bdist_wheel - - name: Install BTrees and dependencies (3.12.0-alpha.6) - if: matrix.python-version == '3.12.0-alpha.6' + - name: Install BTrees and dependencies (3.12.0-alpha.7) + if: matrix.python-version == '3.12.0-alpha.7' run: | # Install to collect dependencies into the (pip) cache. # Use "--pre" here because dependencies with support for this future # Python release may only be available as pre-releases pip install --pre .[test] - name: Install BTrees and dependencies - if: matrix.python-version != '3.12.0-alpha.6' + if: matrix.python-version != '3.12.0-alpha.7' run: | # Install to collect dependencies into the (pip) cache. pip install .[test] @@ -231,7 +231,7 @@ jobs: && startsWith(github.ref, 'refs/tags') && startsWith(runner.os, 'Mac') && !startsWith(matrix.python-version, 'pypy') - && !startsWith(matrix.python-version, '3.12.0-alpha.6') + && !startsWith(matrix.python-version, '3.12.0-alpha.7') env: TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} run: | @@ -250,7 +250,7 @@ jobs: - "3.9" - "3.10" - "3.11" - - "3.12.0-alpha.6" + - "3.12.0-alpha.7" os: [ubuntu-20.04, macos-11] exclude: - os: macos-11 @@ -287,8 +287,8 @@ jobs: with: name: BTrees-${{ runner.os }}-${{ matrix.python-version }}.whl path: dist/ - - name: Install BTrees 3.12.0-alpha.6 - if: ${{ startsWith(matrix.python-version, '3.12.0-alpha.6') }} + - name: Install BTrees 3.12.0-alpha.7 + if: ${{ startsWith(matrix.python-version, '3.12.0-alpha.7') }} run: | pip install -U wheel setuptools # coverage has a wheel on PyPI for a future python version which is @@ -302,7 +302,7 @@ jobs: # Python release may only be available as pre-releases pip install --pre -U -e .[test] - name: Install BTrees - if: ${{ !startsWith(matrix.python-version, '3.12.0-alpha.6') }} + if: ${{ !startsWith(matrix.python-version, '3.12.0-alpha.7') }} run: | pip install -U wheel setuptools pip install -U coverage diff --git a/.meta.toml b/.meta.toml index bea9b0f..4fd359a 100644 --- a/.meta.toml +++ b/.meta.toml @@ -2,7 +2,7 @@ # https://github.com/zopefoundation/meta/tree/master/config/c-code [meta] template = "c-code" -commit-id = "66322213" +commit-id = "fe63cb4c" [python] with-appveyor = true @@ -44,6 +44,7 @@ additional-rules = [ "include *.sh", "recursive-include docs *.bat", "recursive-include docs *.css", + "recursive-include include/persistent *.h", "recursive-include src *.c", "recursive-include src *.h", ] diff --git a/CHANGES.rst b/CHANGES.rst index 5fb0e49..5206903 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,9 @@ 5.1 (unreleased) ================ -- Add preliminary support for Python 3.12a6. +- Drop using ``setup_requires`` due to constant problems on GHA. + +- Add preliminary support for Python 3.12a7. 5.0 (2023-02-10) diff --git a/MANIFEST.in b/MANIFEST.in index dd97482..84f75a2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -17,5 +17,6 @@ recursive-include src *.py include *.sh recursive-include docs *.bat recursive-include docs *.css +recursive-include include/persistent *.h recursive-include src *.c recursive-include src *.h diff --git a/include/persistent/persistent/_compat.h b/include/persistent/persistent/_compat.h new file mode 100644 index 0000000..4bedaeb --- /dev/null +++ b/include/persistent/persistent/_compat.h @@ -0,0 +1,42 @@ +/***************************************************************************** + + Copyright (c) 2012 Zope Foundation and Contributors. + All Rights Reserved. + + This software is subject to the provisions of the Zope Public License, + Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. + THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED + WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS + FOR A PARTICULAR PURPOSE + + ****************************************************************************/ + +#ifndef PERSISTENT__COMPAT_H +#define PERSISTENT__COMPAT_H + +#include "Python.h" + +#define INTERN PyUnicode_InternFromString +#define INTERN_INPLACE PyUnicode_InternInPlace +#define NATIVE_CHECK_EXACT PyUnicode_CheckExact +#define NATIVE_FROM_STRING_AND_SIZE PyUnicode_FromStringAndSize + +#define Py_TPFLAGS_HAVE_RICHCOMPARE 0 + +#define INT_FROM_LONG(x) PyLong_FromLong(x) +#define INT_CHECK(x) PyLong_Check(x) +#define INT_AS_LONG(x) PyLong_AsLong(x) +#define CAPI_CAPSULE_NAME "persistent.cPersistence.CAPI" + +#else +#define INTERN PyString_InternFromString +#define INTERN_INPLACE PyString_InternInPlace +#define NATIVE_CHECK_EXACT PyString_CheckExact +#define NATIVE_FROM_STRING_AND_SIZE PyString_FromStringAndSize + +#define INT_FROM_LONG(x) PyInt_FromLong(x) +#define INT_CHECK(x) PyInt_Check(x) +#define INT_AS_LONG(x) PyInt_AS_LONG(x) + +#endif diff --git a/include/persistent/persistent/cPersistence.h b/include/persistent/persistent/cPersistence.h new file mode 100644 index 0000000..e8c61c6 --- /dev/null +++ b/include/persistent/persistent/cPersistence.h @@ -0,0 +1,156 @@ +/***************************************************************************** + + Copyright (c) 2001, 2002 Zope Foundation and Contributors. + All Rights Reserved. + + This software is subject to the provisions of the Zope Public License, + Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. + THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED + WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS + FOR A PARTICULAR PURPOSE + + ****************************************************************************/ + +#ifndef CPERSISTENCE_H +#define CPERSISTENCE_H + +#include "_compat.h" +#include "bytesobject.h" + +#include "ring.h" + +#define CACHE_HEAD \ + PyObject_HEAD \ + CPersistentRing ring_home; \ + int non_ghost_count; \ + Py_ssize_t total_estimated_size; + +struct ccobject_head_struct; + +typedef struct ccobject_head_struct PerCache; + +/* How big is a persistent object? + + 12 PyGC_Head is two pointers and an int + 8 PyObject_HEAD is an int and a pointer + + 12 jar, oid, cache pointers + 8 ring struct + 8 serialno + 4 state + extra + 4 size info + + (56) so far + + 4 dict ptr + 4 weaklist ptr + ------------------------- + 68 only need 62, but obmalloc rounds up to multiple of eight + + Even a ghost requires 64 bytes. It's possible to make a persistent + instance with slots and no dict, which changes the storage needed. + +*/ + +#define cPersistent_HEAD \ + PyObject_HEAD \ + PyObject *jar; \ + PyObject *oid; \ + PerCache *cache; \ + CPersistentRing ring; \ + char serial[8]; \ + signed state:8; \ + unsigned estimated_size:24; + +/* We recently added estimated_size. We originally added it as a new + unsigned long field after a signed char state field and a + 3-character reserved field. This didn't work because there + are packages in the wild that have their own copies of cPersistence.h + that didn't see the update. + + To get around this, we used the reserved space by making + estimated_size a 24-bit bit field in the space occupied by the old + 3-character reserved field. To fit in 24 bits, we made the units + of estimated_size 64-character blocks. This allows is to handle up + to a GB. We should never see that, but to be paranoid, we also + truncate sizes greater than 1GB. We also set the minimum size to + 64 bytes. + + We use the _estimated_size_in_24_bits and _estimated_size_in_bytes + macros both to avoid repetition and to make intent a little clearer. +*/ +#define _estimated_size_in_24_bits(I) ((I) > 1073741696 ? 16777215 : (I)/64+1) +#define _estimated_size_in_bytes(I) ((I)*64) + +#define cPersistent_GHOST_STATE -1 +#define cPersistent_UPTODATE_STATE 0 +#define cPersistent_CHANGED_STATE 1 +#define cPersistent_STICKY_STATE 2 + +typedef struct { + cPersistent_HEAD +} cPersistentObject; + +typedef void (*percachedelfunc)(PerCache *, PyObject *); + +typedef struct { + PyTypeObject *pertype; + getattrofunc getattro; + setattrofunc setattro; + int (*changed)(cPersistentObject*); + void (*accessed)(cPersistentObject*); + void (*ghostify)(cPersistentObject*); + int (*setstate)(PyObject*); + percachedelfunc percachedel; + int (*readCurrent)(cPersistentObject*); +} cPersistenceCAPIstruct; + +#define cPersistenceType cPersistenceCAPI->pertype + +#ifndef DONT_USE_CPERSISTENCECAPI +static cPersistenceCAPIstruct *cPersistenceCAPI; +#endif + +#define cPersistanceModuleName "cPersistence" + +#define PER_TypeCheck(O) PyObject_TypeCheck((O), cPersistenceCAPI->pertype) + +#define PER_USE_OR_RETURN(O,R) {if((O)->state==cPersistent_GHOST_STATE && cPersistenceCAPI->setstate((PyObject*)(O)) < 0) return (R); else if ((O)->state==cPersistent_UPTODATE_STATE) (O)->state=cPersistent_STICKY_STATE;} + +#define PER_CHANGED(O) (cPersistenceCAPI->changed((cPersistentObject*)(O))) + +#define PER_READCURRENT(O, E) \ + if (cPersistenceCAPI->readCurrent((cPersistentObject*)(O)) < 0) { E; } + +#define PER_GHOSTIFY(O) (cPersistenceCAPI->ghostify((cPersistentObject*)(O))) + +/* If the object is sticky, make it non-sticky, so that it can be ghostified. + The value is not meaningful + */ +#define PER_ALLOW_DEACTIVATION(O) ((O)->state==cPersistent_STICKY_STATE && ((O)->state=cPersistent_UPTODATE_STATE)) + +#define PER_PREVENT_DEACTIVATION(O) ((O)->state==cPersistent_UPTODATE_STATE && ((O)->state=cPersistent_STICKY_STATE)) + +/* + Make a persistent object usable from C by: + + - Making sure it is not a ghost + + - Making it sticky. + + IMPORTANT: If you call this and don't call PER_ALLOW_DEACTIVATION, + your object will not be ghostified. + + PER_USE returns a 1 on success and 0 failure, where failure means + error. + */ +#define PER_USE(O) \ +(((O)->state != cPersistent_GHOST_STATE \ + || (cPersistenceCAPI->setstate((PyObject*)(O)) >= 0)) \ + ? (((O)->state==cPersistent_UPTODATE_STATE) \ + ? ((O)->state=cPersistent_STICKY_STATE) : 1) : 0) + +#define PER_ACCESSED(O) (cPersistenceCAPI->accessed((cPersistentObject*)(O))) + +#endif diff --git a/include/persistent/persistent/ring.h b/include/persistent/persistent/ring.h new file mode 100644 index 0000000..1bc7635 --- /dev/null +++ b/include/persistent/persistent/ring.h @@ -0,0 +1,67 @@ +/***************************************************************************** + + Copyright (c) 2003 Zope Foundation and Contributors. + All Rights Reserved. + + This software is subject to the provisions of the Zope Public License, + Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. + THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED + WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS + FOR A PARTICULAR PURPOSE + + ****************************************************************************/ + +/* Support routines for the doubly-linked list of cached objects. + +The cache stores a headed, doubly-linked, circular list of persistent +objects, with space for the pointers allocated in the objects themselves. +The cache stores the distinguished head of the list, which is not a valid +persistent object. The other list members are non-ghost persistent +objects, linked in LRU (least-recently used) order. + +The r_next pointers traverse the ring starting with the least recently used +object. The r_prev pointers traverse the ring starting with the most +recently used object. + +Obscure: While each object is pointed at twice by list pointers (once by +its predecessor's r_next, again by its successor's r_prev), the refcount +on the object is bumped only by 1. This leads to some possibly surprising +sequences of incref and decref code. Note that since the refcount is +bumped at least once, the list does hold a strong reference to each +object in it. +*/ + +typedef struct CPersistentRing_struct +{ + struct CPersistentRing_struct *r_prev; + struct CPersistentRing_struct *r_next; +} CPersistentRing; + + +/* The list operations here take constant time independent of the + * number of objects in the list: + */ + +/* Add elt as the most recently used object. elt must not already be + * in the list, although this isn't checked. + */ +void ring_add(CPersistentRing *ring, CPersistentRing *elt); + +/* Remove elt from the list. elt must already be in the list, although + * this isn't checked. + */ +void ring_del(CPersistentRing *elt); + +/* elt must already be in the list, although this isn't checked. It's + * unlinked from its current position, and relinked into the list as the + * most recently used object (which is arguably the tail of the list + * instead of the head -- but the name of this function could be argued + * either way). This is equivalent to + * + * ring_del(elt); + * ring_add(ring, elt); + * + * but may be a little quicker. + */ +void ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt); diff --git a/setup.py b/setup.py index 86ced6c..750a037 100644 --- a/setup.py +++ b/setup.py @@ -65,31 +65,6 @@ def _unavailable(self, e): # pip will then go ahead and run 'setup.py install' directly. raise -# Include directories for C extensions -# Sniff the location of the headers in 'persistent' or fall back -# to local headers in the include sub-directory - - -class ModuleHeaderDir: - - def __init__(self, require_spec, where='..'): - # By default, assume top-level pkg has the same name as the dist. - # Also assume that headers are located in the package dir, and - # are meant to be included as follows: - # #include "module/header_name.h" - self._require_spec = require_spec - self._where = where - - def __str__(self): - from pkg_resources import require - from pkg_resources import resource_filename - require(self._require_spec) - path = resource_filename(self._require_spec, self._where) - return os.path.abspath(path) - - -include = [ModuleHeaderDir('persistent')] - # Set up dependencies for the BTrees package base_btrees_depends = [ "src/BTrees/BTreeItemsTemplate.c", @@ -151,7 +126,7 @@ def BTreeExtension(family): value = family[1] name = "BTrees._%sBTree" % family sources = ["src/BTrees/_%sBTree.c" % family] - kwargs = {"include_dirs": include} + kwargs = {"include_dirs": [os.path.join('include', 'persistent')]} if family != "fs": kwargs["depends"] = (base_btrees_depends + [KEY_H % FLAVORS[key], VALUE_H % FLAVORS[value]]) @@ -217,7 +192,6 @@ def BTreeExtension(family): include_package_data=True, zip_safe=False, ext_modules=ext_modules, - setup_requires=['persistent'], extras_require={ 'test': TESTS_REQUIRE, 'ZODB': [