~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/registry/model/distributionsourcepackage.py

Merged replication into pending-db-changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
 
15
15
import itertools
16
16
import operator
 
17
from threading import local
17
18
 
 
19
from bzrlib.lru_cache import LRUCache
18
20
from lazr.restful.utils import smartquote
19
21
from sqlobject.sqlbuilder import SQLConstant
20
22
from storm.expr import (
32
34
    Storm,
33
35
    Unicode,
34
36
    )
 
37
import transaction
35
38
from zope.interface import implements
36
39
 
37
40
from canonical.database.sqlbase import sqlvalues
542
545
 
543
546
    @classmethod
544
547
    def _get(cls, distribution, sourcepackagename):
545
 
        return Store.of(distribution).find(
546
 
            DistributionSourcePackageInDatabase,
547
 
            DistributionSourcePackageInDatabase.sourcepackagename ==
548
 
                sourcepackagename,
549
 
            DistributionSourcePackageInDatabase.distribution ==
550
 
                distribution).one()
 
548
        return DistributionSourcePackageInDatabase.get(
 
549
            distribution, sourcepackagename)
551
550
 
552
551
    @classmethod
553
552
    def _new(cls, distribution, sourcepackagename,
554
553
             is_upstream_link_allowed=False):
555
 
        dsp = DistributionSourcePackageInDatabase()
556
 
        dsp.distribution = distribution
557
 
        dsp.sourcepackagename = sourcepackagename
558
 
        dsp.is_upstream_link_allowed = is_upstream_link_allowed
559
 
        Store.of(distribution).add(dsp)
560
 
        Store.of(distribution).flush()
561
 
        return dsp
 
554
        return DistributionSourcePackageInDatabase.new(
 
555
            distribution, sourcepackagename, is_upstream_link_allowed)
562
556
 
563
557
    @classmethod
564
558
    def ensure(cls, spph=None, sourcepackage=None):
591
585
            cls._new(distribution, sourcepackagename, upstream_link_allowed)
592
586
 
593
587
 
 
588
class ThreadLocalLRUCache(LRUCache, local):
 
589
    """A per-thread LRU cache that can synchronize with a transaction."""
 
590
 
 
591
    implements(transaction.interfaces.ISynchronizer)
 
592
 
 
593
    def newTransaction(self, txn):
 
594
        pass
 
595
 
 
596
    def beforeCompletion(self, txn):
 
597
        pass
 
598
 
 
599
    def afterCompletion(self, txn):
 
600
        # Clear the cache when a transaction is committed or aborted.
 
601
        self.clear()
 
602
 
 
603
 
594
604
class DistributionSourcePackageInDatabase(Storm):
595
605
    """Temporary class to allow access to the database."""
596
606
 
627
637
        releases = self.distribution.getCurrentSourceReleases(
628
638
            [self.sourcepackagename])
629
639
        return releases.get(self)
 
640
 
 
641
    # This is a per-thread LRU cache of mappings from (distribution_id,
 
642
    # sourcepackagename_id)) to dsp_id. See get() for how this cache helps to
 
643
    # avoid database hits without causing consistency issues.
 
644
    _cache = ThreadLocalLRUCache(1000, 700)
 
645
    # Synchronize the mapping cache with transactions. The mapping is not
 
646
    # especially useful after a tranaction completes because Storm invalidates
 
647
    # its caches, and leaving the mapping cache in place causes difficult to
 
648
    # understand test interactions.
 
649
    transaction.manager.registerSynch(_cache)
 
650
 
 
651
    @classmethod
 
652
    def get(cls, distribution, sourcepackagename):
 
653
        """Get a DSP given distribution and source package name.
 
654
 
 
655
        Attempts to use a cached `(distro_id, spn_id) --> dsp_id` mapping to
 
656
        avoid hitting the database.
 
657
        """
 
658
        # Check for a cached mapping from (distro_id, spn_id) to dsp_id.
 
659
        dsp_cache_key = distribution.id, sourcepackagename.id
 
660
        dsp_id = cls._cache.get(dsp_cache_key)
 
661
        # If not, fetch from the database.
 
662
        if dsp_id is None:
 
663
            return cls.getDirect(distribution, sourcepackagename)
 
664
        # Try store.get(), allowing Storm to answer from cache if it can.
 
665
        store = Store.of(distribution)
 
666
        dsp = store.get(DistributionSourcePackageInDatabase, dsp_id)
 
667
        # If it's not found, query the database; the mapping might be stale.
 
668
        if dsp is None:
 
669
            return cls.getDirect(distribution, sourcepackagename)
 
670
        # Check that the mapping in the cache was correct.
 
671
        if distribution.id != dsp.distribution_id:
 
672
            return cls.getDirect(distribution, sourcepackagename)
 
673
        if sourcepackagename.id != dsp.sourcepackagename_id:
 
674
            return cls.getDirect(distribution, sourcepackagename)
 
675
        # Cache hit, phew.
 
676
        return dsp
 
677
 
 
678
    @classmethod
 
679
    def getDirect(cls, distribution, sourcepackagename):
 
680
        """Get a DSP given distribution and source package name.
 
681
 
 
682
        Caches the `(distro_id, spn_id) --> dsp_id` mapping, but does not
 
683
        otherwise use the cache; it always goes to the database.
 
684
        """
 
685
        dsp = Store.of(distribution).find(
 
686
            DistributionSourcePackageInDatabase,
 
687
            DistributionSourcePackageInDatabase.sourcepackagename ==
 
688
                sourcepackagename,
 
689
            DistributionSourcePackageInDatabase.distribution ==
 
690
                distribution).one()
 
691
        dsp_cache_key = distribution.id, sourcepackagename.id
 
692
        if dsp is None:
 
693
            pass  # No way to eject things from the cache!
 
694
        else:
 
695
            cls._cache[dsp_cache_key] = dsp.id
 
696
        return dsp
 
697
 
 
698
    @classmethod
 
699
    def new(cls, distribution, sourcepackagename,
 
700
            is_upstream_link_allowed=False):
 
701
        """Create a new DSP with the given parameters.
 
702
 
 
703
        Caches the `(distro_id, spn_id) --> dsp_id` mapping.
 
704
        """
 
705
        dsp = DistributionSourcePackageInDatabase()
 
706
        dsp.distribution = distribution
 
707
        dsp.sourcepackagename = sourcepackagename
 
708
        dsp.is_upstream_link_allowed = is_upstream_link_allowed
 
709
        Store.of(distribution).add(dsp)
 
710
        Store.of(distribution).flush()
 
711
        dsp_cache_key = distribution.id, sourcepackagename.id
 
712
        cls._cache[dsp_cache_key] = dsp.id
 
713
        return dsp