[libcamera-devel,v2,10/14] py: Switch to non-blocking eventfd
diff mbox series

Message ID 20220629070416.17550-11-tomi.valkeinen@ideasonboard.com
State Superseded
Headers show
Series
  • Python bindings event handling
Related show

Commit Message

Tomi Valkeinen June 29, 2022, 7:04 a.m. UTC
Blocking wait can be easily implemented on top in Python, so rather than
supporting only blocking reads, or supporting both non-blocking and
blocking reads, let's support only non-blocking reads.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 src/py/examples/simple-cam.py                |  5 +++--
 src/py/examples/simple-capture.py            | 12 +++++++++--
 src/py/examples/simple-continuous-capture.py |  5 +++--
 src/py/libcamera/py_camera_manager.cpp       | 22 +++++++++++++-------
 src/py/libcamera/py_camera_manager.h         |  2 +-
 test/py/unittests.py                         |  7 +++++++
 6 files changed, 39 insertions(+), 14 deletions(-)

Patch
diff mbox series

diff --git a/src/py/examples/simple-cam.py b/src/py/examples/simple-cam.py
index e88a4737..1cd1019d 100755
--- a/src/py/examples/simple-cam.py
+++ b/src/py/examples/simple-cam.py
@@ -19,8 +19,9 @@  TIMEOUT_SEC = 3
 
 
 def handle_camera_event(cm):
-    # cm.get_ready_requests() will not block here, as we know there is an event
-    # to read.
+    # cm.get_ready_requests() returns the ready requests, which in our case
+    # should almost always return a single Request, but in some cases there
+    # could be multiple or none.
 
     reqs = cm.get_ready_requests()
 
diff --git a/src/py/examples/simple-capture.py b/src/py/examples/simple-capture.py
index 07d12dae..4b85408f 100755
--- a/src/py/examples/simple-capture.py
+++ b/src/py/examples/simple-capture.py
@@ -14,6 +14,7 @@ 
 
 import argparse
 import libcamera as libcam
+import selectors
 import sys
 
 # Number of frames to capture
@@ -102,11 +103,18 @@  def main():
     # The main loop. Wait for the queued Requests to complete, process them,
     # and re-queue them again.
 
+    sel = selectors.DefaultSelector()
+    sel.register(cm.event_fd, selectors.EVENT_READ)
+
     while frames_done < TOTAL_FRAMES:
-        # cm.get_ready_requests() blocks until there is an event and returns
-        # all the ready requests. Here we should almost always get a single
+        # cm.get_ready_requests() does not block, so we use a Selector to wait
+        # for a camera event. Here we should almost always get a single
         # Request, but in some cases there could be multiple or none.
 
+        events = sel.select()
+        if not events:
+            continue
+
         reqs = cm.get_ready_requests()
 
         for req in reqs:
diff --git a/src/py/examples/simple-continuous-capture.py b/src/py/examples/simple-continuous-capture.py
index ef3f87d1..e1cb931e 100755
--- a/src/py/examples/simple-continuous-capture.py
+++ b/src/py/examples/simple-continuous-capture.py
@@ -83,8 +83,9 @@  class CaptureContext:
     camera_contexts: list[CameraCaptureContext] = []
 
     def handle_camera_event(self):
-        # cm.get_ready_requests() will not block here, as we know there is an event
-        # to read.
+        # cm.get_ready_requests() returns the ready requests, which in our case
+        # should almost always return a single Request, but in some cases there
+        # could be multiple or none.
 
         reqs = self.cm.get_ready_requests()
 
diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp
index 5600f661..3dd8668e 100644
--- a/src/py/libcamera/py_camera_manager.cpp
+++ b/src/py/libcamera/py_camera_manager.cpp
@@ -22,7 +22,7 @@  PyCameraManager::PyCameraManager()
 
 	cameraManager_ = std::make_unique<CameraManager>();
 
-	int fd = eventfd(0, EFD_CLOEXEC);
+	int fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
 	if (fd == -1)
 		throw std::system_error(errno, std::generic_category(),
 					"Failed to create eventfd");
@@ -60,18 +60,24 @@  py::list PyCameraManager::cameras()
 
 std::vector<py::object> PyCameraManager::getReadyRequests()
 {
-	readFd();
+	int ret = readFd();
 
-	std::vector<py::object> ret;
+	if (ret == EAGAIN)
+		return std::vector<py::object>();
+
+	if (ret != 0)
+		throw std::system_error(ret, std::generic_category());
+
+	std::vector<py::object> py_reqs;
 
 	for (Request *request : getCompletedRequests()) {
 		py::object o = py::cast(request);
 		/* Decrease the ref increased in Camera.queue_request() */
 		o.dec_ref();
-		ret.push_back(o);
+		py_reqs.push_back(o);
 	}
 
-	return ret;
+	return py_reqs;
 }
 
 /* Note: Called from another thread */
@@ -94,12 +100,14 @@  void PyCameraManager::writeFd()
 		LOG(Python, Fatal) << "Unable to write to eventfd";
 }
 
-void PyCameraManager::readFd()
+int PyCameraManager::readFd()
 {
 	uint8_t buf[8];
 
 	if (read(eventFd_.get(), buf, 8) != 8)
-		throw std::system_error(errno, std::generic_category());
+		return errno;
+
+	return 0;
 }
 
 void PyCameraManager::pushRequest(Request *req)
diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h
index 56bea13d..3525057d 100644
--- a/src/py/libcamera/py_camera_manager.h
+++ b/src/py/libcamera/py_camera_manager.h
@@ -39,7 +39,7 @@  private:
 		LIBCAMERA_TSA_GUARDED_BY(completedRequestsMutex_);
 
 	void writeFd();
-	void readFd();
+	int readFd();
 	void pushRequest(Request *req);
 	std::vector<Request *> getCompletedRequests();
 };
diff --git a/test/py/unittests.py b/test/py/unittests.py
index b90b5fec..794e46be 100755
--- a/test/py/unittests.py
+++ b/test/py/unittests.py
@@ -191,9 +191,16 @@  class SimpleCaptureMethods(CameraTesterBase):
         reqs = None
         gc.collect()
 
+        sel = selectors.DefaultSelector()
+        sel.register(cm.event_fd, selectors.EVENT_READ)
+
         reqs = []
 
         while True:
+            events = sel.select()
+            if not events:
+                continue
+
             ready_reqs = cm.get_ready_requests()
 
             reqs += ready_reqs