Merge lp:~cjwatson/launchpad/optimise-merge-detection into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18610
Proposed branch: lp:~cjwatson/launchpad/optimise-merge-detection
Merge into: lp:launchpad
Diff against target: 140 lines (+34/-8)
4 files modified
lib/lp/codehosting/scanner/mergedetection.py (+6/-1)
lib/lp/codehosting/scanner/tests/test_mergedetection.py (+2/-1)
lib/lp/services/tests/test_utils.py (+21/-4)
lib/lp/services/utils.py (+5/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/optimise-merge-detection
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+342975@code.launchpad.net

Commit message

Optimise merge detection when the branch has no landing candidates.

Description of the change

I noticed an OOPS when scanning ~vcs-imports/gcc/git-mirror which appears to be due to a fair amount of non-SQL time somewhere around here; this seems like a likely candidate.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/codehosting/scanner/mergedetection.py'
--- lib/lp/codehosting/scanner/mergedetection.py 2016-04-06 12:14:54 +0000
+++ lib/lp/codehosting/scanner/mergedetection.py 2018-04-10 23:11:40 +0000
@@ -92,6 +92,7 @@
92 #92 #
93 # XXX: JonathanLange 2009-05-11 spec=package-branches: This assumes that93 # XXX: JonathanLange 2009-05-11 spec=package-branches: This assumes that
94 # merge detection only works with product branches.94 # merge detection only works with product branches.
95 logger.info("Looking for merged branches.")
95 branches = getUtility(IAllBranches).inProduct(db_branch.product)96 branches = getUtility(IAllBranches).inProduct(db_branch.product)
96 branches = branches.withLifecycleStatus(97 branches = branches.withLifecycleStatus(
97 BranchLifecycleStatus.DEVELOPMENT,98 BranchLifecycleStatus.DEVELOPMENT,
@@ -151,12 +152,16 @@
151 # which introduced the change, that will either be set through the web152 # which introduced the change, that will either be set through the web
152 # ui by a person, or by PQM once it is integrated.153 # ui by a person, or by PQM once it is integrated.
153154
155 logger.info("Looking for merged proposals.")
154 if scan_completed.bzr_branch is None:156 if scan_completed.bzr_branch is None:
155 # Only happens in tests.157 # Only happens in tests.
156 merge_sorted = []158 merge_sorted = []
157 else:159 else:
160 # This is potentially slow for deep histories; we defer even
161 # initialising it until we need it, and we cache the iterator's
162 # results.
158 merge_sorted = CachingIterator(163 merge_sorted = CachingIterator(
159 scan_completed.bzr_branch.iter_merge_sorted_revisions())164 scan_completed.bzr_branch.iter_merge_sorted_revisions)
160 for proposal in db_branch.landing_candidates:165 for proposal in db_branch.landing_candidates:
161 tip_rev_id = proposal.source_branch.last_scanned_id166 tip_rev_id = proposal.source_branch.last_scanned_id
162 if tip_rev_id in new_ancestry:167 if tip_rev_id in new_ancestry:
163168
=== modified file 'lib/lp/codehosting/scanner/tests/test_mergedetection.py'
--- lib/lp/codehosting/scanner/tests/test_mergedetection.py 2015-10-05 17:03:27 +0000
+++ lib/lp/codehosting/scanner/tests/test_mergedetection.py 2018-04-10 23:11:40 +0000
@@ -32,6 +32,7 @@
32 )32 )
33from lp.services.config import config33from lp.services.config import config
34from lp.services.database.interfaces import IStore34from lp.services.database.interfaces import IStore
35from lp.services.log.logger import DevNullLogger
35from lp.services.osutils import override_environ36from lp.services.osutils import override_environ
36from lp.testing import (37from lp.testing import (
37 TestCase,38 TestCase,
@@ -199,7 +200,7 @@
199 mergedetection.auto_merge_branches(200 mergedetection.auto_merge_branches(
200 events.ScanCompleted(201 events.ScanCompleted(
201 db_branch=db_branch, bzr_branch=None,202 db_branch=db_branch, bzr_branch=None,
202 logger=None, new_ancestry=new_ancestry))203 logger=DevNullLogger(), new_ancestry=new_ancestry))
203204
204 def mergeDetected(self, logger, source, target):205 def mergeDetected(self, logger, source, target):
205 # Record the merged branches206 # Record the merged branches
206207
=== modified file 'lib/lp/services/tests/test_utils.py'
--- lib/lp/services/tests/test_utils.py 2018-03-23 12:26:35 +0000
+++ lib/lp/services/tests/test_utils.py 2018-04-10 23:11:40 +0000
@@ -7,6 +7,7 @@
77
8from contextlib import contextmanager8from contextlib import contextmanager
9from datetime import datetime9from datetime import datetime
10from functools import partial
10import hashlib11import hashlib
11import itertools12import itertools
12import os13import os
@@ -145,7 +146,7 @@
145146
146 def test_reuse(self):147 def test_reuse(self):
147 # The same iterator can be used multiple times.148 # The same iterator can be used multiple times.
148 iterator = CachingIterator(itertools.count())149 iterator = CachingIterator(itertools.count)
149 self.assertEqual(150 self.assertEqual(
150 [0, 1, 2, 3, 4], list(itertools.islice(iterator, 0, 5)))151 [0, 1, 2, 3, 4], list(itertools.islice(iterator, 0, 5)))
151 self.assertEqual(152 self.assertEqual(
@@ -154,7 +155,7 @@
154 def test_more_values(self):155 def test_more_values(self):
155 # If a subsequent call to iter causes more values to be fetched, they156 # If a subsequent call to iter causes more values to be fetched, they
156 # are also cached.157 # are also cached.
157 iterator = CachingIterator(itertools.count())158 iterator = CachingIterator(itertools.count)
158 self.assertEqual(159 self.assertEqual(
159 [0, 1, 2], list(itertools.islice(iterator, 0, 3)))160 [0, 1, 2], list(itertools.islice(iterator, 0, 3)))
160 self.assertEqual(161 self.assertEqual(
@@ -162,14 +163,14 @@
162163
163 def test_limited_iterator(self):164 def test_limited_iterator(self):
164 # Make sure that StopIteration is handled correctly.165 # Make sure that StopIteration is handled correctly.
165 iterator = CachingIterator(iter([0, 1, 2, 3, 4]))166 iterator = CachingIterator(partial(iter, [0, 1, 2, 3, 4]))
166 self.assertEqual(167 self.assertEqual(
167 [0, 1, 2], list(itertools.islice(iterator, 0, 3)))168 [0, 1, 2], list(itertools.islice(iterator, 0, 3)))
168 self.assertEqual([0, 1, 2, 3, 4], list(iterator))169 self.assertEqual([0, 1, 2, 3, 4], list(iterator))
169170
170 def test_parallel_iteration(self):171 def test_parallel_iteration(self):
171 # There can be parallel iterators over the CachingIterator.172 # There can be parallel iterators over the CachingIterator.
172 ci = CachingIterator(iter([0, 1, 2, 3, 4]))173 ci = CachingIterator(partial(iter, [0, 1, 2, 3, 4]))
173 i1 = iter(ci)174 i1 = iter(ci)
174 i2 = iter(ci)175 i2 = iter(ci)
175 self.assertEqual(0, i1.next())176 self.assertEqual(0, i1.next())
@@ -177,6 +178,22 @@
177 self.assertEqual([1, 2, 3, 4], list(i2))178 self.assertEqual([1, 2, 3, 4], list(i2))
178 self.assertEqual([1, 2, 3, 4], list(i1))179 self.assertEqual([1, 2, 3, 4], list(i1))
179180
181 def test_deferred_initialisation(self):
182 # Initialising the iterator may be expensive, so CachingIterator
183 # defers this until it needs it.
184 self.initialised = False
185
186 def iterator():
187 self.initialised = True
188 return iter([0, 1, 2])
189
190 ci = CachingIterator(iterator)
191 self.assertFalse(self.initialised)
192 self.assertEqual([0, 1, 2], list(ci))
193 self.assertTrue(self.initialised)
194 self.assertEqual([0, 1, 2], list(ci))
195 self.assertTrue(self.initialised)
196
180197
181class TestDecorateWith(TestCase):198class TestDecorateWith(TestCase):
182 """Tests for `decorate_with`."""199 """Tests for `decorate_with`."""
183200
=== modified file 'lib/lp/services/utils.py'
--- lib/lp/services/utils.py 2018-03-23 12:26:35 +0000
+++ lib/lp/services/utils.py 2018-04-10 23:11:40 +0000
@@ -209,10 +209,13 @@
209 iterator if necessary.209 iterator if necessary.
210 """210 """
211211
212 def __init__(self, iterator):212 def __init__(self, iterator_factory):
213 self.iterator = iterator213 self.iterator_factory = iterator_factory
214 self.iterator = None
214215
215 def __iter__(self):216 def __iter__(self):
217 if self.iterator is None:
218 self.iterator = self.iterator_factory()
216 # Teeing an iterator previously returned by tee won't cause heat219 # Teeing an iterator previously returned by tee won't cause heat
217 # death. See tee_copy in itertoolsmodule.c in the Python source.220 # death. See tee_copy in itertoolsmodule.c in the Python source.
218 self.iterator, iterator = tee(self.iterator)221 self.iterator, iterator = tee(self.iterator)