[Zope-Checkins] CVS: Zope3/lib/python/Persistence - Cache.py:1.1.2.1 ICache.py:1.1.4.1

Jim Fulton jim@zope.com
Sun, 25 Nov 2001 14:49:30 -0500


Update of /cvs-repository/Zope3/lib/python/Persistence
In directory cvs.zope.org:/tmp/cvs-serv21091

Added Files:
      Tag: Zope-3x-branch
	Cache.py ICache.py 
Log Message:
Initial new cache implementation with tests.


=== Added File Zope3/lib/python/Persistence/Cache.py ===
##############################################################################
# Copyright (c) 2001 Zope Corporation and Contributors.  All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 1.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.
##############################################################################

from time import time
from sys import getrefcount
from weakref import ref

class Cache(object):

    __iter=None

    def __init__(self, size=500, inactive=300):
        self.__ghosts={}
        self.__gget=self.__ghosts.get
        self.__active={}
        self.__aget=self.__active.get
        self.__size=size
        self.__inactive=300

    def __getitem__(self, oid):
        o = self.__gget(oid, self)
        if o is self:
            o = self.__active[oid]
        o=o()
        if o is None:
            raise KeyError, oid
        else:
            return o

    def get(self, oid, default=None):
        o = self.__gget(oid, self)
        if o is self:
            o = self.__active.get(oid, self)
            if o is self: return default
        o=o()
        if o is None:
            return default
        else:
            return o

    def __setitem__(self, oid, object):
        if object._p_changed is None:
            # ghost
            self.__ghosts[oid] = ref(object, _dictdel(oid, self.__ghosts))
        else:
            self.__active[oid] = ref(object, _dictdel(oid, self.__active))

    def __len__(self):
        return len(self.__ghosts)+len(self.__active)

    def setstate(self, oid, object):
        try: del self.__ghosts[oid]
        except KeyError: pass
        self.__active[oid] = ref(object, _dictdel(oid, self.__active))

    def incrgc(self, multiple=1):
        na=len(self.__active)
        if na < 1: return

        # how many objects do we scan?
        n=min(multiple * max((na-self.__size)/10, 3), na)
        
        # how long can objects be inactive?
        inactive = self.__inactive * (
            0.2 + 0.1 * (min(100, 8 * self.__size/na))
            )

        active=self.__active
        aget=active.get
        ghosts=self.__ghosts
        doomed=[]

        now=int(time()%86400)

        i=self.__iter
        if i is None:
            i=iter(self.__active)

        while n:
            n-=1
            try: oid = i.next()
            except StopIteration:
                del self.__iter
                return

            ob=aget(oid, self)
            if ob is self: continue
            ob=ob()
            state = ob._p_changed
            
            if state==0 and abs(ob._p_atime-now) > inactive:
                doomed.append(oid)
                continue
            if state is None:
                doomed.append(oid)

        for oid in doomed:
            ob=aget(oid, self)
            if ob is self: continue
            ob=ob()
            ob._p_deactivate()
            state = ob._p_changed
            if state is None:
                del active[oid]
                ghosts[oid] = ref(ob, _dictdel(oid, ghosts))

    def full_sweep(self):
        now=int(time()%86400)
        active=self.__active
        ghosts=self.__ghosts
        na=len(active)
        
        # how long can objects be inactive?
        inactive = self.__inactive * (
            0.2 + 0.1 * (min(100, 8 * self.__size/na))
            )

        doomed=[]

        for oid in active:
            ob=active[oid]
            ob=ob()
            state = ob._p_changed
            if state==0 and abs(ob._p_atime-now) > inactive:
                doomed.append(oid)
                continue
            if state is None:
                doomed.append(oid)

        for oid in doomed:
            ob._p_deactivate()
            state = ob._p_changed
            if state is None:
                del active[oid]
                ghosts[oid] = ref(ob, _dictdel(oid, ghosts))

    def minimize(self):
        active=self.__active
        aget=active.get
        ghosts=self.__ghosts

        # Grump: I cant use an iterator because the size will change
        # during iteration. :(
        for oid in active.keys():
            ob=aget(oid, self)
            if ob is self: continue
            ob=ob()
            ob._p_deactivate()
            if ob._p_changed is None:
                del active[oid]
                ghosts[oid] = ref(ob, _dictdel(oid, ghosts))
        self.__iter=None

    def invalidate(self, oids):
        if oids is None: oids=iter(self.__active)
        aget=self.__aget
        active=self.__active
        ghosts=self.__ghosts
        for oid in oids:
            ob=aget(oid, self)
            if ob is self: continue
            ob=ob()
            del ob._p_changed
            del active[oid]
            ghosts[oid] = ref(ob, _dictdel(oid, ghosts))

    def statistics(self):
        return {
            'ghosts': len(self.__ghosts),
            'active': len(self.__active),
            }
        
class _dictdel(object):

    __slots__='oid', 'dict'

    def __init__(self, oid, dict): self.oid, self.dict = oid, dict
    def __call__(self, *args):
        del self.dict[self.oid]


=== Added File Zope3/lib/python/Persistence/ICache.py ===
##############################################################################
# 
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
# 
# Copyright (c) Digital Creations.  All rights reserved.
# 
# This license has been certified as Open Source(tm).
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
# 1. Redistributions in source code must retain the above copyright
#    notice, this list of conditions, and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions, and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 
# 3. Digital Creations requests that attribution be given to Zope
#    in any manner possible. Zope includes a "Powered by Zope"
#    button that is installed by default. While it is not a license
#    violation to remove this button, it is requested that the
#    attribution remain. A significant investment has been put
#    into Zope, and this effort will continue if the Zope community
#    continues to grow. This is one way to assure that growth.
# 
# 4. All advertising materials and documentation mentioning
#    features derived from or use of this software must display
#    the following acknowledgement:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    In the event that the product being advertised includes an
#    intact Zope distribution (with copyright and license included)
#    then this clause is waived.
# 
# 5. Names associated with Zope or Digital Creations must not be used to
#    endorse or promote products derived from this software without
#    prior written permission from Digital Creations.
# 
# 6. Modified redistributions of any form whatsoever must retain
#    the following acknowledgment:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    Intact (re-)distributions of any official Zope release do not
#    require an external acknowledgement.
# 
# 7. Modifications are encouraged but must be packaged separately as
#    patches to official Zope releases.  Distributions that do not
#    clearly separate the patches from the original work must be clearly
#    labeled as unofficial distributions.  Modifications which do not
#    carry the name Zope may be packaged in any form, as long as they
#    conform to all of the clauses above.
# 
# 
# Disclaimer
# 
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#   SUCH DAMAGE.
# 
# 
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations.  Specific
# attributions are listed in the accompanying credits file.
# 
##############################################################################

# Hack to overcome absense of Interface package
try:
    from Interface import Interface
except ImportError:
    class Interface: pass

class ICache(Interface):
    """In-memory object cache

    Cache objects are used by data managers to implement in-memory
    object caches with automatic object deactivation and removal of
    unreferenced objects.

    Cache objects depend heavily on the Persistent object C API.
    """

    def __getitem__(key):
        """Get a cached object
        """

    def __setitem__(key, value):
        """Add an object to the cache
        """

    def __len__():
        """Return the number of objects in the cache
        """

    def get(oid, default=None):
        """Get a cached object
        """

    def incrgc(multiple=1):
        """Perform incremental garbage collection

        An positive integer argument can be provided to speify a
        number of incremental steps to take.
        """

    def full_sweep():
        """Perform a full sweep of the cache
        """

    def minimize():
        """Remove as many objects as possible from the cache
        """

    def invalidate(oids):
        """Invalidate the objects for the given sequence of object ids

        If oids is None, all of the objets in the cache are
        invalidated.
        """

class ICachePolicy(Interface):

    def maximum_quiet(cache_size):
        """Return a number of seconds

        Objects that haven't been accessed in the last number seconds
        should be deactivated.
        """

    def incremental_check_count(cache_size):
        """Return the number of objects that should be checked in an
        incremental garbage collection.
        """