diff --git a/docs/source/examples/ho_contingency.ipynb b/docs/source/examples/ho_contingency.ipynb index 3d98cb9e8..747f1a4ed 100644 --- a/docs/source/examples/ho_contingency.ipynb +++ b/docs/source/examples/ho_contingency.ipynb @@ -80,6 +80,11 @@ "`RE.halt()` | Do not perform cleanup — just stop.\n", "`RE.state` | Show the RunEngine state. Check if ‘paused’ or ‘idle’.\n", "\n", + "\n", + "If the RE is not responding to `^C`, if you hit it 10 times rapidly it will return the prompt, however the `RE` will be\n", + "left in an invalid state with no graceful recovery possible. This should be used as a last resort. If the `RE` has become hung\n", + "that is likly a bug in one of the ophyd objects methods or the plan.\n", + "\n", "### Programmatic Interruption\n", "\n", "There are two ways for a program to interrupt the `RunEngine`: [suspenders](#suspenders) and [exceptions](#exceptions).\n", @@ -296,13 +301,22 @@ "source": [ "### `try..except..else..finally` in bluesky plans\n", "\n", - "In `bluesky`, `try..except` is such a common pattern that there are two [_decorator_](https://wiki.python.org/moin/PythonDecorators#What_is_a_Python_Decorator) functions available:\n", + "In `bluesky`, `try..except` is a common pattern, however there is some additional complication due to\n", + "plans being generator co-routines so two [_decorator_](https://wiki.python.org/moin/PythonDecorators#What_is_a_Python_Decorator) functions available:\n", "\n", "decorator | synopsis\n", "--- | ---\n", "`finalize_decorator` | Simple. Runs the `final_plan` no matter what happens in the decorated plan.\n", "`contingency_decorator` | Full-featured. Handle each aspect of Python's `try..except..else..finally` clause.\n", "\n", + "**Exception handling in generator coroutines**\n", + "\n", + "Generator coroutines [expose exception throwing](https://tacaswell.github.io/coroutines-i.html) as a user communication channel. This means\n", + "there is some [in-band encoding](https://youtu.be/iKzOBWOHGFE?si=XAtQKhk3eHboHcL-&t=1011) of Python's control exceptions with the users exceptions. When the `close()` [method](https://docs.python.org/3/reference/expressions.html#generator.close) is called on a generator coroutine, Python will throw a [`GeneratorExit`](https://docs.python.org/3/library/exceptions.html#GeneratorExit) exception into the coroutine. If you catch this exception and try to yield another `Msg`, either explicitly in an `except` block or in a `finally` block, Python will raise a `RuntimeError` at the call site.\n", + "\n", + "If you want to use `try..except..else..finally` directly, you must handle this case. See the source of these decorators for a guide.\n", + "\n", + "\n", "**More Reading**\n", "\n", "- https://realpython.com/primer-on-python-decorators/\n", @@ -369,8 +383,6 @@ "try:\n", " yield from bps.mv(shutter, \"open\")\n", " yield from bp.count([detector])\n", - "except Exception:\n", - " pass # ignore all exceptions\n", "finally:\n", " yield from bps.mv(shutter, \"close\")\n", "```" @@ -512,6 +524,7 @@ "def my_else_plan():\n", " print(f\"my_else_plan(): plan completed successfully! {shutter.state=}\")\n", " yield from bps.null()\n", + " yield from bps.null()\n", "\n", "def close_the_shutter():\n", " print(f\"close_the_shutter()\")\n", @@ -550,6 +563,7 @@ " yield from bps.null()\n", "else:\n", " yield from bps.null()\n", + " yield from bps.null()\n", "finally:\n", " yield from bps.mv(shutter, \"close\")\n", "```"