Repository: bluesky/bluesky (python)

Issue:
`Plan` always gives a `RuntimeWarning` on use of `rel_set`
Whenever you use `rel_set` (and presumably other stubs which use `plan_mutator`?), `Plan` will raise a warning.

To replicate:

```python
from bluesky.plan_stubs import rel_set
from ophyd_async.epics.demo import Mover


async def test_runtime_plan_warning(RE):
    mover = Mover("test")
    await mover.connect(mock=True)
    RE(rel_set(mover, 2))
```

This was bothering us mainly in tests, as it obscures the real errors in failing tests, but it can be suppressed by adding to `pyproject.toml`:

```toml
[tool.pytest.ini_options]
...
filterwarnings = [
    ...
    "ignore:((.|\n)*)was never iterated.*:RuntimeWarning",
]
```

I don't think it actually causes any errors in execution or anything like that.

## Expected Behavior
The above fragment should not cause a `RuntimeWarning` to be raised

## Current Behavior

```
test_runtimewarning.py::test_runtime_plan_warning failed: Traceback (most recent call last):
  File ".../python3.11/site-packages/_pytest/runner.py", line 341, in from_call
    result: TResult | None = func()
                             ^^^^^^
  File ".../python3.11/site-packages/_pytest/runner.py", line 242, in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../python3.11/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../python3.11/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../python3.11/site-packages/pluggy/_callers.py", line 182, in _multicall
    return outcome.get_result()
           ^^^^^^^^^^^^^^^^^^^^
  File ".../python3.11/site-packages/pluggy/_result.py", line 100, in get_result
    raise exc.with_traceback(exc.__traceback__)
  File ".../python3.11/site-packages/pluggy/_callers.py", line 167, in _multicall
    teardown.throw(outcome._exception)
  File ".../python3.11/site-packages/_pytest/threadexception.py", line 92, in pytest_runtest_call
    yield from thread_exception_runtest_hook()
  File ".../python3.11/site-packages/_pytest/threadexception.py", line 68, in thread_exception_runtest_hook
    yield
  File ".../python3.11/site-packages/pluggy/_callers.py", line 169, in _multicall
    teardown.send(outcome._result)
  File ".../python3.11/site-packages/_pytest/unraisableexception.py", line 95, in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
  File ".../python3.11/site-packages/_pytest/unraisableexception.py", line 85, in unraisable_exception_runtest_hook
    warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
pytest.PytestUnraisableExceptionWarning: Exception ignored in: <function Plan.__del__ at 0x7f2e1b4042c0>

Traceback (most recent call last):
  File ".../python3.11/site-packages/bluesky/utils/__init__.py", line 1939, in __del__
    warnings.warn(warning_message, RuntimeWarning, stacklevel=1)
RuntimeWarning: 
  File ".../python3.11/lib/python3.11/threading.py", line 995, in _bootstrap
    self._bootstrap_inner()
  File ".../python3.11/lib/python3.11/threading.py", line 1038, in _bootstrap_inner
    self.run()
  File ".../python3.11/lib/python3.11/threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
  File ".../python3.11/lib/python3.11/asyncio/base_events.py", line 607, in run_forever
    self._run_once()
  File ".../python3.11/lib/python3.11/asyncio/base_events.py", line 1922, in _run_once
    handle._run()
  File ".../python3.11/lib/python3.11/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File ".../python3.11/site-packages/bluesky/run_engine.py", line 1588, in _run
    msg = self._plan_stack[-1].send(resp)
  File ".../python3.11/site-packages/bluesky/utils/__init__.py", line 1931, in __iter__
    return (yield from self._iter)
  File ".../python3.11/site-packages/bluesky/plan_stubs.py", line 260, in rel_set
    return (yield from relative_set_wrapper(abs_set(obj, *args, group=group, wait=wait, **kwargs)))
RuntimeWarning: plan `abs_set` was never iterated, did you mean to use `yield from`?
```

## Environment
`bluesky 1.13.0a4`

Pull request:
Fix/mutator warnings
<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->
Catch more ways to mark a Plan as "used".

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

Fixes spurious warnings that plans were not iterated

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

Manually + adding a test.

<!--
## Screenshots (if appropriate):
-->


Closes #1797 

Patch:
diff --git a/src/bluesky/utils/__init__.py b/src/bluesky/utils/__init__.py
index fabb780b..9b54f01a 100644
--- a/src/bluesky/utils/__init__.py
+++ b/src/bluesky/utils/__init__.py
@@ -1922,6 +1922,8 @@ async def maybe_await(ret: SyncOrAsync[T]) -> T:
 
 
 class Plan:
+    __slots__ = ("_iter", "_stack")
+
     def __init__(self, f, *args, **kwargs) -> None:
         self._iter = f(*args, **kwargs)
         self._stack = traceback.format_stack()
@@ -1935,6 +1937,7 @@ class Plan:
         return (yield from self._iter)
 
     def __next__(self):
+        self._stack = None
         return next(self._iter)
 
     def __del__(self):
@@ -1943,9 +1946,11 @@ class Plan:
             warnings.warn(warning_message, RuntimeWarning, stacklevel=1)
 
     def send(self, value):
+        self._stack = None
         return self._iter.send(value)
 
     def throw(self, typ, val=None, tb=None):
+        self._stack = None
         return self._iter.throw(typ, val, tb)
 
 


Test patch:
diff --git a/src/bluesky/tests/test_plans.py b/src/bluesky/tests/test_plans.py
index 7ff59bef..536377b0 100644
--- a/src/bluesky/tests/test_plans.py
+++ b/src/bluesky/tests/test_plans.py
@@ -689,3 +689,9 @@ def test_predeclare_env(hw, monkeypatch, predeclare):
             assert "declare_stream" in cmds
         else:
             assert "declare_stream" not in cmds
+
+
+@pytest.mark.filterwarnings("error")
+def test_no_warning(hw):
+    # this should not warn
+    list(bps.rel_set(hw.motor, 2))