Merge lp:~rharding/launchpad/bugnom_874250 into lp:launchpad

Proposed by Richard Harding on 2012-05-10
Status: Merged
Approved by: Richard Harding on 2012-05-10
Approved revision: no longer in the source branch.
Merged at revision: 15227
Proposed branch: lp:~rharding/launchpad/bugnom_874250
Merge into: lp:launchpad
Diff against target: 218 lines (+88/-21)
6 files modified
lib/lp/bugs/doc/bugtask.txt (+15/-0)
lib/lp/bugs/interfaces/bug.py (+3/-0)
lib/lp/bugs/interfaces/bugtask.py (+4/-0)
lib/lp/bugs/model/bug.py (+4/-0)
lib/lp/bugs/model/bugnomination.py (+2/-2)
lib/lp/bugs/model/bugtask.py (+60/-19)
To merge this branch: bzr merge lp:~rharding/launchpad/bugnom_874250
Reviewer Review Type Date Requested Status
j.c.sackett (community) 2012-05-10 Approve on 2012-05-10
Review via email: mp+105317@code.launchpad.net

Commit Message

Support a createManyTasks so that we perform a single query when approving nominations.

Description of the Change

= Summary =
This takes another hack at improving the performance of approving the
bug nomination when the bug is listed across many source packages and several
series.

See:
https://lp-oops.canonical.com/oops.py/?oopsid=OOPS-6e6b4b29ef635939095468528e92c7ec

== Pre Implementation ==
Had a chat with Deryck. The goal is to fix the queries doing the 'INSERT INTO
bugtask' by avoiding the loop

== Implementation Notes ==
To do this we add a createManyTasks method and using that when approving bug.

== Q/A ==
We're going to have to try to get a bug nomination such as the bug we're linked against and approve it. It'll still probably oops,but we need to check the oops out and make sure we're only seeing one instance of the 'INSERT INTO bugtask' query that is all over the oops linked in the `Summary`.

== Tests ==

lib/lp/bugs/stories/bugtask-management/xx-change-milestone.txt
lib/lp/bugs/doc/bugtask.txt

== Lint ==
All clean

== LoC Qualification ==
A follow up branch will include porting all of doc/bugtask.txt into unit tests. In order to aid in review, these are done as two branches.

To post a comment you must log in.
j.c.sackett (jcsackett) wrote :

This looks good to me. I have some nitpicks below, but nothing blocking.

> === modified file 'lib/lp/bugs/model/bug.py'
> --- lib/lp/bugs/model/bug.py 2012-05-09 06:12:52 +0000
> +++ lib/lp/bugs/model/bug.py 2012-05-10 14:54:18 +0000
> @@ -1237,6 +1237,11 @@
> """See `IBug`."""
> return getUtility(IBugTaskSet).createTask(self, owner, target)
>
> + def addManyTasks(self, owner, targets):
> + """See `IBug`."""
> + new_tasks = getUtility(IBugTaskSet).createManyTasks(self, owner, targets)
> + return new_tasks
> +

Is there any reason to not just have this be?

    def addManyTasks(self, owner, targets):
        """See `IBug`."""
        return getUtility(IBugTaskSet).createManyTasks(self, owner, targets)

> === modified file 'lib/lp/bugs/model/bugtask.py'
> --- lib/lp/bugs/model/bugtask.py 2012-05-09 13:39:12 +0000
> +++ lib/lp/bugs/model/bugtask.py 2012-05-10 14:54:18 +0000
> @@ -1604,11 +1603,8 @@
> params = BugTaskSearchParams(user, **kwargs)
> return self.search(params)
>
> - def createTask(self, bug, owner, target,
> - status=IBugTask['status'].default,
> - importance=IBugTask['importance'].default,
> - assignee=None, milestone=None):
> - """See `IBugTaskSet`."""
> + def _init_new_task(self, bug, owner, target, status, importance, assignee,
> + milestone):
> if not status:
> status = IBugTask['status'].default
> if not importance:

This is a bit nitpicky, but methods, private or otherwise, should still
be camelCase.

review: Approve
Robert Collins (lifeless) wrote :

> + def addManyTasks(self, owner, targets):
> + """See `IBug`."""
> + new_tasks = getUtility(IBugTaskSet).createManyTasks(self, owner, targets)
> + return new_tasks

That method doesn't seem to have any reason to exist... its
indirection for no benefit. I suggest either using it from IBugTaskSet
or not putting it on IBugTaskSet at all and having the body in IBug.

-Rob

Richard Harding (rharding) wrote :

Robert, I had considered it, but thought it best to mirror the addTask functionality which has the same indirection. I just replaced the self.bug.addTask with self.bug.addManyTask

If I take out the indirection I can change the call to:

getUtility(IBugtaskSet).addManyTasks(self.bug, approver, targets) I suppose, but now the two add method work differently.

Robert Collins (lifeless) wrote :

This is a prime source of the fat that makes LP slow to reason about and hard to learn. I don't think consistency here is useful, because its actively increasing the bloat. Feel free to do whatever feels best, I'd be inclined to not have the method on IBug, I think, which will:
 - avoid the indirection
 - be consistent with where the meat of the code is
 - not make changing things later any harder (or easier).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/doc/bugtask.txt'
2--- lib/lp/bugs/doc/bugtask.txt 2012-05-04 05:48:00 +0000
3+++ lib/lp/bugs/doc/bugtask.txt 2012-05-10 17:21:19 +0000
4@@ -86,6 +86,21 @@
5 >>> distro_series_task.distroseries == warty
6 True
7
8+
9+Next we verify that we can create many tasks at a time for multiple targets.
10+
11+ >>> bug_many = getUtility(IBugSet).get(4)
12+ >>> taskset = bugtaskset.createManyTasks(bug_many, mark,
13+ ... [evolution, a_distro, warty], status=BugTaskStatus.FIXRELEASED)
14+ >>> tasks = [(t.product, t.distribution, t.distroseries) for t in taskset]
15+ >>> tasks.sort()
16+ >>> tasks[0][2] == warty
17+ True
18+ >>> tasks[1][1] == a_distro
19+ True
20+ >>> tasks[2][0] == evolution
21+ True
22+
23 # XXX: Brad Bollenbach 2005-02-24: See the bottom of this file for a chunk of
24 # test documentation that is missing from here, due to problems with resetting
25 # the connection after a ProgrammingError is raised. ARGH.
26
27=== modified file 'lib/lp/bugs/interfaces/bug.py'
28--- lib/lp/bugs/interfaces/bug.py 2012-04-27 05:56:48 +0000
29+++ lib/lp/bugs/interfaces/bug.py 2012-05-10 17:21:19 +0000
30@@ -779,6 +779,9 @@
31 def removeWatch(bug_watch, owner):
32 """Remove a bug watch from the bug."""
33
34+ def addManyTasks(owners, targets):
35+ """Create multiple bug tasks on this bug."""
36+
37 @call_with(owner=REQUEST_USER)
38 @operation_parameters(target=copy_field(IBugTask['target']))
39 @export_factory_operation(IBugTask, [])
40
41=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
42--- lib/lp/bugs/interfaces/bugtask.py 2012-04-17 08:04:20 +0000
43+++ lib/lp/bugs/interfaces/bugtask.py 2012-05-10 17:21:19 +0000
44@@ -1570,6 +1570,10 @@
45 :return: A list of tuples containing (status_id, count).
46 """
47
48+ def createManyTasks(bug, owner, targets, status=None, importance=None,
49+ assignee=None, milestone=None):
50+ """Create a series of bug tasks and return them."""
51+
52 def createTask(bug, owner, target, status=None, importance=None,
53 assignee=None, milestone=None):
54 """Create a bug task on a bug and return it.
55
56=== modified file 'lib/lp/bugs/model/bug.py'
57--- lib/lp/bugs/model/bug.py 2012-05-09 06:12:52 +0000
58+++ lib/lp/bugs/model/bug.py 2012-05-10 17:21:19 +0000
59@@ -1237,6 +1237,10 @@
60 """See `IBug`."""
61 return getUtility(IBugTaskSet).createTask(self, owner, target)
62
63+ def addManyTasks(self, owner, targets):
64+ """See `IBug`."""
65+ return getUtility(IBugTaskSet).createManyTasks(self, owner, targets)
66+
67 def addWatch(self, bugtracker, remotebug, owner):
68 """See `IBug`."""
69 # We shouldn't add duplicate bug watches.
70
71=== modified file 'lib/lp/bugs/model/bugnomination.py'
72--- lib/lp/bugs/model/bugnomination.py 2011-12-30 06:14:56 +0000
73+++ lib/lp/bugs/model/bugnomination.py 2012-05-10 17:21:19 +0000
74@@ -91,8 +91,8 @@
75 targets.append(distroseries)
76 else:
77 targets.append(self.productseries)
78- for target in targets:
79- bug_task = self.bug.addTask(approver, target)
80+ bugtasks = self.bug.addManyTasks(approver, targets)
81+ for bug_task in bugtasks:
82 self.bug.addChange(BugTaskAdded(UTC_NOW, approver, bug_task))
83
84 def decline(self, decliner):
85
86=== modified file 'lib/lp/bugs/model/bugtask.py'
87--- lib/lp/bugs/model/bugtask.py 2012-05-09 13:39:12 +0000
88+++ lib/lp/bugs/model/bugtask.py 2012-05-10 17:21:19 +0000
89@@ -119,7 +119,10 @@
90 from lp.registry.model.pillar import pillar_sort_key
91 from lp.registry.model.sourcepackagename import SourcePackageName
92 from lp.services import features
93-from lp.services.database.bulk import load_related
94+from lp.services.database.bulk import (
95+ create,
96+ load_related,
97+ )
98 from lp.services.database.constants import UTC_NOW
99 from lp.services.database.datetimecol import UtcDateTimeCol
100 from lp.services.database.enumcol import EnumCol
101@@ -150,31 +153,27 @@
102 - distro tasks, followed by their distroseries tasks
103 - ubuntu first among the distros
104 """
105+ product_name = None
106+ productseries_name = None
107+ distribution_name = None
108+ distroseries_name = None
109+ sourcepackage_name = None
110+
111 if bugtask.product:
112 product_name = bugtask.product.name
113- productseries_name = None
114 elif bugtask.productseries:
115 productseries_name = bugtask.productseries.name
116 product_name = bugtask.productseries.product.name
117- else:
118- product_name = None
119- productseries_name = None
120
121 if bugtask.distribution:
122 distribution_name = bugtask.distribution.name
123- else:
124- distribution_name = None
125
126 if bugtask.distroseries:
127 distroseries_name = bugtask.distroseries.version
128 distribution_name = bugtask.distroseries.distribution.name
129- else:
130- distroseries_name = None
131
132 if bugtask.sourcepackagename:
133 sourcepackage_name = bugtask.sourcepackagename.name
134- else:
135- sourcepackage_name = None
136
137 # Move ubuntu to the top.
138 if distribution_name == 'ubuntu':
139@@ -1604,11 +1603,8 @@
140 params = BugTaskSearchParams(user, **kwargs)
141 return self.search(params)
142
143- def createTask(self, bug, owner, target,
144- status=IBugTask['status'].default,
145- importance=IBugTask['importance'].default,
146- assignee=None, milestone=None):
147- """See `IBugTaskSet`."""
148+ def _initNewTask(self, bug, owner, target, status, importance, assignee,
149+ milestone):
150 if not status:
151 status = IBugTask['status'].default
152 if not importance:
153@@ -1640,18 +1636,63 @@
154 milestone=milestone)
155 create_params = non_target_create_params.copy()
156 create_params.update(target_key)
157+ return create_params, non_target_create_params
158+
159+ def createManyTasks(self, bug, owner, targets,
160+ status=IBugTask['status'].default,
161+ importance=IBugTask['importance'].default,
162+ assignee=None, milestone=None):
163+ """See `IBugTaskSet`."""
164+ params = [self._initNewTask(bug, owner, target, status, importance,
165+ assignee, milestone) for target in targets]
166+
167+ fieldnames = (
168+ 'assignee',
169+ 'bug',
170+ 'distribution',
171+ 'distroseries',
172+ 'importance',
173+ 'milestone',
174+ 'owner',
175+ 'product',
176+ 'productseries',
177+ 'sourcepackagename',
178+ '_status',
179+ )
180+
181+ fields = [getattr(BugTask, field) for field in fieldnames]
182+ # Values need to be a list of tuples in the order we're declaring our
183+ # fields.
184+ values = [[p[0].get(field) for field in fieldnames] for p in params]
185+
186+ taskset = create(fields, values, get_objects=True)
187+ del get_property_cache(bug).bugtasks
188+ for bugtask in taskset:
189+ bugtask.updateTargetNameCache()
190+ if bugtask.conjoined_slave:
191+ bugtask._syncFromConjoinedSlave()
192+ return taskset
193+
194+ def createTask(self, bug, owner, target,
195+ status=IBugTask['status'].default,
196+ importance=IBugTask['importance'].default,
197+ assignee=None, milestone=None):
198+ """See `IBugTaskSet`."""
199+ create_params, non_target_create_params = self._initNewTask(bug,
200+ owner, target, status, importance, assignee, milestone)
201 bugtask = BugTask(**create_params)
202- if target_key['distribution']:
203+
204+ if create_params['distribution']:
205 # Create tasks for accepted nominations if this is a source
206 # package addition.
207 accepted_nominations = [
208 nomination for nomination in
209- bug.getNominations(target_key['distribution'])
210+ bug.getNominations(create_params['distribution'])
211 if nomination.isApproved()]
212 for nomination in accepted_nominations:
213 accepted_series_task = BugTask(
214 distroseries=nomination.distroseries,
215- sourcepackagename=target_key['sourcepackagename'],
216+ sourcepackagename=create_params['sourcepackagename'],
217 **non_target_create_params)
218 accepted_series_task.updateTargetNameCache()
219