[{"id":5301,"web_url":"https://patchwork.libcamera.org/comment/5301/","msgid":"<20200620022045.GY5823@pendragon.ideasonboard.com>","date":"2020-06-20T02:20:45","subject":"Re: [libcamera-devel] [PATCH v2 12/17] v4l2: v4l2_camera: Don't use\n\tlibcamera::Semaphore for available buffers","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Paul,\n\nThank you for the patch.\n\nOn Fri, Jun 19, 2020 at 02:41:18PM +0900, Paul Elder wrote:\n> In V4L2, a blocked dqbuf should not not also block a streamoff. This\n> means that on streamoff, the blocked dqbuf must return (with error). We\n> cannot do this with the libcamera semaphore, so pull out the necessary\n> components of a semaphore, and put them into V4L2Camera, so that dqbuf\n> from V4L2CameraProxy can wait on a disjunct condition of the\n> availability of the semaphore or the stopping of the stream.\n> \n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> \n> ---\n> Changes in v2:\n> - remove mutex for isRunning_\n> - use V4L2Camera::isRunning() instead of V4L2CameraProxy::streaming_ in\n>   the dqbuf ioctl handler\n> ---\n>  src/v4l2/v4l2_camera.cpp       | 28 ++++++++++++++++++++++++++--\n>  src/v4l2/v4l2_camera.h         |  7 ++++++-\n>  src/v4l2/v4l2_camera_proxy.cpp | 11 +++++++++--\n>  3 files changed, 41 insertions(+), 5 deletions(-)\n> \n> diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> index 177b1ea..99d34b9 100644\n> --- a/src/v4l2/v4l2_camera.cpp\n> +++ b/src/v4l2/v4l2_camera.cpp\n> @@ -18,7 +18,7 @@ LOG_DECLARE_CATEGORY(V4L2Compat);\n>  \n>  V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera)\n>  \t: camera_(camera), isRunning_(false), bufferAllocator_(nullptr),\n> -\t  efd_(-1)\n> +\t  efd_(-1), bufferAvailableCount_(0)\n>  {\n>  \tcamera_->requestCompleted.connect(this, &V4L2Camera::requestComplete);\n>  }\n> @@ -100,7 +100,9 @@ void V4L2Camera::requestComplete(Request *request)\n>  \tif (ret != sizeof(data))\n>  \t\tLOG(V4L2Compat, Error) << \"Failed to signal eventfd POLLIN\";\n>  \n> -\tbufferSema_.release();\n> +\tMutexLocker locker(bufferMutex_);\n> +\tbufferAvailableCount_++;\n> +\tbufferCV_.notify_all();\n\nCalling notify_all() with the lock held will hinder performances. See\nhttps://en.cppreference.com/w/cpp/thread/condition_variable/notify_all.\n\n\t{\n\t\tMutexLocker locker(bufferMutex_);\n\t\tbufferAvailableCount_++;\n\t}\n\tbufferCV_.notify_all();\n\n>  }\n>  \n>  int V4L2Camera::configure(StreamConfiguration *streamConfigOut,\n> @@ -144,6 +146,7 @@ int V4L2Camera::allocBuffers(unsigned int count)\n>  void V4L2Camera::freeBuffers()\n>  {\n>  \tStream *stream = *camera_->streams().begin();\n> +\n>  \tbufferAllocator_->free(stream);\n>  }\n>  \n> @@ -193,6 +196,7 @@ int V4L2Camera::streamOff()\n>  \t\treturn ret == -EACCES ? -EBUSY : ret;\n>  \n>  \tisRunning_ = false;\n> +\tbufferCV_.notify_all();\n\n\nIsn't this racy, can't the compiler (or the CPU) reorder these two lines\n? I think you need to lock access to isRunning_ with bufferMutex_. On\nthe other hand, if we want to support multi-threaded applications, we\nwill have to add locking to the V4L2 compat layer. This will likely be\ndone with a lock in V4L2CameraProxy, taken in V4L2CameraProxy::ioctl().\nisRunning_ would thus be protected by that lock, so we could skip taking\nthe bufferMutex_ here. I think I'd skip it for now, otherwise we would\nneed to use bufferMutex_ to lock other accesses to isRunning_, and it\nwould become messy.\n\nNow that I've written this, it seems being thread-safe would be quite\nsimple with the V4L2CameraProxy lock. You would just need to be careful\nto unlock it before calling V4L2Camera::waitForBufferAvailable(), and\nrelock it right after. Could you give it a try on top of this series ?\n\n>  \n>  \treturn 0;\n>  }\n> @@ -228,6 +232,26 @@ int V4L2Camera::qbuf(unsigned int index)\n>  \treturn 0;\n>  }\n>  \n> +void V4L2Camera::waitForBufferAvailable()\n> +{\n> +\tMutexLocker locker(bufferMutex_);\n> +\tbufferCV_.wait(locker, [&] {\n> +\t\t\treturn bufferAvailableCount_ >= 1 || !isRunning_;\n> +\t\t\t});\n> +\tif (isRunning_)\n> +\t\tbufferAvailableCount_--;\n> +}\n> +\n> +bool V4L2Camera::isBufferAvailable()\n> +{\n> +\tMutexLocker locker(bufferMutex_);\n> +\tif (bufferAvailableCount_ < 1)\n> +\t\treturn false;\n> +\n> +\tbufferAvailableCount_--;\n> +\treturn true;\n> +}\n> +\n>  bool V4L2Camera::isRunning()\n>  {\n>  \treturn isRunning_;\n> diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> index c157a80..515e906 100644\n> --- a/src/v4l2/v4l2_camera.h\n> +++ b/src/v4l2/v4l2_camera.h\n> @@ -57,7 +57,8 @@ public:\n>  \n>  \tint qbuf(unsigned int index);\n>  \n> -\tSemaphore bufferSema_;\n> +\tvoid waitForBufferAvailable();\n> +\tbool isBufferAvailable();\n>  \n>  \tbool isRunning();\n>  \n> @@ -76,6 +77,10 @@ private:\n>  \tstd::deque<std::unique_ptr<Buffer>> completedBuffers_;\n>  \n>  \tint efd_;\n> +\n> +\tMutex bufferMutex_;\n> +\tstd::condition_variable bufferCV_;\n> +\tunsigned int bufferAvailableCount_;\n>  };\n>  \n>  #endif /* __V4L2_CAMERA_H__ */\n> diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp\n> index ea9222e..2723450 100644\n> --- a/src/v4l2/v4l2_camera_proxy.cpp\n> +++ b/src/v4l2/v4l2_camera_proxy.cpp\n> @@ -595,10 +595,17 @@ int V4L2CameraProxy::vidioc_dqbuf(V4L2CameraFile *cf, struct v4l2_buffer *arg)\n>  \t\treturn -EINVAL;\n>  \n>  \tif (!(cf->nonBlocking()))\n> -\t\tvcam_->bufferSema_.acquire();\n> -\telse if (!vcam_->bufferSema_.tryAcquire())\n> +\t\tvcam_->waitForBufferAvailable();\n> +\telse if (!vcam_->isBufferAvailable())\n>  \t\treturn -EAGAIN;\n>  \n> +\t/*\n> +\t * We need to check here again in case stream was turned off while we\n> +\t * were blocked on dqbuf.\n\ns/dqbuf/waitForBufferAvailable()/ ?\n\nI'm not sure this is checking \"again\". Well, technically it is, but it's\nreally about checking which of the two conditions is true.\n\n> +\t */\n> +\tif (!vcam_->isRunning())\n> +\t\treturn -EINVAL;\n> +\n>  \tupdateBuffers();\n>  \n>  \tstruct v4l2_buffer &buf = buffers_[currentBuf_];","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0B1DA60710\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 20 Jun 2020 04:21:10 +0200 (CEST)","from pendragon.ideasonboard.com (81-175-216-236.bb.dnainternet.fi\n\t[81.175.216.236])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 43E75552;\n\tSat, 20 Jun 2020 04:21:09 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"SOafDzd0\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1592619669;\n\tbh=U3kWEQ2c8KQCoTx0mBD01M9sEbXMDauxsvYyKXWaDsk=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=SOafDzd0rNqGkLL/Kp01QQskB35hNhOE2f3ViQTKctNgdDChvxUxDKz86sU/Iltnz\n\toZO8D106PiCjzTCj3y4KYRxHj/CbDTOHp+KtI9q7nU8qFzIB1kkUR2nkHKY3Qij9Nl\n\tJAFb6nojH5jKrrs4nqquE2DrK6rJHYFC2MX+lN68=","Date":"Sat, 20 Jun 2020 05:20:45 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20200620022045.GY5823@pendragon.ideasonboard.com>","References":"<20200619054123.19052-1-paul.elder@ideasonboard.com>\n\t<20200619054123.19052-13-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20200619054123.19052-13-paul.elder@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v2 12/17] v4l2: v4l2_camera: Don't use\n\tlibcamera::Semaphore for available buffers","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Sat, 20 Jun 2020 02:21:10 -0000"}}]