Bug report
Bug description:
Shelf.sync() sets self.writeback to False while it writes the cached entries back, and only sets it back to True after the loop. If writing one of the entries raises (for example, a cached object that can no longer be pickled), the exception propagates as expected, but writeback stays False. The shelf keeps working, and later mutations of accessed objects are silently lost:
import shelve
class Unpicklable:
def __reduce__(self):
raise RuntimeError("cannot pickle")
d = {}
s = shelve.Shelf(d, writeback=True)
s["a"] = []
s["a"].append(Unpicklable())
try:
s.sync()
except RuntimeError:
pass
print(s.writeback) # False, expected True
s["a"].clear()
s["b"] = [1]
s["b"].append(2)
s.close()
print(shelve.Shelf(d)["b"]) # [1], expected [1, 2]
The cached entries are still in s.cache after the failure, so the caller could fix the offending object and call sync() again, but by then the shelf is no longer in writeback mode.
Restoring writeback in a finally block would fix this, leaving the cache intact so sync() can be retried.
CPython versions tested on:
3.13, 3.14, 3.15, 3.16, CPython main branch
Operating systems tested on:
Linux
Linked PRs
Bug report
Bug description:
Shelf.sync()setsself.writebacktoFalsewhile it writes the cached entries back, and only sets it back toTrueafter the loop. If writing one of the entries raises (for example, a cached object that can no longer be pickled), the exception propagates as expected, butwritebackstaysFalse. The shelf keeps working, and later mutations of accessed objects are silently lost:The cached entries are still in
s.cacheafter the failure, so the caller could fix the offending object and callsync()again, but by then the shelf is no longer in writeback mode.Restoring
writebackin afinallyblock would fix this, leaving the cache intact sosync()can be retried.CPython versions tested on:
3.13, 3.14, 3.15, 3.16, CPython main branch
Operating systems tested on:
Linux
Linked PRs