[libcamera-devel] [PATCH v3 09/17] py: Switch to non-blocking eventfd

Tomi Valkeinen tomi.valkeinen at ideasonboard.com
Fri Aug 19 08:10:48 CEST 2022


On 18/08/2022 23:07, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Fri, Jul 01, 2022 at 11:45:13AM +0300, Tomi Valkeinen wrote:
>> 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 at 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(-)
>>
>> diff --git a/src/py/examples/simple-cam.py b/src/py/examples/simple-cam.py
>> index 2b81bb65..b3e97ca7 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 a6a9b33e..5f93574f 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
>> @@ -107,11 +108,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 fe78a2dd..26a8060b 100755
>> --- a/src/py/examples/simple-continuous-capture.py
>> +++ b/src/py/examples/simple-continuous-capture.py
>> @@ -88,8 +88,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;
> 
> You could possibly name the variable py_reqs already in the patch that
> introduces this function, up to you.

Ok.

>>   
>>   	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;
> 
> When no error occurs, there's no guarantee that read() will set errno to
> 0, so if the return value of read() is 0, errno will be undefined. I
> would also return a negative error code as we usually do (don't forget
> to update the caller).

I think you're right. I read man eventfd so that read always returns 8 
bytes or an error. And I guess that's correct, but I don't think it's 
strictly described anywhere, so better to check the return value more 
thoroughly.

  Tomi


More information about the libcamera-devel mailing list