Merge ~cjwatson/launchpad:update-pkgcache-binary-race into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 37ad5d5f93444577c369ef43bfff4a4b345c0d57
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:update-pkgcache-binary-race
Merge into: launchpad:master
Diff against target: 112 lines (+70/-4)
2 files modified
lib/lp/soyuz/model/distroseriespackagecache.py (+12/-0)
lib/lp/soyuz/scripts/tests/test_update_pkgcache.py (+58/-4)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+444768@code.launchpad.net

Commit message

Fix update-pkgcache crash due to binary deletion

Description of the change

The `update-pkgcache` script started crashing on production about a week ago, and mysteriously started working again today without us changing anything. Based on code inspection, the only way I can see for this particular crash to happen is if a binary was deleted between `DistroSeriesPackageCache.findCurrentBinaryPackageNames` and `DistroSeriesPackageCache._update` (`update-pkgcache` is slow enough that it makes many commits along the way, so this is possible), and simulating that in a test reproduces the same crash.

It seems safe to skip the affected binary in such cases, since `DistroSeriesPackageCache.removeOld` will eventually clean up its cache entries.

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/soyuz/model/distroseriespackagecache.py b/lib/lp/soyuz/model/distroseriespackagecache.py
2index bedc2fd..93d3b3e 100644
3--- a/lib/lp/soyuz/model/distroseriespackagecache.py
4+++ b/lib/lp/soyuz/model/distroseriespackagecache.py
5@@ -77,6 +77,10 @@ class DistroSeriesPackageCache(StormBase):
6 ),
7 )
8 .config(distinct=True)
9+ # Not necessary for correctness, but useful for testability; and
10+ # at the time of writing the sort only adds perhaps 10-20 ms to
11+ # the query time on staging.
12+ .order_by(BinaryPackagePublishingHistory.binarypackagenameID)
13 )
14 return bulk.load(BinaryPackageName, bpn_ids)
15
16@@ -204,6 +208,14 @@ class DistroSeriesPackageCache(StormBase):
17 )
18
19 for bpn in binarypackagenames:
20+ if bpn not in details_map:
21+ log.debug(
22+ "No active publishing details found for %s; perhaps "
23+ "removed in parallel with update-pkgcache? Skipping.",
24+ bpn.name,
25+ )
26+ continue
27+
28 cache = cache_map[bpn]
29 details = details_map[bpn]
30 # make sure the cached name, summary and description are correct
31diff --git a/lib/lp/soyuz/scripts/tests/test_update_pkgcache.py b/lib/lp/soyuz/scripts/tests/test_update_pkgcache.py
32index 875e956..5560b57 100644
33--- a/lib/lp/soyuz/scripts/tests/test_update_pkgcache.py
34+++ b/lib/lp/soyuz/scripts/tests/test_update_pkgcache.py
35@@ -6,8 +6,11 @@
36 import transaction
37
38 from lp.services.log.logger import BufferLogger
39+from lp.soyuz.enums import PackagePublishingStatus
40+from lp.soyuz.model.distroseriespackagecache import DistroSeriesPackageCache
41 from lp.soyuz.scripts.update_pkgcache import PackageCacheUpdater
42 from lp.testing import TestCaseWithFactory
43+from lp.testing.dbuser import dbuser
44 from lp.testing.layers import ZopelessDatabaseLayer
45
46
47@@ -34,10 +37,61 @@ class TestPackageCacheUpdater(TestCaseWithFactory):
48 self.assertEqual(0, archive.sources_cached)
49 self.factory.makeSourcePackagePublishingHistory(archive=archive)
50 script = self.makeScript()
51- script.updateDistributionCache(distribution, archives[0])
52- archives[0].updateArchiveCache()
53+ with dbuser(self.dbuser):
54+ script.updateDistributionCache(distribution, archives[0])
55+ archives[0].updateArchiveCache()
56 self.assertEqual(1, archives[0].sources_cached)
57 archives[1].disable()
58- script.updateDistributionCache(distribution, archives[1])
59- archives[1].updateArchiveCache()
60+ with dbuser(self.dbuser):
61+ script.updateDistributionCache(distribution, archives[1])
62+ archives[1].updateArchiveCache()
63 self.assertEqual(0, archives[1].sources_cached)
64+
65+ def test_binary_deleted_during_run(self):
66+ distroseries = self.factory.makeDistroSeries()
67+ das = self.factory.makeDistroArchSeries(distroseries=distroseries)
68+ archive = self.factory.makeArchive(
69+ distribution=distroseries.distribution
70+ )
71+ bpns = [self.factory.makeBinaryPackageName() for _ in range(4)]
72+ bpphs = [
73+ self.factory.makeBinaryPackagePublishingHistory(
74+ binarypackagename=bpn,
75+ distroarchseries=das,
76+ status=PackagePublishingStatus.PUBLISHED,
77+ archive=archive,
78+ )
79+ for bpn in bpns
80+ ]
81+
82+ class InstrumentedTransaction:
83+ def commit(self):
84+ transaction.commit()
85+ # Change this binary package publishing history's status
86+ # after the first commit, to simulate the situation where a
87+ # BPPH ceases to be active part-way through an
88+ # update-pkgcache run.
89+ if bpphs[2].status == PackagePublishingStatus.PUBLISHED:
90+ with dbuser("launchpad"):
91+ bpphs[2].requestDeletion(archive.owner)
92+
93+ logger = BufferLogger()
94+ with dbuser(self.dbuser):
95+ DistroSeriesPackageCache.updateAll(
96+ distroseries,
97+ archive=archive,
98+ ztm=InstrumentedTransaction(),
99+ log=logger,
100+ commit_chunk=2,
101+ )
102+ archive.updateArchiveCache()
103+ self.assertEqual(4, archive.binaries_cached)
104+ self.assertEqual(
105+ "DEBUG Considering binaries {bpns[0].name}, {bpns[1].name}\n"
106+ "DEBUG Committing\n"
107+ "DEBUG Considering binaries {bpns[2].name}, {bpns[3].name}\n"
108+ "DEBUG No active publishing details found for {bpns[2].name};"
109+ " perhaps removed in parallel with update-pkgcache? Skipping.\n"
110+ "DEBUG Committing\n".format(bpns=bpns),
111+ logger.getLogBuffer(),
112+ )

Subscribers

People subscribed via source and target branches

to status/vote changes: