refactor: simplify async D-Bus connection handling

This commit is contained in:
Stanislav Angelovic
2024-04-24 20:20:29 +02:00
committed by Stanislav Angelovič
parent 7450515d0b
commit 3e20fc639e
24 changed files with 544 additions and 441 deletions
+90 -83
View File
@@ -51,62 +51,7 @@ namespace sdbus {
class IConnection
{
public:
/*!
* Poll Data for external event loop implementations.
*
* To integrate sdbus with your app's own custom event handling system
* you can use this method to query which file descriptors, poll events
* and timeouts you should add to your app's poll(2), or select(2)
* call in your main event loop.
*
* If you are unsure what this all means then use
* enterEventLoop() or enterEventLoopAsync() instead.
*
* See: getEventLoopPollData()
*/
struct PollData
{
/*!
* The read fd to be monitored by the event loop.
*/
int fd;
/*!
* The events to use for poll(2) alongside fd.
*/
short int events;
/*!
* Absolute timeout value in micro seconds and based of CLOCK_MONOTONIC.
*/
uint64_t timeout_usec;
/*!
* Get the event poll timeout.
*
* The timeout is an absolute value based of CLOCK_MONOTONIC.
*
* @return a duration since the CLOCK_MONOTONIC epoch started.
*/
[[nodiscard]] std::chrono::microseconds getAbsoluteTimeout() const
{
return std::chrono::microseconds(timeout_usec);
}
/*!
* Get the timeout as relative value from now
*
* @return std::nullopt if the timeout is indefinite. A duration otherwise.
*/
[[nodiscard]] std::optional<std::chrono::microseconds> getRelativeTimeout() const;
/*!
* Get a converted, relative timeout which can be passed as argument 'timeout' to poll(2)
*
* @return -1 if the timeout is indefinite. 0 if the poll(2) shouldn't block. An integer in milli
* seconds otherwise.
*/
[[nodiscard]] int getPollTimeout() const;
};
struct PollData;
virtual ~IConnection() = default;
@@ -129,7 +74,7 @@ namespace sdbus {
virtual void releaseName(const std::string& name) = 0;
/*!
* @brief Retrieve the unique name of a connection. E.g. ":1.xx"
* @brief Retrieves the unique name of a connection. E.g. ":1.xx"
*
* @throws sdbus::Error in case of failure
*/
@@ -180,44 +125,57 @@ namespace sdbus {
[[deprecated("Use one of other addObjectManager overloads")]] virtual void addObjectManager(const std::string& objectPath) = 0;
/*!
* @brief Returns fd, I/O events and timeout data you can pass to poll
* @brief Returns fd's, I/O events and timeout data to be used in an external event loop
*
* To integrate sdbus with your app's own custom event handling system
* (without the requirement of an extra thread), you can use this
* method to query which file descriptors, poll events and timeouts you
* should add to your app's poll call in your main event loop. If these
* file descriptors signal, then you should call processPendingRequest
* to process the event. This means that all of sdbus's callbacks will
* arrive on your app's main event thread (opposed to on a thread created
* by sdbus-c++). If you are unsure what this all means then use
* enterEventLoop() or enterEventLoopAsync() instead.
* This function is useful to hook up a bus connection object with an
* external (like GMainLoop, boost::asio, etc.) or manual event loop
* involving poll() or a similar I/O polling call.
*
* To integrate sdbus-c++ into a gtk app, pass the file descriptor returned
* by this method to g_main_context_add_poll.
* Before **each** invocation of the I/O polling call, this function
* should be invoked. Returned PollData::fd file descriptor should
* be polled for the events indicated by PollData::events, and the I/O
* call should block for that up to the returned PollData::timeout.
*
* Additionally, returned PollData::eventFd should be polled for POLLIN
* events.
*
* After each I/O polling call the bus connection needs to process
* incoming or outgoing data, by invoking processPendingEvent().
*
* Note that the returned timeout should be considered only a maximum
* sleeping time. It is permissible (and even expected) that shorter
* timeouts are used by the calling program, in case other event sources
* are polled in the same event loop. Note that the returned time-value
* is absolute, based of CLOCK_MONOTONIC and specified in microseconds.
* Use PollData::getPollTimeout() to have the timeout value converted
* in a form that can be passed to poll(2).
*
* @throws sdbus::Error in case of failure
*/
virtual PollData getEventLoopPollData() const = 0;
[[nodiscard]] virtual PollData getEventLoopPollData() const = 0;
/*!
* @brief Process a pending request
* @brief Processes a pending event
*
* @returns true if an event was processed, false if poll should be called
* @returns True if an event was processed, false if no operations were pending
*
* Processes a single dbus event. All of sdbus-c++'s callbacks will be called
* from within this method. This method should ONLY be used in conjuction
* with getEventLoopPollData().
* This method returns true if an I/O message was processed. This you can try
* to call this method again before going to poll on I/O events. The method
* returns false if no operations were pending, and the caller should then
* poll for I/O events before calling this method again.
* enterEventLoop() and enterEventLoopAsync() will call this method for you,
* so there is no need to call it when using these. If you are unsure what
* this all means then don't use this method.
* This function drives the D-Bus connection. It processes pending I/O events.
* Queued outgoing messages (or parts thereof) are sent out. Queued incoming
* messages are dispatched to registered callbacks. Timeouts are recalculated.
*
* It returns false when no operations were pending and true if a message was
* processed. When false is returned the caller should synchronously poll for
* I/O events before calling into processPendingEvent() again.
* Don't forget to call getEventLoopPollData() each time before the next poll.
*
* You don't need to directly call this method or getEventLoopPollData() method
* when using convenient, internal bus connection event loops through
* enterEventLoop() or enterEventLoopAsync() calls.
* It is invoked automatically when necessary.
*
* @throws sdbus::Error in case of failure
*/
virtual bool processPendingRequest() = 0;
virtual bool processPendingEvent() = 0;
/*!
* @brief Sets general method call timeout
@@ -373,6 +331,55 @@ namespace sdbus {
* @deprecated This function has been replaced by getEventLoopPollData()
*/
[[deprecated("This function has been replaced by getEventLoopPollData()")]] PollData getProcessLoopPollData() const;
/*!
* @struct PollData
*
* Carries poll data needed for integration with external event loop implementations.
*
* See getEventLoopPollData() for more info.
*/
struct PollData
{
/*!
* The read fd to be monitored by the event loop.
*/
int fd;
/*!
* The events to use for poll(2) alongside fd.
*/
short int events;
/*!
* Absolute timeout value in microseconds, based of CLOCK_MONOTONIC.
*
* Call getPollTimeout() to get timeout recalculated to relative timeout that can be passed to poll(2).
*/
std::chrono::microseconds timeout;
/*!
* An additional event fd to be monitored by the event loop for POLLIN events.
*/
int eventFd;
/*!
* Returns the timeout as relative value from now.
*
* Returned value is std::chrono::microseconds::max() if the timeout is indefinite.
*
* @return Relative timeout as a time duration
*/
[[nodiscard]] std::chrono::microseconds getRelativeTimeout() const;
/*!
* Returns relative timeout in the form which can be passed as argument 'timeout' to poll(2)
*
* @return -1 if the timeout is indefinite. 0 if the poll(2) shouldn't block.
* An integer in milliseconds otherwise.
*/
[[nodiscard]] int getPollTimeout() const;
};
};
template <typename _Rep, typename _Period>
+19 -10
View File
@@ -83,20 +83,29 @@ namespace sdbus {
virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Calls method on the D-Bus object
* @brief Calls method on the remote D-Bus object
*
* @param[in] message Message representing a method call
* @param[in] timeout Timeout for dbus call in microseconds
* @return A method reply message
*
* Normally, the call is blocking, i.e. it waits for the remote method to finish with either
* a return value or an error.
* The call does not block if the method call has dont-expect-reply flag set. In that case,
* the call returns immediately and the return value is an empty, invalid method reply.
*
* If the method call argument is set to not expect reply, the call will not wait for the remote
* method to finish, i.e. the call will be non-blocking, and the function will return an empty,
* invalid MethodReply object (representing void).
* The call blocks otherwise, waiting for the remote peer to send back a reply, or an error,
* or until the call times out.
*
* Note: To avoid messing with messages, use higher-level API defined below.
* While blocking, other concurrent operations (in other threads) on the underlying bus
* connection are stalled until the call returns. This is not an issue in vast majority of
* (simple, single-threaded) applications. In asynchronous, multi-threaded designs involving
* shared bus connections, this may be an issue. It is advised to instead use an asynchronous
* callMethod() function overload, which does not block the bus connection, or do the synchronous
* call from another Proxy instance created just before the call and then destroyed (which is
* anyway quite a typical approach in D-Bus implementations). Such proxy instance must have
* its own bus connection. Slim proxies created with `dont_run_event_loop_thread` tag are
* designed for exactly that purpose.
*
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
*
* @throws sdbus::Error in case of failure
*/
@@ -117,10 +126,10 @@ namespace sdbus {
* @return Cookie for the the pending asynchronous call
*
* The call is non-blocking. It doesn't wait for the reply. Once the reply arrives,
* the provided async reply handler will get invoked from the context of the connection
* I/O event loop thread.
* the provided async reply handler will get invoked from the context of the bus
* connection I/O event loop thread.
*
* Note: To avoid messing with messages, use higher-level API defined below.
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
*
* @throws sdbus::Error in case of failure
*/
+1 -3
View File
@@ -56,7 +56,6 @@ namespace sdbus {
class MethodReply;
namespace internal {
class ISdBus;
class IConnection;
}
}
@@ -258,12 +257,11 @@ namespace sdbus {
bool doesntExpectReply() const;
protected:
MethodCall(void *msg, internal::ISdBus* sdbus, const internal::IConnection* connection, adopt_message_t) noexcept;
MethodCall(void *msg, internal::ISdBus* sdbus, adopt_message_t) noexcept;
private:
MethodReply sendWithReply(uint64_t timeout = 0) const;
MethodReply sendWithNoReply() const;
const internal::IConnection* connection_{};
};
class MethodReply : public Message