diff --git a/include/libcamera/internal/tracepoints/request.tp b/include/libcamera/internal/tracepoints/request.tp
index 9e872951374d..9c841b97438a 100644
--- a/include/libcamera/internal/tracepoints/request.tp
+++ b/include/libcamera/internal/tracepoints/request.tp
@@ -66,6 +66,14 @@ TRACEPOINT_EVENT_INSTANCE(
 	)
 )
 
+TRACEPOINT_EVENT_INSTANCE(
+	libcamera,
+	request,
+	request_cancel,
+	TP_ARGS(
+		libcamera::Request *, req
+	)
+)
 
 TRACEPOINT_EVENT(
 	libcamera,
diff --git a/include/libcamera/request.h b/include/libcamera/request.h
index 4cf5ff3f7d3b..5596901ddd8e 100644
--- a/include/libcamera/request.h
+++ b/include/libcamera/request.h
@@ -65,6 +65,7 @@ private:
 	friend class PipelineHandler;
 
 	void complete();
+	void cancel();
 
 	bool completeBuffer(FrameBuffer *buffer);
 
diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
index ce2dd7b17f10..6611e74d1800 100644
--- a/src/libcamera/request.cpp
+++ b/src/libcamera/request.cpp
@@ -292,6 +292,29 @@ void Request::complete()
 	LIBCAMERA_TRACEPOINT(request_complete, this);
 }
 
+/**
+ * \brief Cancel a queued request
+ *
+ * Mark the request and its associated buffers as cancelled and complete it.
+ *
+ * Set each pending buffer in error state and emit the buffer completion signal
+ * before completing the Request.
+ */
+void Request::cancel()
+{
+	LIBCAMERA_TRACEPOINT(request_cancel, this);
+
+	ASSERT(status_ == RequestPending);
+
+	for (FrameBuffer *buffer : pending_) {
+		buffer->cancel();
+		camera_->bufferCompleted.emit(this, buffer);
+	}
+
+	pending_.clear();
+	cancelled_ = true;
+}
+
 /**
  * \brief Complete a buffer for the request
  * \param[in] buffer The buffer that has completed
