diff --git a/Lib/shelve.py b/Lib/shelve.py index 9f6296667fdb6b..f791123f0457e3 100644 --- a/Lib/shelve.py +++ b/Lib/shelve.py @@ -176,9 +176,11 @@ def __del__(self): def sync(self): if self.writeback and self.cache: self.writeback = False - for key, entry in self.cache.items(): - self[key] = entry - self.writeback = True + try: + for key, entry in self.cache.items(): + self[key] = entry + finally: + self.writeback = True self.cache = {} if hasattr(self.dict, 'sync'): self.dict.sync() diff --git a/Lib/test/test_shelve.py b/Lib/test/test_shelve.py index 5f6a030e018f96..e0f7844ce900dd 100644 --- a/Lib/test/test_shelve.py +++ b/Lib/test/test_shelve.py @@ -148,6 +148,25 @@ def test_writeback_also_writes_immediately(self): p2 = d[encodedkey] self.assertNotEqual(p1, p2) # Write creates new object in store + def test_writeback_restored_after_failed_sync(self): + class Unpicklable: + def __reduce__(self): + raise RuntimeError('cannot pickle') + + d = {} + with shelve.Shelf(d, writeback=True) as s: + s['key'] = [] + s['key'].append(Unpicklable()) + with self.assertRaises(RuntimeError): + s.sync() + self.assertTrue(s.writeback) + self.assertIn('key', s.cache) + s['key'].clear() + s['other'] = [1] + s['other'].append(2) + self.assertEqual(pickle.loads(d[b'key']), []) + self.assertEqual(pickle.loads(d[b'other']), [1, 2]) + def test_with(self): d1 = {} with shelve.Shelf(d1, protocol=2, writeback=False) as s: diff --git a/Misc/NEWS.d/next/Library/2026-09-23-03-52-29.gh-issue-157976.cf2fd0.rst b/Misc/NEWS.d/next/Library/2026-09-23-03-52-29.gh-issue-157976.cf2fd0.rst new file mode 100644 index 00000000000000..a67d59edb89266 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-23-03-52-29.gh-issue-157976.cf2fd0.rst @@ -0,0 +1,2 @@ +Fix :meth:`shelve.Shelf.sync` leaving *writeback* disabled when writing back +a cached entry raises an exception. Patch by Christian Aurich Zanettini Martins.