Repository: dwavesystems/dwave-optimization (language: python)

Issue:
Model remains locked on erroneous context exit
An error in the lock() context will exit the context but leave the model locked.

```
>>> from dwave.optimization.model import Model
>>> model = Model()
>>> with model.lock():
            duckbilledplatypus
```

exits context with error `NameError: name 'duckbilledplatypus' is not defined`  but keeps the model locked:

```
>>> model.is_locked()
True
```

Should the lock context have a `try` context so it can exit gracefully (undo the lock)?


Discussion / hints:

Fix patch:
diff --git a/dwave/optimization/model.pyx b/dwave/optimization/model.pyx
index d87de8f6..033683fb 100644
--- a/dwave/optimization/model.pyx
+++ b/dwave/optimization/model.pyx
@@ -53,8 +53,10 @@ __all__ = ["Model"]
 @contextlib.contextmanager
 def locked(model):
     """Context manager that hold a locked model and unlocks it when the context is exited."""
-    yield
-    model.unlock()
+    try:
+        yield
+    finally:
+        model.unlock()
 
 
 cdef class Model:


Test patch:
diff --git a/tests/test_model.py b/tests/test_model.py
index 5ccdb630..79a2cfa0 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -117,6 +117,12 @@ def test_lock(self):
             self.assertTrue(model.is_locked())
         self.assertFalse(model.is_locked())
 
+        # Works in case of an exception
+        with model.lock(), self.assertRaises(ValueError):
+            self.assertTrue(model.is_locked())
+            raise ValueError
+        self.assertFalse(model.is_locked())
+
         # Works as a reentrant context manager
         with model.lock():
             self.assertTrue(model.is_locked())