Merge lp:~rbalint/update-manager/fix-stale-pkg-refs into lp:update-manager

Proposed by Balint Reczey
Status: Rejected
Rejected by: Balint Reczey
Proposed branch: lp:~rbalint/update-manager/fix-stale-pkg-refs
Merge into: lp:update-manager
Diff against target: 336 lines (+76/-48)
4 files modified
UpdateManager/Core/UpdateList.py (+37/-21)
UpdateManager/UpdatesAvailable.py (+17/-17)
debian/changelog (+11/-0)
tests/test_update_list.py (+11/-10)
To merge this branch: bzr merge lp:~rbalint/update-manager/fix-stale-pkg-refs
Reviewer Review Type Date Requested Status
Brian Murray Approve
Julian Andres Klode Pending
Ubuntu Core Development Team Pending
Review via email: mp+347195@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Balint Reczey (rbalint) wrote :

I'm also checking rdeps if they need update to adapt to this change.

Revision history for this message
Balint Reczey (rbalint) wrote :

I considered replacing pkg with a proxy object, but the code would have been more confusing imo.

Revision history for this message
Brian Murray (brian-murray) :
review: Approve
Revision history for this message
Julian Andres Klode (juliank) wrote :

So, does this work reliably if a package disappears between stages? I'm not sure it can even haooen.

Revision history for this message
Balint Reczey (rbalint) wrote :

It crashes with exception when a package is going away like before but this is very rare and is not a regression. We can fix that later.

Revision history for this message
Balint Reczey (rbalint) wrote :

Julian provided and alternative fix that updates referenced pkg objects upon cache.open() which is fairly fast and would fix the issue in a different way.

Unmerged revisions

2824. By Balint Reczey

Update changelog

2823. By Balint Reczey

Store package name and cache reference in UpdateItem to avoid invalid package references

LP: #1773316

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'UpdateManager/Core/UpdateList.py'
--- UpdateManager/Core/UpdateList.py 2018-03-21 17:41:10 +0000
+++ UpdateManager/Core/UpdateList.py 2018-05-31 12:03:14 +0000
@@ -43,29 +43,42 @@
4343
4444
45class UpdateItem():45class UpdateItem():
46 def __init__(self, pkg, name, icon, to_remove):46 def __init__(self, pkg, cache, name, icon, to_remove):
47 self.icon = icon47 self.icon = icon
48 self.name = name48 self.name = name
49 self.pkg = pkg49 self.cache = cache
50 if pkg is not None:
51 self.pkg_name = pkg.name
52 else:
53 self.pkg_name = None
50 self.to_remove = to_remove54 self.to_remove = to_remove
5155
52 def is_selected(self):56 def is_selected(self):
53 if not self.to_remove:57 if not self.to_remove:
54 return self.pkg.marked_install or self.pkg.marked_upgrade58 return (self.get_pkg().marked_install or
59 self.get_pkg().marked_upgrade)
55 else:60 else:
56 return self.pkg.marked_delete61 return self.get_pkg().marked_delete
62
63 def get_pkg(self):
64 if self.cache is None:
65 return None
66 try:
67 return self.cache[self.pkg_name]
68 except KeyError:
69 return None
5770
5871
59class UpdateGroup(UpdateItem):72class UpdateGroup(UpdateItem):
60 _depcache = {}73 _depcache = {}
6174
62 def __init__(self, pkg, name, icon, to_remove):75 def __init__(self, pkg, cache, name, icon, to_remove):
63 UpdateItem.__init__(self, pkg, name, icon, to_remove)76 UpdateItem.__init__(self, pkg, cache, name, icon, to_remove)
64 self._items = set()77 self._items = set()
65 self._deps = set()78 self._deps = set()
66 self.core_item = None79 self.core_item = None
67 if pkg is not None:80 if pkg is not None:
68 self.core_item = UpdateItem(pkg, name, icon, to_remove)81 self.core_item = UpdateItem(pkg, cache, name, icon, to_remove)
69 self._items.add(self.core_item)82 self._items.add(self.core_item)
7083
71 @property84 @property
@@ -77,7 +90,7 @@
77 def add(self, pkg, cache=None, eventloop_callback=None, to_remove=False):90 def add(self, pkg, cache=None, eventloop_callback=None, to_remove=False):
78 name = utils.get_package_label(pkg)91 name = utils.get_package_label(pkg)
79 icon = Gio.ThemedIcon.new("package")92 icon = Gio.ThemedIcon.new("package")
80 self._items.add(UpdateItem(pkg, name, icon, to_remove))93 self._items.add(UpdateItem(pkg, cache, name, icon, to_remove))
81 # If the pkg is in self._deps, stop here. We have already calculated94 # If the pkg is in self._deps, stop here. We have already calculated
82 # the recursive dependencies for this package, no need to do it again.95 # the recursive dependencies for this package, no need to do it again.
83 if cache and pkg.name in cache and pkg.name not in self._deps:96 if cache and pkg.name in cache and pkg.name not in self._deps:
@@ -93,8 +106,8 @@
93106
94 def _init_deps(self, cache, eventloop_callback):107 def _init_deps(self, cache, eventloop_callback):
95 for item in self._items:108 for item in self._items:
96 if item.pkg and item.pkg.name not in self._deps:109 if item.pkg_name is not None and item.pkg_name not in self._deps:
97 self._add_deps(item.pkg, cache, eventloop_callback)110 self._add_deps(item.get_pkg(), cache, eventloop_callback)
98111
99 def _add_deps(self, pkg, cache, eventloop_callback):112 def _add_deps(self, pkg, cache, eventloop_callback):
100 """Adds pkg and dependencies of pkg to the dependency list."""113 """Adds pkg and dependencies of pkg to the dependency list."""
@@ -148,23 +161,24 @@
148 return 0161 return 0
149 size = 0162 size = 0
150 for item in self.items:163 for item in self.items:
151 size += getattr(item.pkg.candidate, "size", 0)164 size += getattr(item.get_pkg().candidate, "size", 0)
152 return size165 return size
153166
154167
155class UpdateApplicationGroup(UpdateGroup):168class UpdateApplicationGroup(UpdateGroup):
156 def __init__(self, pkg, application, to_remove):169 def __init__(self, pkg, cache, application, to_remove):
157 name = application.get_display_name()170 name = application.get_display_name()
158 icon = application.get_icon()171 icon = application.get_icon()
159 super(UpdateApplicationGroup, self).__init__(pkg, name, icon,172 super(UpdateApplicationGroup, self).__init__(pkg, cache, name, icon,
160 to_remove)173 to_remove)
161174
162175
163class UpdatePackageGroup(UpdateGroup):176class UpdatePackageGroup(UpdateGroup):
164 def __init__(self, pkg, to_remove):177 def __init__(self, pkg, cache, to_remove):
165 name = utils.get_package_label(pkg)178 name = utils.get_package_label(pkg)
166 icon = Gio.ThemedIcon.new("package")179 icon = Gio.ThemedIcon.new("package")
167 super(UpdatePackageGroup, self).__init__(pkg, name, icon, to_remove)180 super(UpdatePackageGroup, self).__init__(pkg, cache, name, icon,
181 to_remove)
168182
169183
170class UpdateSystemGroup(UpdateGroup):184class UpdateSystemGroup(UpdateGroup):
@@ -173,7 +187,8 @@
173 # the core components and packages.187 # the core components and packages.
174 name = _("%s base") % utils.get_ubuntu_flavor_name(cache=cache)188 name = _("%s base") % utils.get_ubuntu_flavor_name(cache=cache)
175 icon = Gio.ThemedIcon.new("distributor-logo")189 icon = Gio.ThemedIcon.new("distributor-logo")
176 super(UpdateSystemGroup, self).__init__(None, name, icon, to_remove)190 super(UpdateSystemGroup, self).__init__(None, None, name, icon,
191 to_remove)
177192
178193
179class UpdateOrigin():194class UpdateOrigin():
@@ -429,7 +444,7 @@
429 for pkg in pkgs:444 for pkg in pkgs:
430 app = self._get_application_for_package(pkg)445 app = self._get_application_for_package(pkg)
431 if app is not None:446 if app is not None:
432 app_group = UpdateApplicationGroup(pkg, app, to_remove)447 app_group = UpdateApplicationGroup(pkg, cache, app, to_remove)
433 app_groups.append(app_group)448 app_groups.append(app_group)
434 else:449 else:
435 ungrouped_pkgs.append(pkg)450 ungrouped_pkgs.append(pkg)
@@ -450,20 +465,21 @@
450 if ungrouped_pkgs:465 if ungrouped_pkgs:
451 # Separate out system base packages. If we have already found an466 # Separate out system base packages. If we have already found an
452 # application for all updates, don't bother.467 # application for all updates, don't bother.
453 meta_group = UpdateGroup(None, None, None, to_remove)468 meta_group = UpdateGroup(None, None, None, None, to_remove)
454 flavor_package = utils.get_ubuntu_flavor_package(cache=cache)469 flavor_package = utils.get_ubuntu_flavor_package(cache=cache)
455 meta_pkgs = [flavor_package, "ubuntu-standard", "ubuntu-minimal"]470 meta_pkgs = [flavor_package, "ubuntu-standard", "ubuntu-minimal"]
456 meta_pkgs.extend(self._get_linux_packages())471 meta_pkgs.extend(self._get_linux_packages())
457 for pkg in meta_pkgs:472 for pkg in meta_pkgs:
458 if pkg in cache:473 if pkg in cache:
459 meta_group.add(cache[pkg])474 meta_group.add(cache[pkg], cache)
460 for pkg in ungrouped_pkgs:475 for pkg in ungrouped_pkgs:
461 if meta_group.is_dependency(pkg, cache, eventloop_callback):476 if meta_group.is_dependency(pkg, cache, eventloop_callback):
462 if system_group is None:477 if system_group is None:
463 system_group = UpdateSystemGroup(cache, to_remove)478 system_group = UpdateSystemGroup(cache, to_remove)
464 system_group.add(pkg)479 system_group.add(pkg, cache)
465 else:480 else:
466 pkg_groups.append(UpdatePackageGroup(pkg, to_remove))481 pkg_groups.append(UpdatePackageGroup(pkg, cache,
482 to_remove))
467483
468 app_groups.sort(key=lambda a: a.name.lower())484 app_groups.sort(key=lambda a: a.name.lower())
469 pkg_groups.sort(key=lambda a: a.name.lower())485 pkg_groups.sort(key=lambda a: a.name.lower())
470486
=== modified file 'UpdateManager/UpdatesAvailable.py'
--- UpdateManager/UpdatesAvailable.py 2018-03-18 21:43:58 +0000
+++ UpdateManager/UpdatesAvailable.py 2018-05-31 12:03:14 +0000
@@ -407,14 +407,13 @@
407 path = model.get_path(iter)407 path = model.get_path(iter)
408408
409 requires_restart = False409 requires_restart = False
410 if data.item and data.item.pkg:410 if data.item:
411 requires_restart = self.pkg_requires_restart(data.item.pkg)411 requires_restart = self.pkg_requires_restart(data.item.get_pkg())
412 elif data.group:412 elif data.group:
413 if not self.treeview_update.row_expanded(path):413 if not self.treeview_update.row_expanded(path):
414 # A package in the group requires restart414 # A package in the group requires restart
415 for group_item in data.group.items:415 for group_item in data.group.items:
416 if group_item.pkg and self.pkg_requires_restart(416 if self.pkg_requires_restart(group_item.get_pkg()):
417 group_item.pkg):
418 requires_restart = True417 requires_restart = True
419 break418 break
420419
@@ -431,7 +430,7 @@
431 activatable = False430 activatable = False
432 inconsistent = False431 inconsistent = False
433 if data.item:432 if data.item:
434 activatable = data.item.pkg.name not in self.list.held_back433 activatable = data.item.pkg_name not in self.list.held_back
435 inconsistent = False434 inconsistent = False
436 elif data.group:435 elif data.group:
437 activatable = True436 activatable = True
@@ -534,16 +533,16 @@
534 if (item is None and data.group is not None and533 if (item is None and data.group is not None and
535 data.group.core_item is not None):534 data.group.core_item is not None):
536 item = data.group.core_item535 item = data.group.core_item
537 if (item is None or item.pkg is None or536 pkg = None if item is None else item.get_pkg()
538 item.pkg.candidate is None or537 if pkg is None or pkg.candidate is None or \
539 item.pkg.candidate.description is None):538 pkg.candidate.description is None:
540 changes_buffer = self.textview_changes.get_buffer()539 changes_buffer = self.textview_changes.get_buffer()
541 changes_buffer.set_text("")540 changes_buffer.set_text("")
542 desc_buffer = self.textview_descr.get_buffer()541 desc_buffer = self.textview_descr.get_buffer()
543 desc_buffer.set_text("")542 desc_buffer.set_text("")
544 self.notebook_details.set_sensitive(False)543 self.notebook_details.set_sensitive(False)
545 return544 return
546 long_desc = item.pkg.candidate.description545 long_desc = pkg.candidate.description
547 self.notebook_details.set_sensitive(True)546 self.notebook_details.set_sensitive(True)
548 # do some regular expression magic on the description547 # do some regular expression magic on the description
549 # Add a newline before each bullet548 # Add a newline before each bullet
@@ -560,7 +559,7 @@
560 desc_buffer.set_text(long_desc)559 desc_buffer.set_text(long_desc)
561560
562 # now do the changelog561 # now do the changelog
563 name = item.pkg.name562 name = item.pkg_name
564 if name is None:563 if name is None:
565 return564 return
566565
@@ -713,9 +712,9 @@
713 return712 return
714 pkg = None713 pkg = None
715 if data.item:714 if data.item:
716 pkg = data.item.pkg715 pkg = data.item.get_pkg()
717 elif data.group and data.group.core_item:716 elif data.group and data.group.core_item:
718 pkg = data.group.core_item.pkg717 pkg = data.group.core_item.get_pkg()
719 if pkg and self.pkg_requires_restart(pkg):718 if pkg and self.pkg_requires_restart(pkg):
720 nonlocal requires_restart719 nonlocal requires_restart
721 requires_restart = True720 requires_restart = True
@@ -953,12 +952,12 @@
953 for item in items:952 for item in items:
954 try:953 try:
955 if keep_packages:954 if keep_packages:
956 item.pkg.mark_keep()955 item.get_pkg().mark_keep()
957 elif item.pkg.name not in self.list.held_back:956 elif item.pkg_name not in self.list.held_back:
958 if not item.to_remove:957 if not item.to_remove:
959 item.pkg.mark_install()958 item.get_pkg().mark_install()
960 else:959 else:
961 item.pkg.mark_delete()960 item.get_pkg().mark_delete()
962 except SystemError:961 except SystemError:
963 pass962 pass
964963
@@ -1030,7 +1029,8 @@
1030 item_row = [1029 item_row = [
1031 item.name,1030 item.name,
1032 UpdateData(None, None, item),1031 UpdateData(None, None, item),
1033 humanize_size(getattr(item.pkg.candidate, "size", 0)),1032 humanize_size(getattr(item.get_pkg().candidate,
1033 "size", 0)),
1034 True1034 True
1035 ]1035 ]
1036 self.store.append(group_iter, item_row)1036 self.store.append(group_iter, item_row)
10371037
=== modified file 'debian/changelog'
--- debian/changelog 2018-05-28 14:07:41 +0000
+++ debian/changelog 2018-05-31 12:03:14 +0000
@@ -1,3 +1,14 @@
1update-manager (1:18.10.2) UNRELEASED; urgency=medium
2
3 [ Steve Langasek ]
4 * Fix code comment to document the actual proxy behavior, which is correct.
5
6 [ Balint Reczey ]
7 * Store package name and cache reference in UpdateItem to avoid stale
8 package references (LP: #1773316)
9
10 -- Balint Reczey <rbalint@ubuntu.com> Thu, 31 May 2018 13:59:25 +0200
11
1update-manager (1:18.10.1) cosmic; urgency=medium12update-manager (1:18.10.1) cosmic; urgency=medium
213
3 * Block style context changed signal while enforcing the main window's14 * Block style context changed signal while enforcing the main window's
415
=== modified file 'tests/test_update_list.py'
--- tests/test_update_list.py 2017-08-07 19:42:06 +0000
+++ tests/test_update_list.py 2018-05-31 12:03:14 +0000
@@ -59,7 +59,8 @@
59 ignored_srcs = set([pkg.candidate.source_name for pkg in59 ignored_srcs = set([pkg.candidate.source_name for pkg in
60 self.updates_list.ignored_phased_updates])60 self.updates_list.ignored_phased_updates])
61 group = self.updates_list.update_groups[0]61 group = self.updates_list.update_groups[0]
62 install_srcs = set([x.pkg.candidate.source_name for x in group.items])62 install_srcs = set(
63 [x.get_pkg().candidate.source_name for x in group.items])
63 self.assertEqual(ignored_srcs, set({'zsh'}))64 self.assertEqual(ignored_srcs, set({'zsh'}))
64 self.assertEqual(install_srcs, set({'apt'}))65 self.assertEqual(install_srcs, set({'apt'}))
65 self.assertTrue(len(ignored_srcs & install_srcs) == 0)66 self.assertTrue(len(ignored_srcs & install_srcs) == 0)
@@ -142,8 +143,8 @@
142 group = self.updates_list.update_groups[0]143 group = self.updates_list.update_groups[0]
143 self.assertIsInstance(group, UpdateList.UpdateApplicationGroup)144 self.assertIsInstance(group, UpdateList.UpdateApplicationGroup)
144 self.assertIsNotNone(group.core_item)145 self.assertIsNotNone(group.core_item)
145 self.assertEqual(group.core_item.pkg.name, 'installed-app')146 self.assertEqual(group.core_item.pkg_name, 'installed-app')
146 self.assertListEqual([x.pkg.name for x in group.items],147 self.assertListEqual([x.pkg_name for x in group.items],
147 ['installed-app'])148 ['installed-app'])
148149
149 def test_app_with_subitems(self):150 def test_app_with_subitems(self):
@@ -151,9 +152,9 @@
151 group = self.updates_list.update_groups[1]152 group = self.updates_list.update_groups[1]
152 self.assertIsInstance(group, UpdateList.UpdateApplicationGroup)153 self.assertIsInstance(group, UpdateList.UpdateApplicationGroup)
153 self.assertIsNotNone(group.core_item)154 self.assertIsNotNone(group.core_item)
154 self.assertEqual(group.core_item.pkg.name,155 self.assertEqual(group.core_item.pkg_name,
155 'installed-app-with-subitems')156 'installed-app-with-subitems')
156 self.assertListEqual([x.pkg.name for x in group.items],157 self.assertListEqual([x.pkg_name for x in group.items],
157 ['installed-app-with-subitems',158 ['installed-app-with-subitems',
158 'installed-pkg-single-dep'])159 'installed-pkg-single-dep'])
159160
@@ -162,8 +163,8 @@
162 group = self.updates_list.update_groups[2]163 group = self.updates_list.update_groups[2]
163 self.assertIsInstance(group, UpdateList.UpdatePackageGroup)164 self.assertIsInstance(group, UpdateList.UpdatePackageGroup)
164 self.assertIsNotNone(group.core_item)165 self.assertIsNotNone(group.core_item)
165 self.assertEqual(group.core_item.pkg.name, 'installed-pkg')166 self.assertEqual(group.core_item.pkg_name, 'installed-pkg')
166 self.assertListEqual([x.pkg.name for x in group.items],167 self.assertListEqual([x.pkg_name for x in group.items],
167 ['installed-pkg'])168 ['installed-pkg'])
168169
169 def test_pkg_multiple_deps(self):170 def test_pkg_multiple_deps(self):
@@ -171,9 +172,9 @@
171 group = self.updates_list.update_groups[3]172 group = self.updates_list.update_groups[3]
172 self.assertIsInstance(group, UpdateList.UpdatePackageGroup)173 self.assertIsInstance(group, UpdateList.UpdatePackageGroup)
173 self.assertIsNotNone(group.core_item)174 self.assertIsNotNone(group.core_item)
174 self.assertEqual(group.core_item.pkg.name,175 self.assertEqual(group.core_item.pkg_name,
175 'installed-pkg-multiple-deps')176 'installed-pkg-multiple-deps')
176 self.assertListEqual([x.pkg.name for x in group.items],177 self.assertListEqual([x.pkg_name for x in group.items],
177 ['installed-pkg-multiple-deps'])178 ['installed-pkg-multiple-deps'])
178179
179 def test_security(self):180 def test_security(self):
@@ -181,7 +182,7 @@
181 group = self.updates_list.security_groups[0]182 group = self.updates_list.security_groups[0]
182 self.assertIsInstance(group, UpdateList.UpdateSystemGroup)183 self.assertIsInstance(group, UpdateList.UpdateSystemGroup)
183 self.assertIsNone(group.core_item)184 self.assertIsNone(group.core_item)
184 self.assertListEqual([x.pkg.name for x in group.items], ['base-pkg'])185 self.assertListEqual([x.pkg_name for x in group.items], ['base-pkg'])
185186
186187
187if __name__ == "__main__":188if __name__ == "__main__":

Subscribers

People subscribed via source and target branches

to status/vote changes: