Repository: holoviz/panel

Issue:
Inconsistent pn.state.busy when throttling is enabled globally
#### ALL software version info
Panel 1.4.0
Bokeh 3.4.0
In a server

#### Description of expected behavior and the observed behavior
I expect `pn.state.busy` to reflect the actual state of the app. With Panel 1.4.0, if `pn.config.throttled` is set to `True`, the busy parameter will be `True` as soon as a slider is moved, even if it's not released, and stay `True` forever, including in new sessions for the remainder of the app life cycle.

Note that this does not happen if throttling is disabled globally and the function is bound to `slider.param.value_throttled`.

#### Complete, minimal, self-contained example code that reproduces the issue

```python
import panel as pn

pn.extension(throttled=True)


def synced_spinner(value):
    return pn.indicators.LoadingSpinner(value=value)


slider = pn.widgets.IntSlider(name='Slider', start=0, end=1)
text = pn.bind(lambda x: f'Value of slider is {x}', slider)
spinner = pn.bind(synced_spinner, pn.state.param.busy)

pn.Row(pn.Column(slider, text), spinner).servable()
```

#### Stack traceback and/or browser JavaScript console output
No error in python or javascript

#### Screenshots or screencasts of the bug in action
![busy_bug](https://github.com/holoviz/panel/assets/49269671/ab13ba97-1737-4588-aa47-b804b06aa7e4)


Discussion:
Strangely I was able to reproduce this once, and then never again.

Resolving patch:
diff --git a/panel/reactive.py b/panel/reactive.py
--- a/panel/reactive.py
+++ b/panel/reactive.py
@@ -380,13 +380,13 @@ def _process_events(self, events: Dict[str, Any]) -> None:
         if any(e for e in events if e not in self._busy__ignore):
             with edit_readonly(state):
                 state._busy_counter += 1
-        events = self._process_property_change(events)
+        params = self._process_property_change(dict(events))
         try:
             with edit_readonly(self):
-                self_events = {k: v for k, v in events.items() if '.' not in k}
-                with _syncing(self, list(self_events)):
-                    self.param.update(**self_events)
-            for k, v in self_events.items():
+                self_params = {k: v for k, v in params.items() if '.' not in k}
+                with _syncing(self, list(self_params)):
+                    self.param.update(**self_params)
+            for k, v in params.items():
                 if '.' not in k:
                     continue
                 *subpath, p = k.split('.')
@@ -397,13 +397,13 @@ def _process_events(self, events: Dict[str, Any]) -> None:
                     with _syncing(obj, [p]):
                         obj.param.update(**{p: v})
         except Exception:
-            if len(events)>1:
-                msg_end = f" changing properties {pformat(events)} \n"
-            elif len(events)==1:
-                msg_end = f" changing property {pformat(events)} \n"
+            if len(params) > 1:
+                msg_end = f"changing properties {pformat(params)} \n"
+            elif len(params) == 1:
+                msg_end = f"changing property {pformat(params)} \n"
             else:
                 msg_end = "\n"
-            log.exception(f'Callback failed for object named "{self.name}"{msg_end}')
+            log.exception(f'Callback failed for object named {self.name!r} {msg_end}')
             raise
         finally:
             self._log('finished processing events %s', events)


Test patch: