lock object with .acquire() and .release() methods and a task callable that might raise. Write run_locked(lock, task) that acquires, runs the task and returns its result — guaranteeing release() runs even when the task raises (let the exception propagate after releasing). finally is the tool.run_locked(lock, lambda: 42)
42 (lock released)
On success and on failure alike, release() executes.
No except clause needed at all.
Code in finally runs on every exit path.