pyobs-aravis 2.0.0.dev6__tar.gz → 2.0.0.dev7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/PKG-INFO +1 -1
  2. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/pyobs_aravis/araviscamera.py +40 -4
  3. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/pyproject.toml +1 -1
  4. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/uv.lock +1 -1
  5. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/.github/workflows/pypi.yml +0 -0
  6. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/.github/workflows/ruff.yml +0 -0
  7. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/.pre-commit-config.yaml +0 -0
  8. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/.readthedocs.yml +0 -0
  9. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/CHANGELOG.rst +0 -0
  10. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/LICENSE +0 -0
  11. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/README.md +0 -0
  12. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/docs/Makefile +0 -0
  13. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/docs/make.bat +0 -0
  14. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/docs/requirements.txt +0 -0
  15. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/docs/source/_static/pyobs.gif +0 -0
  16. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/docs/source/conf.py +0 -0
  17. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/docs/source/index.rst +0 -0
  18. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/pyobs_aravis/__init__.py +0 -0
  19. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/pyobs_aravis/aravis.py +0 -0
  20. {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev7}/pyobs_aravis/gui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobs-aravis
3
- Version: 2.0.0.dev6
3
+ Version: 2.0.0.dev7
4
4
  Summary: pyobs module for Aravis cameras
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -1,6 +1,8 @@
1
1
  import asyncio
2
2
  import logging
3
+ import threading
3
4
  import time
5
+ from collections.abc import Callable
4
6
  from typing import Any
5
7
 
6
8
  import numpy.typing as npt
@@ -9,6 +11,11 @@ from pyobs.modules.camera import BaseVideo
9
11
 
10
12
  log = logging.getLogger(__name__)
11
13
 
14
+ # aravis/GLib calls are blocking and are made directly on the event loop thread (see _run_blocking).
15
+ # If the camera has gone unresponsive, they can hang indefinitely, so we bound them with a timeout
16
+ # rather than let a single dead camera freeze the whole module.
17
+ _SDK_CALL_TIMEOUT = 5.0
18
+
12
19
 
13
20
  class AravisCamera(BaseVideo, IExposureTime):
14
21
  """A pyobs module for Aravis cameras."""
@@ -59,8 +66,7 @@ class AravisCamera(BaseVideo, IExposureTime):
59
66
  async def close(self) -> None:
60
67
  """Close the module."""
61
68
  await BaseVideo.close(self)
62
- async with self._camera_lock:
63
- self._close_camera()
69
+ await self._deactivate_camera()
64
70
 
65
71
  def _open_camera(self) -> None:
66
72
  """Open camera."""
@@ -87,15 +93,45 @@ class AravisCamera(BaseVideo, IExposureTime):
87
93
  log.exception("Error closing camera.")
88
94
  self._camera = None
89
95
 
96
+ @staticmethod
97
+ async def _run_blocking(func: Callable[[], None], timeout: float = _SDK_CALL_TIMEOUT) -> bool:
98
+ """Run a blocking aravis/GLib call in a daemon thread, so a hung call can't freeze the module.
99
+
100
+ A plain executor isn't used here, since its worker threads are non-daemon and Python joins
101
+ them on interpreter shutdown -- a hung call would then just move the freeze to process exit.
102
+
103
+ Returns:
104
+ True if func completed within timeout, False if it's still running in the background.
105
+ """
106
+ loop = asyncio.get_running_loop()
107
+ future: asyncio.Future[None] = loop.create_future()
108
+
109
+ def _wrapper() -> None:
110
+ try:
111
+ func()
112
+ finally:
113
+ loop.call_soon_threadsafe(future.set_result, None)
114
+
115
+ threading.Thread(target=_wrapper, daemon=True).start()
116
+ try:
117
+ await asyncio.wait_for(future, timeout=timeout)
118
+ return True
119
+ except TimeoutError:
120
+ return False
121
+
90
122
  async def _activate_camera(self) -> None:
91
123
  """Open camera on activation."""
92
124
  async with self._camera_lock:
93
- self._open_camera()
125
+ if not await self._run_blocking(self._open_camera):
126
+ log.error("Timed out connecting to camera after %.1fs.", _SDK_CALL_TIMEOUT)
127
+ self._camera = None
94
128
 
95
129
  async def _deactivate_camera(self) -> None:
96
130
  """Close camera on deactivation."""
97
131
  async with self._camera_lock:
98
- self._close_camera()
132
+ if not await self._run_blocking(self._close_camera):
133
+ log.error("Timed out closing camera after %.1fs, abandoning cleanup.", _SDK_CALL_TIMEOUT)
134
+ self._camera = None
99
135
 
100
136
  async def _capture(self) -> None:
101
137
  """Take new images in loop."""
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyobs-aravis"
3
- version = "2.0.0.dev6"
3
+ version = "2.0.0.dev7"
4
4
  description = "pyobs module for Aravis cameras"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
6
6
  requires-python = ">=3.11,<3.14"
@@ -1770,7 +1770,7 @@ wheels = [
1770
1770
 
1771
1771
  [[package]]
1772
1772
  name = "pyobs-aravis"
1773
- version = "2.0.0.dev6"
1773
+ version = "2.0.0.dev7"
1774
1774
  source = { editable = "." }
1775
1775
  dependencies = [
1776
1776
  { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },