DB.py 26.6 KB
Newer Older
Jim Fulton's avatar
Jim Fulton committed
1
##############################################################################
2
#
3 4
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
5
#
matt@zope.com's avatar
matt@zope.com committed
6 7 8 9 10 11
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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
12
#
Jim Fulton's avatar
Jim Fulton committed
13 14 15
##############################################################################
"""Database objects

16
$Id$"""
Jim Fulton's avatar
Jim Fulton committed
17

Jeremy Hylton's avatar
Jeremy Hylton committed
18
import cPickle, cStringIO, sys
19
from thread import allocate_lock
20
from time import time, ctime
Jeremy Hylton's avatar
Jeremy Hylton committed
21
import warnings
22
import logging
Jim Fulton's avatar
Jim Fulton committed
23

Jeremy Hylton's avatar
Jeremy Hylton committed
24 25 26
from ZODB.broken import find_global
from ZODB.Connection import Connection
from ZODB.serialize import referencesf
27

28 29
import transaction

30
logger = logging.getLogger('ZODB.DB')
31

Jeremy Hylton's avatar
Jeremy Hylton committed
32
class DB(object):
Jim Fulton's avatar
Jim Fulton committed
33
    """The Object Database
34
    -------------------
Jim Fulton's avatar
Jim Fulton committed
35

36 37 38
    The DB class coordinates the activities of multiple database
    Connection instances.  Most of the work is done by the
    Connections created via the open method.
Jeremy Hylton's avatar
Jeremy Hylton committed
39

40 41
    The DB instance manages a pool of connections.  If a connection is
    closed, it is returned to the pool and its object cache is
Jeremy Hylton's avatar
Jeremy Hylton committed
42 43 44 45 46
    preserved.  A subsequent call to open() will reuse the connection.
    There is a limit to the pool size; if all its connections are in
    use, calls to open() will block until one of the open connections
    is closed.

47 48 49 50 51
    The class variable 'klass' is used by open() to create database
    connections.  It is set to Connection, but a subclass could override
    it to provide a different connection implementation.

    The database provides a few methods intended for application code
52 53
    -- open, close, undo, and pack -- and a large collection of
    methods for inspecting the database and its connections' caches.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

    :Cvariables:
      - `klass`: Class used by L{open} to create database connections

    :Groups:
      - `User Methods`: __init__, open, close, undo, pack, classFactory
      - `Inspection Methods`: getName, getSize, objectCount,
        getActivityMonitor, setActivityMonitor
      - `Connection Pool Methods`: getPoolSize, getVersionPoolSize,
        removeVersionPool, setPoolSize, setVersionPoolSize
      - `Transaction Methods`: invalidate
      - `Other Methods`: lastTransaction, connectionDebugInfo
      - `Version Methods`: modifiedInVersion, abortVersion, commitVersion,
        versionEmpty
      - `Cache Inspection Methods`: cacheDetail, cacheExtremeDetail,
        cacheFullSweep, cacheLastGCTime, cacheMinimize, cacheMeanAge,
        cacheMeanDeac, cacheMeanDeal, cacheSize, cacheDetailSize,
        getCacheSize, getVersionCacheSize, setCacheSize, setVersionCacheSize,
        cacheStatistics
      - `Deprecated Methods`: getCacheDeactivateAfter,
        setCacheDeactivateAfter,
        getVersionCacheDeactivateAfter, setVersionCacheDeactivateAfter
Jim Fulton's avatar
Jim Fulton committed
76
    """
77

78 79
    klass = Connection  # Class to use for connections
    _activity_monitor = None
Jim Fulton's avatar
Jim Fulton committed
80 81 82 83

    def __init__(self, storage,
                 pool_size=7,
                 cache_size=400,
Jeremy Hylton's avatar
Jeremy Hylton committed
84
                 cache_deactivate_after=None,
Jim Fulton's avatar
Jim Fulton committed
85 86
                 version_pool_size=3,
                 version_cache_size=100,
87
                 version_cache_deactivate_after=None,
Jim Fulton's avatar
Jim Fulton committed
88 89 90
                 ):
        """Create an object database.

91 92 93 94 95 96 97
        :Parameters:
          - `storage`: the storage used by the database, e.g. FileStorage
          - `pool_size`: maximum number of open connections
          - `cache_size`: target size of Connection object cache
          - `cache_deactivate_after`: ignored
          - `version_pool_size`: maximum number of connections (per version)
          - `version_cache_size`: target size of Connection object cache for
98
            version connections
99
          - `version_cache_deactivate_after`: ignored
Jim Fulton's avatar
Jim Fulton committed
100 101 102 103 104 105
        """
        # Allocate locks:
        l=allocate_lock()
        self._a=l.acquire
        self._r=l.release

106
        # Setup connection pools and cache info
Jeremy Hylton's avatar
Jeremy Hylton committed
107 108 109 110 111 112 113 114 115 116 117 118
        self._pools = {},[]
        self._temps = []
        self._pool_size = pool_size
        self._cache_size = cache_size
        self._version_pool_size = version_pool_size
        self._version_cache_size = version_cache_size

        # warn about use of deprecated arguments
        if (cache_deactivate_after is not None or
            version_cache_deactivate_after is not None):
            warnings.warn("cache_deactivate_after has no effect",
                          DeprecationWarning)
Jim Fulton's avatar
Jim Fulton committed
119

120
        self._miv_cache = {}
121

122 123 124 125
        # Setup storage
        self._storage=storage
        storage.registerDB(self, None)
        if not hasattr(storage,'tpc_vote'): storage.tpc_vote=lambda *args: None
126 127 128 129
        try:
            storage.load('\0\0\0\0\0\0\0\0','')
        except KeyError:
            # Create the database's root in the storage if it doesn't exist
130 131
            from persistent.mapping import PersistentMapping
            root = PersistentMapping()
132 133 134 135 136 137
            # Manually create a pickle for the root to put in the storage.
            # The pickle must be in the special ZODB format.
            file = cStringIO.StringIO()
            p = cPickle.Pickler(file, 1)
            p.dump((root.__class__, None))
            p.dump(root.__getstate__())
138
            t = transaction.Transaction()
139
            t.description = 'initial database creation'
140 141 142 143 144
            storage.tpc_begin(t)
            storage.store('\0\0\0\0\0\0\0\0', None, file.getvalue(), '', t)
            storage.tpc_vote(t)
            storage.tpc_finish(t)

Jim Fulton's avatar
Jim Fulton committed
145
        # Pass through methods:
146 147
        for m in ['history', 'supportsUndo', 'supportsVersions', 'undoLog',
                  'versionEmpty', 'versions']:
Jim Fulton's avatar
Jim Fulton committed
148
            setattr(self, m, getattr(storage, m))
Jim Fulton's avatar
Jim Fulton committed
149 150

        if hasattr(storage, 'undoInfo'):
151
            self.undoInfo = storage.undoInfo
152

Jim Fulton's avatar
Jim Fulton committed
153 154

    def _cacheMean(self, attr):
155
        # XXX this method doesn't work
Jim Fulton's avatar
Jim Fulton committed
156
        m=[0,0]
157
        def f(con, m=m, attr=attr):
158
            t=getattr(con._cache, attr)
Jim Fulton's avatar
Jim Fulton committed
159 160 161 162 163 164 165
            m[0]=m[0]+t
            m[1]=m[1]+1

        self._connectionMap(f)
        if m[1]: m=m[0]/m[1]
        else: m=None
        return m
166

Jim Fulton's avatar
Jim Fulton committed
167
    def _closeConnection(self, connection):
168 169 170 171 172
        """Return a connection to the pool.

        connection._db must be self on entry.
        """

Jim Fulton's avatar
Jim Fulton committed
173 174
        self._a()
        try:
175 176 177
            assert connection._db is self
            connection._db = None

178 179 180
            am = self._activity_monitor
            if am is not None:
                am.closedConnection(connection)
181 182
            version = connection._version
            pools, pooll = self._pools
183 184 185 186 187 188
            try:
                pool, allocated, pool_lock = pools[version]
            except KeyError:
                # No such version. We must have deleted the pool.
                # Just let the connection go.

189 190
                # We need to break circular refs to make it really go.
                # XXX What objects are involved in the cycle?
191
                connection.__dict__.clear()
192

193
                return
194

Jim Fulton's avatar
Jim Fulton committed
195
            pool.append(connection)
196
            if len(pool) == 1:
Jim Fulton's avatar
Jim Fulton committed
197 198
                # Pool now usable again, unlock it.
                pool_lock.release()
199 200
        finally:
            self._r()
201

202
    def _connectionMap(self, f):
Jim Fulton's avatar
Jim Fulton committed
203 204 205 206 207 208 209 210 211
        self._a()
        try:
            pools,pooll=self._pools
            for pool, allocated in pooll:
                for cc in allocated: f(cc)

            temps=self._temps
            if temps:
                t=[]
Jim Fulton's avatar
Jim Fulton committed
212
                rc=sys.getrefcount
Jim Fulton's avatar
Jim Fulton committed
213 214 215 216 217
                for cc in temps:
                    if rc(cc) > 3: f(cc)
                self._temps=t
        finally: self._r()

218 219 220 221
    def abortVersion(self, version, txn=None):
        if txn is None:
            txn = transaction.get()
        txn.register(AbortVersion(self, version))
Jim Fulton's avatar
Jim Fulton committed
222 223 224 225

    def cacheDetail(self):
        """Return information on objects in the various caches

226 227
        Organized by class.
        """
Jim Fulton's avatar
Jim Fulton committed
228

229 230
        detail = {}
        def f(con, detail=detail, have_detail=detail.has_key):
Jim Fulton's avatar
Jim Fulton committed
231
            for oid, ob in con._cache.items():
232 233
                module = getattr(ob.__class__, '__module__', '')
                module = module and '%s.' % module or ''
234 235 236 237 238
                c = "%s%s" % (module, ob.__class__.__name__)
                if have_detail(c):
                    detail[c] = detail[c] + 1
                else:
                    detail[c] = 1
239

Jim Fulton's avatar
Jim Fulton committed
240
        self._connectionMap(f)
241
        detail = detail.items()
Jim Fulton's avatar
Jim Fulton committed
242 243 244 245
        detail.sort()
        return detail

    def cacheExtremeDetail(self):
246
        detail = []
247 248
        conn_no = [0]  # A mutable reference to a counter
        def f(con, detail=detail, rc=sys.getrefcount, conn_no=conn_no):
249
            conn_no[0] += 1
250
            cn = conn_no[0]
251
            for oid, ob in con._cache_items():
252 253 254
                id = ''
                if hasattr(ob, '__dict__'):
                    d = ob.__dict__
Jim Fulton's avatar
Jim Fulton committed
255
                    if d.has_key('id'):
256
                        id = d['id']
Jim Fulton's avatar
Jim Fulton committed
257
                    elif d.has_key('__name__'):
258
                        id = d['__name__']
259 260

                module = getattr(ob.__class__, '__module__', '')
261 262 263 264 265 266 267 268 269 270 271 272
                module = module and ('%s.' % module) or ''

                # What refcount ('rc') should we return?  The intent is
                # that we return the true Python refcount, but as if the
                # cache didn't exist.  This routine adds 3 to the true
                # refcount:  1 for binding to name 'ob', another because
                # ob lives in the con._cache_items() list we're iterating
                # over, and calling sys.getrefcount(ob) boosts ob's
                # count by 1 too.  So the true refcount is 3 less than
                # sys.getrefcount(ob) returns.  But, in addition to that,
                # the cache holds an extra reference on non-ghost objects,
                # and we also want to pretend that doesn't exist.
Jim Fulton's avatar
Jim Fulton committed
273
                detail.append({
274 275 276
                    'conn_no': cn,
                    'oid': oid,
                    'id': id,
277
                    'klass': "%s%s" % (module, ob.__class__.__name__),
278
                    'rc': rc(ob) - 3 - (ob._p_changed is not None),
279 280
                    'state': ob._p_changed,
                    #'references': con.references(oid),
Jim Fulton's avatar
Jim Fulton committed
281 282 283 284 285
                    })

        self._connectionMap(f)
        return detail

286 287
    def cacheFullSweep(self):
        self._connectionMap(lambda c: c._cache.full_sweep())
Jim Fulton's avatar
Jim Fulton committed
288 289 290 291 292 293 294 295 296 297

    def cacheLastGCTime(self):
        m=[0]
        def f(con, m=m):
            t=con._cache.cache_last_gc_time
            if t > m[0]: m[0]=t

        self._connectionMap(f)
        return m[0]

298 299
    def cacheMinimize(self):
        self._connectionMap(lambda c: c._cache.minimize())
Jim Fulton's avatar
Jim Fulton committed
300 301 302 303 304 305 306 307

    def cacheMeanAge(self): return self._cacheMean('cache_mean_age')
    def cacheMeanDeac(self): return self._cacheMean('cache_mean_deac')
    def cacheMeanDeal(self): return self._cacheMean('cache_mean_deal')

    def cacheSize(self):
        m=[0]
        def f(con, m=m):
308
            m[0] = m[0] + con._cache.cache_non_ghost_count
Jim Fulton's avatar
Jim Fulton committed
309 310 311 312

        self._connectionMap(f)
        return m[0]

313 314 315 316 317 318 319 320 321 322
    def cacheDetailSize(self):
        m=[]
        def f(con, m=m):
            m.append({'connection':repr(con),
                      'ngsize':con._cache.cache_non_ghost_count,
                      'size':len(con._cache)})
        self._connectionMap(f)
        m.sort()
        return m

Jeremy Hylton's avatar
Jeremy Hylton committed
323
    def close(self):
Jeremy Hylton's avatar
Jeremy Hylton committed
324 325 326 327 328 329 330 331 332 333 334 335
        """Close the database and its underlying storage.

        It is important to close the database, because the storage may
        flush in-memory data structures to disk when it is closed.
        Leaving the storage open with the process exits can cause the
        next open to be slow.

        What effect does closing the database have on existing
        connections?  Technically, they remain open, but their storage
        is closed, so they stop behaving usefully.  Perhaps close()
        should also close all the Connections.
        """
Jeremy Hylton's avatar
Jeremy Hylton committed
336
        self._storage.close()
Jim Fulton's avatar
Jim Fulton committed
337

338 339 340 341
    def commitVersion(self, source, destination='', txn=None):
        if txn is None:
            txn = transaction.get()
        txn.register(CommitVersion(self, source, destination))
Jim Fulton's avatar
Jim Fulton committed
342

343 344
    def getCacheSize(self):
        return self._cache_size
Jim Fulton's avatar
Jim Fulton committed
345

346 347 348
    def lastTransaction(self):
        return self._storage.lastTransaction()

Jim Fulton's avatar
Jim Fulton committed
349 350
    def getName(self): return self._storage.getName()

351 352
    def getPoolSize(self): return self._pool_size

Jim Fulton's avatar
Jim Fulton committed
353 354
    def getSize(self): return self._storage.getSize()

355 356
    def getVersionCacheSize(self):
        return self._version_cache_size
357

358 359
    def getVersionPoolSize(self):
        return self._version_pool_size
360

361
    def invalidate(self, tid, oids, connection=None, version=''):
Jim Fulton's avatar
Jim Fulton committed
362 363 364 365 366 367 368
        """Invalidate references to a given oid.

        This is used to indicate that one of the connections has committed a
        change to the object.  The connection commiting the change should be
        passed in to prevent useless (but harmless) messages to the
        connection.
        """
369 370 371
        if connection is not None:
            version=connection._version
        # Update modified in version cache
372
        # XXX must make this work with list or dict to backport to 2.6
Jeremy Hylton's avatar
Jeremy Hylton committed
373
        for oid in oids.keys():
374 375 376
            h=hash(oid)%131
            o=self._miv_cache.get(h, None)
            if o is not None and o[0]==oid: del self._miv_cache[h]
377 378 379 380 381 382

        # Notify connections
        for pool, allocated in self._pools[1]:
            for cc in allocated:
                if (cc is not connection and
                    (not version or cc._version==version)):
383
                    if sys.getrefcount(cc) <= 3:
384
                        cc.close()
385
                    cc.invalidate(tid, oids)
386

387
        if self._temps:
388
            t=[]
389 390
            for cc in self._temps:
                if sys.getrefcount(cc) > 3:
Jim Fulton's avatar
Jim Fulton committed
391
                    if (cc is not connection and
392 393
                        (not version or cc._version == version)):
                        cc.invalidate(tid, oids)
394
                    t.append(cc)
395 396 397
                else:
                    cc.close()
            self._temps = t
Jim Fulton's avatar
Jim Fulton committed
398

399 400 401 402 403 404 405 406 407 408
    def modifiedInVersion(self, oid):
        h=hash(oid)%131
        cache=self._miv_cache
        o=cache.get(h, None)
        if o and o[0]==oid:
            return o[1]
        v=self._storage.modifiedInVersion(oid)
        cache[h]=oid, v
        return v

409 410
    def objectCount(self):
        return len(self._storage)
411

Jim Fulton's avatar
Jim Fulton committed
412
    def open(self, version='', transaction=None, temporary=0, force=None,
413
             waitflag=1, mvcc=True, txn_mgr=None, synch=True):
414
        """Return a database Connection for use by application code.
Jim Fulton's avatar
Jim Fulton committed
415 416 417 418 419 420 421 422 423

        The optional version argument can be used to specify that a
        version connection is desired.

        The optional transaction argument can be provided to cause the
        connection to be automatically closed when a transaction is
        terminated.  In addition, connections per transaction are
        reused, if possible.

424 425 426
        Note that the connection pool is managed as a stack, to
        increate the likelihood that the connection's stack will
        include useful objects.
427 428 429 430 431 432 433 434 435 436 437 438 439

        :Parameters:
          - `version`: the "version" that all changes will be made
             in, defaults to no version.
          - `transaction`: XXX
          - `temporary`: XXX
          - `force`: XXX
          - `waitflag`: XXX
          - `mvcc`: boolean indicating whether MVCC is enabled
          - `txn_mgr`: transaction manager to use.  None means
             used the default transaction manager.
          - `synch`: boolean indicating whether Connection should
             register for afterCompletion() calls.
Tim Peters's avatar
Tim Peters committed
440

Jim Fulton's avatar
Jim Fulton committed
441 442 443 444 445
        """
        self._a()
        try:

            if transaction is not None:
446
                connections = transaction._connections
Jim Fulton's avatar
Jim Fulton committed
447
                if connections:
448
                    if connections.has_key(version) and not temporary:
Jim Fulton's avatar
Jim Fulton committed
449 450
                        return connections[version]
                else:
451 452
                    transaction._connections = connections = {}
                transaction = transaction._connections
Jim Fulton's avatar
Jim Fulton committed
453 454 455 456 457

            if temporary:
                # This is a temporary connection.
                # We won't bother with the pools.  This will be
                # a one-use connection.
458 459
                c = self.klass(version=version,
                               cache_size=self._version_cache_size,
460
                               mvcc=mvcc, txn_mgr=txn_mgr, synch=synch)
Jim Fulton's avatar
Jim Fulton committed
461 462
                c._setDB(self)
                self._temps.append(c)
463 464
                if transaction is not None:
                    transaction[id(c)] = c
Jim Fulton's avatar
Jim Fulton committed
465 466 467
                return c


468
            pools, pooll = self._pools
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487

            # pools is a mapping object:
            #
            #   {version -> (pool, allocated, lock)
            #
            # where:
            #
            #   pool is the connection pool for the version,
            #   allocated is a list of all of the allocated
            #     connections, and
            #   lock is a lock that is used to block when a pool is
            #     empty and no more connections can be allocated.
            #
            # pooll is a list of all of the pools and allocated for
            # use in cases where we need to iterate over all
            # connections or all inactive connections.

            # Pool locks are tricky.  Basically, the lock needs to be
            # set whenever the pool becomes empty so that threads are
488
            # forced to wait until the pool gets a connection in it.
489 490 491 492 493
            # The lock is acquired when the (empty) pool is
            # created. The The lock is acquired just prior to removing
            # the last connection from the pool and just after adding
            # a connection to an empty pool.

494

Jim Fulton's avatar
Jim Fulton committed
495 496 497 498 499 500 501 502 503 504
            if pools.has_key(version):
                pool, allocated, pool_lock = pools[version]
            else:
                pool, allocated, pool_lock = pools[version] = (
                    [], [], allocate_lock())
                pooll.append((pool, allocated))
                pool_lock.acquire()


            if not pool:
505
                c = None
Jim Fulton's avatar
Jim Fulton committed
506
                if version:
507
                    if self._version_pool_size > len(allocated) or force:
508 509
                        c = self.klass(version=version,
                                       cache_size=self._version_cache_size,
510
                                       mvcc=mvcc, txn_mgr=txn_mgr)
Jim Fulton's avatar
Jim Fulton committed
511 512 513
                        allocated.append(c)
                        pool.append(c)
                elif self._pool_size > len(allocated) or force:
514 515
                    c = self.klass(version=version,
                                   cache_size=self._cache_size,
516
                                   mvcc=mvcc, txn_mgr=txn_mgr, synch=synch)
Jim Fulton's avatar
Jim Fulton committed
517 518
                    allocated.append(c)
                    pool.append(c)
519

Jim Fulton's avatar
Jim Fulton committed
520 521 522 523 524
                if c is None:
                    if waitflag:
                        self._r()
                        pool_lock.acquire()
                        self._a()
525 526 527 528
                        if len(pool) > 1:
                            # Note that the pool size will normally be 1 here,
                            # but it could be higher due to a race condition.
                            pool_lock.release()
Jim Fulton's avatar
Jim Fulton committed
529 530
                    else: return

531
            elif len(pool) == 1:
Jim Fulton's avatar
Jim Fulton committed
532
                # Taking last one, lock the pool
533 534 535 536 537
                # Note that another thread might grab the lock
                # before us, so we might actually block, however,
                # when we get the lock back, there *will* be a
                # connection in the pool.
                self._r()
Jim Fulton's avatar
Jim Fulton committed
538
                pool_lock.acquire()
539
                self._a()
540 541 542 543
                if len(pool) > 1:
                    # Note that the pool size will normally be 1 here,
                    # but it could be higher due to a race condition.
                    pool_lock.release()
Jim Fulton's avatar
Jim Fulton committed
544

545
            c = pool[-1]
Jim Fulton's avatar
Jim Fulton committed
546
            del pool[-1]
547
            c._setDB(self, mvcc=mvcc, txn_mgr=txn_mgr, synch=synch)
Jim Fulton's avatar
Jim Fulton committed
548 549
            for pool, allocated in pooll:
                for cc in pool:
550
                    cc.cacheGC()
Jim Fulton's avatar
Jim Fulton committed
551

552 553
            if transaction is not None:
                transaction[version] = c
Jim Fulton's avatar
Jim Fulton committed
554 555 556
            return c

        finally: self._r()
557

558 559 560 561 562 563 564
    def removeVersionPool(self, version):
        pools, pooll = self._pools
        info = pools.get(version)
        if info:
            del pools[version]
            pool, allocated, pool_lock = info
            pooll.remove((pool, allocated))
565 566 567 568
            try:
                pool_lock.release()
            except: # XXX Do we actually expect this to fail?
                pass
569 570 571
            del pool[:]
            del allocated[:]

572 573 574
    def connectionDebugInfo(self):
        r=[]
        pools,pooll=self._pools
575
        t=time()
576 577
        for version, (pool, allocated, lock) in pools.items():
            for c in allocated:
578
                o=c._opened
Jim Fulton's avatar
Jim Fulton committed
579 580 581 582 583
                d=c._debug_info
                if d:
                    if len(d)==1: d=d[0]
                else: d=''
                d="%s (%s)" % (d, len(c._cache))
584

585
                r.append({
586
                    'opened': o and ("%s (%.2fs)" % (ctime(o), t-o)),
Jim Fulton's avatar
Jim Fulton committed
587
                    'info': d,
588 589 590
                    'version': version,
                    })
        return r
591

592 593
    def getActivityMonitor(self):
        return self._activity_monitor
594

595
    def pack(self, t=None, days=0):
Jeremy Hylton's avatar
Jeremy Hylton committed
596 597 598 599 600 601 602 603 604
        """Pack the storage, deleting unused object revisions.

        A pack is always performed relative to a particular time, by
        default the current time.  All object revisions that are not
        reachable as of the pack time are deleted from the storage.

        The cost of this operation varies by storage, but it is
        usually an expensive operation.

605 606 607 608
        There are two optional arguments that can be used to set the
        pack time: t, pack time in seconds since the epcoh, and days,
        the number of days to substract from t or from the current
        time if t is not specified.
Jeremy Hylton's avatar
Jeremy Hylton committed
609
        """
610 611 612 613 614
        if t is None:
            t = time()
        t -= days * 86400
        try:
            self._storage.pack(t, referencesf)
Jim Fulton's avatar
Jim Fulton committed
615
        except:
616
            logger.error("packing", exc_info=True)
Jim Fulton's avatar
Jim Fulton committed
617
            raise
618

619
    def setCacheSize(self, v):
620 621 622 623 624 625
        self._cache_size = v
        d = self._pools[0]
        pool_info = d.get('')
        if pool_info is not None:
            for c in pool_info[1]:
                c._cache.cache_size = v
626

627
    def classFactory(self, connection, modulename, globalname):
628
        # Zope will rebind this method to arbitrary user code at runtime.
629
        return find_global(modulename, globalname)
630

631 632
    def setPoolSize(self, v):
        self._pool_size=v
633 634 635 636

    def setActivityMonitor(self, am):
        self._activity_monitor = am

637 638 639
    def setVersionCacheSize(self, v):
        self._version_cache_size=v
        for ver in self._pools[0].keys():
640
            if ver:
641 642
                for c in self._pools[0][ver][1]:
                    c._cache.cache_size=v
643

Jim Fulton's avatar
Jim Fulton committed
644
    def setVersionPoolSize(self, v): self._version_pool_size=v
Jim Fulton's avatar
Jim Fulton committed
645 646

    def cacheStatistics(self): return () # :(
Jim Fulton's avatar
Jim Fulton committed
647

648
    def undo(self, id, txn=None):
649
        """Undo a transaction identified by id.
Jeremy Hylton's avatar
Jeremy Hylton committed
650 651 652 653 654 655

        A transaction can be undone if all of the objects involved in
        the transaction were not modified subsequently, if any
        modifications can be resolved by conflict resolution, or if
        subsequent changes resulted in the same object state.

656 657 658
        The value of id should be generated by calling undoLog()
        or undoInfo().  The value of id is not the same as a
        transaction id used by other methods; it is unique to undo().
Jeremy Hylton's avatar
Jeremy Hylton committed
659

660 661
        :Parameters:
          - `id`: a storage-specific transaction identifier
662
          - `txn`: transaction context to use for undo().
663
            By default, uses the current transaction.
Jeremy Hylton's avatar
Jeremy Hylton committed
664
        """
665 666 667
        if txn is None:
            txn = transaction.get()
        txn.register(TransactionalUndo(self, id))
668

Jim Fulton's avatar
Jim Fulton committed
669 670 671
    def versionEmpty(self, version):
        return self._storage.versionEmpty(version)

Jeremy Hylton's avatar
Jeremy Hylton committed
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
    # The following methods are deprecated and have no effect

    def getCacheDeactivateAfter(self):
        """Deprecated"""
        warnings.warn("cache_deactivate_after has no effect",
                      DeprecationWarning)

    def getVersionCacheDeactivateAfter(self):
        """Deprecated"""
        warnings.warn("cache_deactivate_after has no effect",
                      DeprecationWarning)

    def setCacheDeactivateAfter(self, v):
        """Deprecated"""
        warnings.warn("cache_deactivate_after has no effect",
                      DeprecationWarning)

    def setVersionCacheDeactivateAfter(self, v):
        """Deprecated"""
        warnings.warn("cache_deactivate_after has no effect",
                      DeprecationWarning)

694 695
class ResourceManager(object):
    """Transaction participation for a version or undo resource."""
696

697 698 699 700 701 702
    def __init__(self, db):
        self._db = db
        # Delegate the actual 2PC methods to the storage
        self.tpc_vote = self._db._storage.tpc_vote
        self.tpc_finish = self._db._storage.tpc_finish
        self.tpc_abort = self._db._storage.tpc_abort
703

704
    def sortKey(self):
705 706
        return "%s:%s" % (self._db._storage.sortKey(), id(self))

707 708 709 710 711 712
    def tpc_begin(self, txn, sub=False):
        # XXX we should never be called with sub=True.
        if sub:
            raise ValueError, "doesn't supoprt sub-transactions"
        self._db._storage.tpc_begin(txn)

713 714 715
    # The object registers itself with the txn manager, so the ob
    # argument to the methods below is self.

716
    def abort(self, obj, txn):
717 718
        pass

719
    def commit(self, obj, txn):
720
        pass
721

722
class CommitVersion(ResourceManager):
723

724 725 726 727 728 729
    def __init__(self, db, version, dest=''):
        super(CommitVersion, self).__init__(db)
        self._version = version
        self._dest = dest

    def commit(self, ob, t):
730
        dest=self._dest
731 732
        tid, oids = self._db._storage.commitVersion(self._version, self._dest,
                                                    t)
Jeremy Hylton's avatar
Jeremy Hylton committed
733
        oids = dict.fromkeys(oids, 1)
734 735
        self._db.invalidate(tid, oids, version=self._dest)
        if self._dest:
736 737
            # the code above just invalidated the dest version.
            # now we need to invalidate the source!
738
            self._db.invalidate(tid, oids, version=self._version)
739

740
class AbortVersion(ResourceManager):
741

742 743 744
    def __init__(self, db, version):
        super(AbortVersion, self).__init__(db)
        self._version = version
745

746 747
    def commit(self, ob, t):
        tid, oids = self._db._storage.abortVersion(self._version, t)
Jeremy Hylton's avatar
Jeremy Hylton committed
748
        self._db.invalidate(tid, dict.fromkeys(oids, 1), version=self._version)
749

750
class TransactionalUndo(ResourceManager):
751

752 753 754
    def __init__(self, db, tid):
        super(TransactionalUndo, self).__init__(db)
        self._tid = tid
755

756 757
    def commit(self, ob, t):
        tid, oids = self._db._storage.undo(self._tid, t)
Jeremy Hylton's avatar
Jeremy Hylton committed
758
        self._db.invalidate(tid, dict.fromkeys(oids, 1))