[libcamera-devel] [PATCH v4 2/3] cam: event_loop: Execute events in the libevent loop

Niklas Söderlund niklas.soderlund at ragnatech.se
Fri Feb 5 10:14:40 CET 2021


Hi Laurent,

Thanks for your feedback.

On 2021-02-04 02:44:04 +0200, Laurent Pinchart wrote:
> Hi Niklas,
> 
> Thank you for the patch.
> 
> On Tue, Feb 02, 2021 at 11:10:50PM +0100, Niklas Söderlund wrote:
> > Cam uses libevent to deal with threads and idle loop while still
> > implementing its own event queue. This creates issues when the event
> > loop is terminated as it might get stuck in the idle loop if exit() is
> > called while the thread is busy with dispatchCalls().
> > 
> > Solve this my removing the custom event execution and instead injecting
> 
> s/my/by/
> 
> > the calls as events to the base event loop.
> > 
> > Reported-by: Sebastian Fricke <sebastian.fricke at posteo.net>
> > Signed-off-by: Niklas Söderlund <niklas.soderlund at ragnatech.se>
> > ---
> >  src/cam/event_loop.cpp | 43 ++++++++++++++++++++----------------------
> >  src/cam/event_loop.h   |  6 +-----
> >  2 files changed, 21 insertions(+), 28 deletions(-)
> > 
> > diff --git a/src/cam/event_loop.cpp b/src/cam/event_loop.cpp
> > index 3a2b665abdc063de..e84e437f1f8ff6d7 100644
> > --- a/src/cam/event_loop.cpp
> > +++ b/src/cam/event_loop.cpp
> > @@ -13,6 +13,13 @@
> >  
> >  EventLoop *EventLoop::instance_ = nullptr;
> >  
> > +static void dispatchCallback([[maybe_unused]] evutil_socket_t fd,
> > +			     [[maybe_unused]] short flags, void *param)
> > +{
> > +	EventLoop *loop = static_cast<EventLoop *>(param);
> > +	loop->dispatchCall();
> > +}
> 
> This could be a private static function of the EventLoop class, which
> would allow EventLoop::dispatchCall() to become a private function. The
> event_loop.h header will have an issue with the undefined
> evutil_socket_t type, which could be solved by either including
> event2/util.h, or using the int type instead (evutil_socket_t is used to
> abstract platforms, I really doubt libevent will switch to another type
> than int on Linux).

That is a very good idea! I will include event2/util.h as I think the 
correct type really should be used.

> 
> > +
> >  EventLoop::EventLoop()
> >  {
> >  	assert(!instance_);
> > @@ -38,25 +45,13 @@ EventLoop *EventLoop::instance()
> >  int EventLoop::exec()
> >  {
> >  	exitCode_ = -1;
> > -	exit_.store(false, std::memory_order_release);
> > -
> > -	while (!exit_.load(std::memory_order_acquire)) {
> > -		dispatchCalls();
> > -		event_base_loop(base_, EVLOOP_NO_EXIT_ON_EMPTY);
> > -	}
> > -
> > +	event_base_loop(base_, EVLOOP_NO_EXIT_ON_EMPTY);
> >  	return exitCode_;
> >  }
> >  
> >  void EventLoop::exit(int code)
> >  {
> >  	exitCode_ = code;
> > -	exit_.store(true, std::memory_order_release);
> > -	interrupt();
> > -}
> > -
> > -void EventLoop::interrupt()
> > -{
> >  	event_base_loopbreak(base_);
> >  }
> >  
> > @@ -67,20 +62,22 @@ void EventLoop::callLater(const std::function<void()> &func)
> >  		calls_.push_back(func);
> >  	}
> >  
> > -	interrupt();
> > +	struct event *event = event_new(base_, -1, 0, dispatchCallback, this);
> > +	event_active(event, 0, 0);
> 
> I believe the memory allocated by event_new() is leaked. valgrind seems
> to agree with me :-) The following may fix it:
> 
> 	event_base_once(base_, -1, EV_TIMEOUT, dispatchCallback, this, NULL);

Thanks!

> 
> We could go one step further and drop the calls_ list, with the
> following:
> 
> void EventLoop::callLater(const std::function<void()> &func)
> {
> 	event_base_once(base_, -1, EV_TIMEOUT, EventLoop::dispatchCallback,
> 			new std::function<void()>(func), NULL);
> }
> 
> void EventLoop::dispatchCallback([[maybe_unused]] int fd,
> 				 [[maybe_unused]] short flags, void *param)
> {
> 	std::function<void()> *call = static_cast<std::function<void()> *>(param);
> 	(*call)();
> 	delete call;
> }
> 
> There's one issue though, as libevent (at least starting with version
> 2.1) guarantees that the event allocated internally by event_base_once()
> will always be freed even if the event is never triggered (I believe
> this can happen if event_base_loopbreak() races with event_base_once(),
> but that may actually not be the case), but we would in that case leak
> the memory allocated for the argument to the callback. The
> event_base_once() explains this, and states that applications are on
> their own to free this memory. The simplest way to do so would be to
> store the call in a calls_ list, so we would be back to where we are
> today.
> 
> Without support in libevent to handle this issue, I don't think there's
> much we could do to reduce the amonut of code we have here, but I may
> have missed something.

I tried something similar before posting this, and as you point out I 
could not find a good way to handle events still queued after the loop 
is stopped. I think I will keep this as is for now and if we learn more 
of libevent we could switch to it later.

> 
> >  }
> >  
> > -void EventLoop::dispatchCalls()
> > +void EventLoop::dispatchCall()
> >  {
> > -	std::unique_lock<std::mutex> locker(lock_);
> > +	std::function<void()> call;
> >  
> > -	for (auto iter = calls_.begin(); iter != calls_.end(); ) {
> > -		std::function<void()> call = std::move(*iter);
> > +	{
> > +		std::unique_lock<std::mutex> locker(lock_);
> > +		if (calls_.empty())
> > +			return;
> >  
> > -		iter = calls_.erase(iter);
> > -
> > -		locker.unlock();
> > -		call();
> > -		locker.lock();
> > +		call = calls_.front();
> > +		calls_.pop_front();
> >  	}
> > +
> > +	call();
> 
> We may be reducing the efficiency a little bit by queuing one event to
> libevent for each call to dispatch, but I think that's fine. cam's goal
> isn't to optimize event handling, relying on features provided by
> libevent as much as possible to keep the code simple is best.

Agreed.

> 
> The part that bothers me a bit with this patch is that I half recall
> doing something similar when switching to libevent, and running into
> issues. As that memory could very well be incorrect, and as this patch
> seems fine,

Yea this seems too simple, but I can't think how it can blow up at a 
moment. Oh well for future me to discover and hopefully solve ;-)

> 
> Reviewed-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> 
> with the above issues addressed.
> 
> >  }
> > diff --git a/src/cam/event_loop.h b/src/cam/event_loop.h
> > index d0d5b5a53c830670..5f1cd88c2f5c830e 100644
> > --- a/src/cam/event_loop.h
> > +++ b/src/cam/event_loop.h
> > @@ -7,7 +7,6 @@
> >  #ifndef __CAM_EVENT_LOOP_H__
> >  #define __CAM_EVENT_LOOP_H__
> >  
> > -#include <atomic>
> >  #include <functional>
> >  #include <list>
> >  #include <mutex>
> > @@ -26,19 +25,16 @@ public:
> >  	void exit(int code = 0);
> >  
> >  	void callLater(const std::function<void()> &func);
> > +	void dispatchCall();
> >  
> >  private:
> >  	static EventLoop *instance_;
> >  
> >  	struct event_base *base_;
> > -	std::atomic<bool> exit_;
> >  	int exitCode_;
> >  
> >  	std::list<std::function<void()>> calls_;
> >  	std::mutex lock_;
> > -
> > -	void interrupt();
> > -	void dispatchCalls();
> >  };
> >  
> >  #endif /* __CAM_EVENT_LOOP_H__ */
> 
> -- 
> Regards,
> 
> Laurent Pinchart

-- 
Regards,
Niklas Söderlund


More information about the libcamera-devel mailing list