Skip to content

Commit 36c96d8

Browse files
authored
Removing mutex from Track hot-path (#261)
* Reimplement Track with incredibly fine grane Signed-off-by: jparisu <javierparis@eprosima.com> * Add documentation forthcoming Signed-off-by: jparisu <javierparis@eprosima.com> * apply suggestion Signed-off-by: jparisu <javierparis@eprosima.com> * uncrustify Signed-off-by: jparisu <javierparis@eprosima.com> Signed-off-by: jparisu <javierparis@eprosima.com>
1 parent 87fe006 commit 36c96d8

3 files changed

Lines changed: 41 additions & 80 deletions

File tree

ddsrouter_core/src/cpp/communication/Track.cpp

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ void Track::disable() noexcept
135135

136136
bool Track::should_transmit_() noexcept
137137
{
138-
return !exit_ && enabled_ && this->is_data_available_();
138+
return !exit_ && enabled_;
139139
}
140140

141141
void Track::data_available_() noexcept
@@ -145,27 +145,19 @@ void Track::data_available_() noexcept
145145
{
146146
logDebug(DDSROUTER_TRACK, "Track " << *this << " has data ready to be sent.");
147147

148-
// Lock data_available_status_mutex_ to avoid changing the status while it is being checked
149-
std::lock_guard<std::mutex> lock(data_available_status_mutex_);
148+
// Get previous status and set current one to >=2 (it it was already >=2 it will keep being >2)
149+
unsigned int previous_status = data_available_status_.fetch_add(DataAvailableStatus::new_data_arrived);
150150

151-
// This method will always be called from the Reader thread, so it is safe to set the status
152-
DataAvailableStatus current_status = data_available_status_.exchange(DataAvailableStatus::new_data_arrived);
153-
154-
if (current_status == DataAvailableStatus::no_more_data)
151+
if (previous_status == DataAvailableStatus::no_more_data)
155152
{
156-
// Only send the callback to thread pool if it was not running
153+
// no_more_data was set as current status, so no thread was running
154+
// (and will not start as 2 is set as new current status)
157155
thread_pool_->emit(transmit_task_id_);
158156
logDebug(DDSROUTER_TRACK, "Track " << *this << " send callback to queue.");
159157
}
160158
}
161159
}
162160

163-
bool Track::is_data_available_() const noexcept
164-
{
165-
return data_available_status_ == DataAvailableStatus::new_data_arrived ||
166-
data_available_status_ == DataAvailableStatus::transmitting_data;
167-
}
168-
169161
void Track::transmit_() noexcept
170162
{
171163
// Loop that ends if it should stop transmitting (should_transmit_nts_).
@@ -180,29 +172,29 @@ void Track::transmit_() noexcept
180172
while (should_transmit_())
181173
{
182174
// It starts transmitting, so it sets the data available status as transmitting
183-
data_available_status_ = DataAvailableStatus::transmitting_data;
175+
// This will erase every previous value added in on_data_available and set 1
176+
data_available_status_.store(DataAvailableStatus::transmitting_data);
184177

185178
// Get data received
186179
std::unique_ptr<DataReceived> data = std::make_unique<DataReceived>();
187180
utils::ReturnCode ret = reader_->take(data);
188181

189182
if (ret == utils::ReturnCode::RETCODE_NO_DATA)
190183
{
191-
// Lock data_available_status_mutex_ to avoid changing the status while it is being checked
192-
std::lock_guard<std::mutex> lock(data_available_status_mutex_);
193-
194-
// There is no more data, so finish loop and wait again for new data
195-
DataAvailableStatus current_status = data_available_status_.exchange(DataAvailableStatus::no_more_data);
196-
if (current_status == DataAvailableStatus::new_data_arrived)
184+
// There is no more data, so reduce in 1 the status
185+
unsigned int previous_status = data_available_status_.fetch_sub(DataAvailableStatus::transmitting_data);
186+
if (previous_status == DataAvailableStatus::transmitting_data)
197187
{
198-
// New data has arrived while setting no_more_data, so it should continues
199-
data_available_status_.store(DataAvailableStatus::transmitting_data);
200-
continue;
188+
// Previous Status = 1 (transmitting => no on_data_available callback has been called)
189+
// Current Status = 0 (new callbacks will emit the task)
190+
// => close this thread and keeps status as 0 so new data available emit task
191+
break;
201192
}
202193
else
203194
{
204-
// no_more_data has been set, so if any other data arrives it will send a callback to thread pool
205-
break;
195+
// New data has arrived while setting no_more_data, so it should continue
196+
// While setting status to 1 again, the value is still >=1 so no other thread will start
197+
continue;
206198
}
207199
}
208200
else if (!ret)
@@ -237,8 +229,6 @@ void Track::transmit_() noexcept
237229

238230
payload_pool_->release_payload(data->payload);
239231
}
240-
241-
data_available_status_.store(DataAvailableStatus::no_more_data);
242232
}
243233

244234
std::ostream& operator <<(

ddsrouter_core/src/cpp/communication/Track.hpp

Lines changed: 19 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -102,75 +102,44 @@ class Track
102102
* WORKAROUND:
103103
* A problem has been found in the use of Track within FastDDS Readers:
104104
* the on_data_available callback is called with the Reader mutex taken, so it may occur a deadlock while
105-
* reading a data and receiving it at the same time from different threads, and this is a scenario that
106-
* could happen with this design.
105+
* reading a data and receiving it at the same time from different threads if on_data_available and read
106+
* methods share a mutex.
107107
*
108108
* In order to avoid this deadlock, there is a DataAvailableStatus enumeration setting the status
109109
* of the data taking into account the Listener(listen) update and the Track(read) update.
110+
* This enumeration works as numbers and not as enumeration (could be seen as a collection of constexpr)
110111
*
111-
* The main point is to not have any mutex taken while take method is called in the Reader, but a mutex could
112-
* be used to guard the access to the actual Track data available status.
112+
* The main point is to not have to tak any mutex in on_data_available neither in read.
113113
*/
114114
//! Status of the data available in the Track's Reader
115-
enum class DataAvailableStatus
115+
enum DataAvailableStatus
116116
{
117-
new_data_arrived, //! Listener has announced that new data has arrived
118-
transmitting_data, //! Track is taking data from the Reader, so it could or could not be data
119-
no_more_data, //! Track has announced that Reader has no more data, and Listener has not notified new data
117+
no_more_data = 0, //! Track has announced that Reader has no more data
118+
transmitting_data = 1, //! Track is taking data from the Reader, so it could or could not be data
119+
new_data_arrived = 2 /* >2 */, //! Listener has announced that new data has arrived
120120
};
121121

122122
/**
123123
* Callback that will be called by the reader in case there is available data to be forwarded.
124124
*
125-
* This method is sent to the Reader so it could call it when there is new data.
125+
* This method is registered in the Reader so it could call it when there is new data.
126126
*
127-
* This method will set the variable \c data_available_status_ to \c new_data_arrived and awake the transmit thread.
128-
* If Track is disabled, the callback will be lost.
127+
* This method will add the variable \c data_available_status_ in \c new_data_arrived .
128+
* It will emit a task to execute transmit in a different thread if there was no previous thread before.
129129
*/
130130
void data_available_() noexcept;
131131

132132
/**
133-
* @brief Whether there is data waiting to be taken in the Reader
134-
*
135-
* The times there is data is when \c data_available_status_ is set as \c new_data_arrived or \c transmitting_data
136-
*
137-
* @return true if there is available data
138-
* @return false otherwise
139-
*/
140-
bool is_data_available_() const noexcept;
141-
142-
/**
143-
* Callback that will be called when there is no more data available to be forwarded.
144-
*
145-
* @note: this method is called from the Track after receiving a NO_DATA from Reader. But during the time to
146-
* set \c data_available_status_ the Listener could notify new data (it is not possible to guard this
147-
* behaviour as no shared mutex could be locked in transmit and listen because of FastDDS Reader mutex taken
148-
* while \c on_data_available callback). If this happens, it should not be set as NO_DATA, but as new data.
149-
* If this happens, the transmit thread will stop transmit loop, it will arrive to wait and it will automatically
150-
* exit it as there is actual data to be sent, so there is no case where it gets stopped with new data available.
151-
*/
152-
void no_more_data_available_() noexcept;
153-
154-
/**
155-
* Whether this Track is enabled
133+
* Whether this Track is enabled and should not exit.
156134
*
157135
* This method does not lock a mutex as it only acces atomic values to read them.
158136
*/
159137
bool should_transmit_() noexcept;
160138

161-
/**
162-
* Main function of Track.
163-
* It waits for data to be available
164-
* Once there is data available, call \c transmit_ till there is no data to be forwarded, and turn back to read
165-
*
166-
* Transmission is not executed in case track must be terminated or is not enabled.
167-
*/
168-
void transmit_thread_function_() noexcept;
169-
170139
/**
171140
* Take data from the Reader \c source and send this data through every writer in \c targets .
172141
*
173-
* When no more data is available, call \c no_more_data_available_ and exit.
142+
* When no more data is available, set \c data_available_status_ as \c no_more_data .
174143
*
175144
* It could exit without having finished transmitting all the data if track should terminate or track becomes
176145
* disabled.
@@ -226,18 +195,16 @@ class Track
226195
* Current status of the data available
227196
*
228197
* There are 3 states:
229-
* \c DataAvailableStatus::new_data_arrived : Reader Listener has notified that there are new data
230-
* \c DataAvailableStatus::transmitting_data : Track is currently taking data, so there may or may not be data available
231-
* \c DataAvailableStatus::no_more_data : Track has received a NO_DATA from Reader
198+
* \c 0 no_more_data : Track has received a NO_DATA from Reader and no data has been set from on_data_available
199+
* \c 1 transmitting_data : Track is currently taking data, so there may or may not be data available
200+
* \c >1 new_data_arrived : Reader Listener has notified that there are new data
232201
*
233-
* This variable is protected by \c data_available_mutex_
202+
* This variable does not need to be protected as it is atomic.
234203
*/
235-
std::atomic<DataAvailableStatus> data_available_status_;
236-
237-
std::mutex data_available_status_mutex_;
204+
std::atomic<unsigned int> data_available_status_;
238205

239206
/**
240-
* Mutex to guard while the Track is sending a message.
207+
* Mutex to guard while the Track is sending a message so it could not be disabled.
241208
*/
242209
std::mutex on_transmission_mutex_;
243210

docs/rst/notes/forthcoming_version.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ Next release will include the following **major changes**:
2727

2828
* ``wan`` Participant Kind uses now Initial Peers Discovery Protocol, while Discovery Server
2929
requires a new Participant Kind ``wan-discovery-server``.
30+
31+
Next release will include the following **performance improvements**:
32+
33+
* No locking in the :code:`Track` hot-path.

0 commit comments

Comments
 (0)