[libcamera-devel] [PATCH 2/2] libcamera: request: A request canary
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Tue Feb 7 17:05:54 CET 2023
Hello,
On Mon, Jan 30, 2023 at 08:38:47PM +0100, Jacopo Mondi via libcamera-devel wrote:
> On Mon, Jan 30, 2023 at 06:02:44PM +0000, Kieran Bingham via libcamera-devel wrote:
> > Request objects are created and owned by the application, but are
> > utilised widely throughout the internals of libcamera.
>
> I would have sworn me moved Request in and out from the Camera..
>
> unique_ptr<Request> Camera::createRequest();
> int Camera::queueRequest(unique_ptr<Request> r);
>
> But we indeed pass it by referece...
>
> int Camera::queueRequest(Request *r);
>
> I wonder
>
> - Once a Request is queued and before it is signaled as complete,
> should applications be able to access it ?
Possibly in read-only mode, but even that will likely be racy, and I
don't see any compelling use case right now.
> - Request are signaled as complete through a signal, I guess a signal
> cannot transport a unique_ptr<> back ?
Correct. This is due to the fact that you can connect a signal to
multiple slots, which would break the ownership transfer requirements of
std::unique_ptr<>.
I'm more and more tempted to experiment with passing requests by value
(although we'll have a similar problem with framebuffers). It feels like
we're working around a badly designed API, we should fix the API
instead.
> > If the application free's the requests while they are still active
s/free's/fress/
> > within libcamera a use after free will occur. While this can be trapped
> > by tools such as valgrind, given the importance of this object and the
> > relationship of external ownership, it may have some value to provide
> > extended assertions on the condition of these objects.
> >
> > Make sure the request fails an assertion immediately if used after free.
> >
> > Further more, this allows us to immediately reject invalid Request
s/Further more/Furthermore/
> > objects directly from the Camera queueRequest API.
> >
> > Signed-off-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
> >
> > ---
> >
> > v2?
> > - Added canary to both the public and private request objects.
> > - Added validation to the Camera queueRequest().
> >
> > Signed-off-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
> > ---
> > include/libcamera/internal/request.h | 2 ++
> > include/libcamera/request.h | 4 +++
> > src/libcamera/camera.cpp | 5 +++
> > src/libcamera/request.cpp | 54 ++++++++++++++++++++++++++--
> > 4 files changed, 63 insertions(+), 2 deletions(-)
> >
> > diff --git a/include/libcamera/internal/request.h b/include/libcamera/internal/request.h
> > index 8c92a27a95e5..981ad184fffa 100644
> > --- a/include/libcamera/internal/request.h
> > +++ b/include/libcamera/internal/request.h
> > @@ -59,6 +59,8 @@ private:
> > std::unordered_set<FrameBuffer *> pending_;
> > std::map<FrameBuffer *, std::unique_ptr<EventNotifier>> notifiers_;
> > std::unique_ptr<Timer> timer_;
> > +
> > + uint32_t canary_;
> > };
> >
> > } /* namespace libcamera */
> > diff --git a/include/libcamera/request.h b/include/libcamera/request.h
> > index dffde1536cad..8e377de14b12 100644
> > --- a/include/libcamera/request.h
> > +++ b/include/libcamera/request.h
> > @@ -65,6 +65,8 @@ public:
> >
> > std::string toString() const;
> >
> > + bool canary() const;
> > +
> > private:
> > LIBCAMERA_DISABLE_COPY(Request)
> >
> > @@ -74,6 +76,8 @@ private:
> >
> > const uint64_t cookie_;
> > Status status_;
> > +
> > + int32_t canary_;
We're trying to move away from storing private data in the public class
:-S I really dislike exposing this to the public API, including the
public canary() function.
> > };
> >
> > std::ostream &operator<<(std::ostream &out, const Request &r);
> > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp
> > index 48bf19b28979..3b72098cce59 100644
> > --- a/src/libcamera/camera.cpp
> > +++ b/src/libcamera/camera.cpp
> > @@ -1116,6 +1116,11 @@ int Camera::queueRequest(Request *request)
> > {
> > Private *const d = _d();
> >
> > + if (request->canary()) {
> > + LOG(Camera, Error) << "Invalid request";
> > + return -EINVAL;
I'd make everything fatal, using ASSERT(). If the request passed to this
function has been freed, it's game over anyway. Same below.
> > + }
> > +
> > int ret = d->isAccessAllowed(Private::CameraRunning);
> > if (ret < 0)
> > return ret;
> > diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
> > index 949c556fa437..cfe732765f86 100644
> > --- a/src/libcamera/request.cpp
> > +++ b/src/libcamera/request.cpp
> > @@ -23,6 +23,8 @@
> > #include "libcamera/internal/framebuffer.h"
> > #include "libcamera/internal/tracepoints.h"
> >
> > +#define REQUEST_CANARY 0x1F2E3D4C
Lower-case hex constants.
> > +
> > /**
> > * \file libcamera/request.h
> > * \brief Describes a frame capture request to be processed by a camera
> > @@ -48,13 +50,14 @@ LOG_DEFINE_CATEGORY(Request)
> > * \param camera The Camera that creates the request
> > */
> > Request::Private::Private(Camera *camera)
> > - : camera_(camera), cancelled_(false)
> > + : camera_(camera), cancelled_(false), canary_(REQUEST_CANARY)
> > {
> > }
> >
> > Request::Private::~Private()
> > {
> > doCancelRequest();
> > + canary_ = 0;
> > }
> >
> > /**
> > @@ -114,6 +117,7 @@ void Request::Private::complete()
> > {
> > Request *request = _o<Request>();
> >
> > + ASSERT(canary_ == REQUEST_CANARY);
> > ASSERT(request->status() == RequestPending);
> > ASSERT(!hasPendingBuffers());
> >
> > @@ -128,6 +132,8 @@ void Request::Private::doCancelRequest()
> > {
> > Request *request = _o<Request>();
> >
> > + ASSERT(canary_ == REQUEST_CANARY);
> > +
> > for (FrameBuffer *buffer : pending_) {
> > buffer->_d()->cancel();
> > camera_->bufferCompleted.emit(request, buffer);
> > @@ -149,6 +155,8 @@ void Request::Private::doCancelRequest()
> > */
> > void Request::Private::cancel()
> > {
> > + ASSERT(canary_ == REQUEST_CANARY);
> > +
> > LIBCAMERA_TRACEPOINT(request_cancel, this);
> >
> > Request *request = _o<Request>();
> > @@ -346,7 +354,7 @@ void Request::Private::timeout()
> > */
> > Request::Request(Camera *camera, uint64_t cookie)
> > : Extensible(std::make_unique<Private>(camera)),
> > - cookie_(cookie), status_(RequestPending)
> > + cookie_(cookie), status_(RequestPending), canary_(REQUEST_CANARY)
> > {
> > controls_ = new ControlList(controls::controls,
> > camera->_d()->validator());
> > @@ -367,6 +375,8 @@ Request::~Request()
> >
> > delete metadata_;
> > delete controls_;
> > +
> > + canary_ = 0;
> > }
> >
> > /**
> > @@ -381,6 +391,11 @@ Request::~Request()
> > */
> > void Request::reuse(ReuseFlag flags)
> > {
> > + if (canary_ != REQUEST_CANARY) {
> > + LOG(Request, Error) << "Invalid Request object";
> > + return;
> > + }
> > +
> > LIBCAMERA_TRACEPOINT(request_reuse, this);
> >
> > _d()->reset();
> > @@ -462,6 +477,11 @@ void Request::reuse(ReuseFlag flags)
> > int Request::addBuffer(const Stream *stream, FrameBuffer *buffer,
> > std::unique_ptr<Fence> fence)
> > {
> > + if (canary_ != REQUEST_CANARY) {
> > + LOG(Request, Error) << "Attempt to add buffer to invalid request";
> > + return -EINVAL;
> > + }
> > +
> > if (!stream) {
> > LOG(Request, Error) << "Invalid stream reference";
> > return -EINVAL;
> > @@ -509,6 +529,11 @@ int Request::addBuffer(const Stream *stream, FrameBuffer *buffer,
> > */
> > FrameBuffer *Request::findBuffer(const Stream *stream) const
> > {
> > + if (canary_ != REQUEST_CANARY) {
> > + LOG(Request, Error) << "Invalid Request object";
> > + return nullptr;
> > + }
> > +
> > const auto it = bufferMap_.find(stream);
> > if (it == bufferMap_.end())
> > return nullptr;
> > @@ -571,6 +596,7 @@ uint32_t Request::sequence() const
> > */
> > bool Request::hasPendingBuffers() const
> > {
> > + ASSERT(canary_ == REQUEST_CANARY);
> > return !_d()->pending_.empty();
> > }
> >
> > @@ -590,6 +616,25 @@ std::string Request::toString() const
> > return ss.str();
> > }
> >
> > +/**
> > + * \brief Identify if the Request object is valid and alive
> > + *
> > + * This provides a means of checking if the request is a valid request object.
> > + * While Requests are constructed by libcamera, they are owned and may be freed
> > + * by applications. It can be all to easy too release a Request object while it
> > + * is still in use by libcamera - or attempt to requeue invalid or deleted
> > + * requests.
> > + *
> > + * The canary provides an insight that the object is not valid and shall be
> > + * rejected by libcamera API calls.
> > + *
> > + * \return True if the canary has died, and the object shall not be trusted
> > + */
> > +bool Request::canary() const
> > +{
> > + return canary_ != REQUEST_CANARY;
> > +}
> > +
> > /**
> > * \brief Insert a text representation of a Request into an output stream
> > * \param[in] out The output stream
> > @@ -601,6 +646,11 @@ std::ostream &operator<<(std::ostream &out, const Request &r)
> > /* Pending, Completed, Cancelled(X). */
> > static const char *statuses = "PCX";
> >
> > + if (r.canary()) {
> > + out << "Request(Invalid Canary)";
> > + return out;
> > + }
> > +
> > /* Example Output: Request(55:P:1/2:6523524) */
> > out << "Request(" << r.sequence() << ":" << statuses[r.status()] << ":"
> > << r._d()->pending_.size() << "/" << r.buffers().size() << ":"
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list