Skip to content

Commit bd91551

Browse files
arkqpull[bot]
authored andcommitted
[Linux] Run functions on GLib event loop in a sync way (#25916)
* Run functions on GLib event loop in a sync way * Include invoke sync template when GLib is enabled * Restore workaround for TSAN false positives * Fix variable shadowing * Do not use g_main_context_invoke_full for waiting for loop to start The g_main_context_invoke_full() function checks whether the context, on which it's supposed to run callback function, is acquired. Otherwise, it runs given callback on the current thread. In our case this may lead to incorrect GLib signals bindings, so we have to use g_source_attach() when waiting for main loop. * Release context object after joining glib thread TSAN can report false-positive on context unref. By firstly joining thread and later unreferencing context we will prevent such warning.
1 parent b9109ea commit bd91551

File tree

5 files changed

+118
-116
lines changed

5 files changed

+118
-116
lines changed

src/platform/Linux/PlatformManagerImpl.cpp

+50-48
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,28 @@ CHIP_ERROR PlatformManagerImpl::_InitChipStack()
194194
mGLibMainLoopThread = g_thread_new("gmain-matter", GLibMainLoopThread, mGLibMainLoop);
195195

196196
{
197+
// Wait for the GLib main loop to start. It is required that the context used
198+
// by the main loop is acquired before any other GLib functions are called. Otherwise,
199+
// the GLibMatterContextInvokeSync() might run functions on the wrong thread.
200+
197201
std::unique_lock<std::mutex> lock(mGLibMainLoopCallbackIndirectionMutex);
198-
CallbackIndirection startedInd([](void *) { return G_SOURCE_REMOVE; }, nullptr);
199-
g_idle_add(G_SOURCE_FUNC(&CallbackIndirection::Callback), &startedInd);
200-
startedInd.Wait(lock);
202+
GLibMatterContextInvokeData invokeData{};
203+
204+
auto * idleSource = g_idle_source_new();
205+
g_source_set_callback(
206+
idleSource,
207+
[](void * userData_) {
208+
auto * data = reinterpret_cast<GLibMatterContextInvokeData *>(userData_);
209+
std::unique_lock<std::mutex> lock_(PlatformMgrImpl().mGLibMainLoopCallbackIndirectionMutex);
210+
data->mDone = true;
211+
data->mDoneCond.notify_one();
212+
return G_SOURCE_REMOVE;
213+
},
214+
&invokeData, nullptr);
215+
g_source_attach(idleSource, g_main_loop_get_context(mGLibMainLoop));
216+
g_source_unref(idleSource);
217+
218+
invokeData.mDoneCond.wait(lock, [&invokeData]() { return invokeData.mDone; });
201219
}
202220

203221
#endif
@@ -248,68 +266,52 @@ void PlatformManagerImpl::_Shutdown()
248266

249267
#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP
250268
g_main_loop_quit(mGLibMainLoop);
251-
g_main_loop_unref(mGLibMainLoop);
252269
g_thread_join(mGLibMainLoopThread);
270+
g_main_loop_unref(mGLibMainLoop);
253271
#endif
254272
}
255273

256274
#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP
257-
258-
void PlatformManagerImpl::CallbackIndirection::Wait(std::unique_lock<std::mutex> & lock)
259-
{
260-
mDoneCond.wait(lock, [this]() { return mDone; });
261-
}
262-
263-
gboolean PlatformManagerImpl::CallbackIndirection::Callback(CallbackIndirection * self)
275+
CHIP_ERROR PlatformManagerImpl::_GLibMatterContextInvokeSync(CHIP_ERROR (*func)(void *), void * userData)
264276
{
265-
// We can not access "self" before acquiring the lock, because TSAN will complain that
266-
// there is a race condition between the thread that created the object and the thread
267-
// that is executing the callback.
268-
std::unique_lock<std::mutex> lock(PlatformMgrImpl().mGLibMainLoopCallbackIndirectionMutex);
277+
// Because of TSAN false positives, we need to use a mutex to synchronize access to all members of
278+
// the GLibMatterContextInvokeData object (including constructor and destructor). This is a temporary
279+
// workaround until TSAN-enabled GLib will be used in our CI.
280+
std::unique_lock<std::mutex> lock(mGLibMainLoopCallbackIndirectionMutex);
269281

270-
auto callback = self->mCallback;
271-
auto userData = self->mUserData;
282+
GLibMatterContextInvokeData invokeData{ func, userData };
272283

273284
lock.unlock();
274-
auto result = callback(userData);
275-
lock.lock();
276285

277-
self->mDone = true;
278-
self->mDoneCond.notify_all();
286+
g_main_context_invoke_full(
287+
g_main_loop_get_context(mGLibMainLoop), G_PRIORITY_HIGH_IDLE,
288+
[](void * userData_) {
289+
auto * data = reinterpret_cast<GLibMatterContextInvokeData *>(userData_);
279290

280-
return result;
281-
}
291+
// XXX: Temporary workaround for TSAN false positives.
292+
std::unique_lock<std::mutex> lock_(PlatformMgrImpl().mGLibMainLoopCallbackIndirectionMutex);
282293

283-
#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
284-
CHIP_ERROR PlatformManagerImpl::RunOnGLibMainLoopThread(GSourceFunc callback, void * userData, bool wait)
285-
{
294+
auto mFunc = data->mFunc;
295+
auto mUserData = data->mFuncUserData;
286296

287-
GMainContext * context = g_main_loop_get_context(mGLibMainLoop);
288-
VerifyOrReturnError(context != nullptr, CHIP_ERROR_INTERNAL,
289-
ChipLogDetail(DeviceLayer, "Failed to get GLib main loop context"));
297+
lock_.unlock();
298+
auto result = mFunc(mUserData);
299+
lock_.lock();
290300

291-
// If we've been called from the GLib main loop thread itself, there is no reason to wait
292-
// for the callback, as it will be executed immediately by the g_main_context_invoke() call
293-
// below. Using a callback indirection in this case would cause a deadlock.
294-
if (g_main_context_is_owner(context))
295-
{
296-
wait = false;
297-
}
301+
data->mDone = true;
302+
data->mFuncResult = result;
303+
data->mDoneCond.notify_one();
298304

299-
if (wait)
300-
{
301-
std::unique_lock<std::mutex> lock(mGLibMainLoopCallbackIndirectionMutex);
302-
CallbackIndirection indirection(callback, userData);
303-
g_main_context_invoke(context, G_SOURCE_FUNC(&CallbackIndirection::Callback), &indirection);
304-
indirection.Wait(lock);
305-
return CHIP_NO_ERROR;
306-
}
305+
return G_SOURCE_REMOVE;
306+
},
307+
&invokeData, nullptr);
307308

308-
g_main_context_invoke(context, callback, userData);
309-
return CHIP_NO_ERROR;
310-
}
311-
#endif // CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
309+
lock.lock();
312310

311+
invokeData.mDoneCond.wait(lock, [&invokeData]() { return invokeData.mDone; });
312+
313+
return invokeData.mFuncResult;
314+
}
313315
#endif // CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP
314316

315317
} // namespace DeviceLayer

src/platform/Linux/PlatformManagerImpl.h

+24-24
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,22 @@ class PlatformManagerImpl final : public PlatformManager, public Internal::Gener
5454
public:
5555
// ===== Platform-specific members that may be accessed directly by the application.
5656

57-
#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP && CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
57+
#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP
5858

5959
/**
60-
* @brief Executes a callback in the GLib main loop thread.
60+
* @brief Invoke a function on the Matter GLib context.
6161
*
62-
* @param[in] callback The callback to execute.
63-
* @param[in] userData User data to pass to the callback.
64-
* @param[in] wait If true, the function will block until the callback has been executed.
65-
* @returns CHIP_NO_ERROR if the callback was successfully executed.
66-
*/
67-
CHIP_ERROR RunOnGLibMainLoopThread(GSourceFunc callback, void * userData, bool wait = false);
68-
69-
/**
70-
* @brief Convenience method to require less casts to void pointers.
62+
* If execution of the function will have to be scheduled on other thread,
63+
* this call will block the current thread until the function is executed.
64+
*
65+
* @param[in] function The function to call.
66+
* @param[in] userData User data to pass to the function.
67+
* @returns The result of the function.
7168
*/
72-
template <class T>
73-
CHIP_ERROR ScheduleOnGLibMainLoopThread(gboolean (*callback)(T *), T * userData, bool wait = false)
69+
template <typename T>
70+
CHIP_ERROR GLibMatterContextInvokeSync(CHIP_ERROR (*func)(T *), T * userData)
7471
{
75-
return RunOnGLibMainLoopThread(G_SOURCE_FUNC(callback), userData, wait);
72+
return _GLibMatterContextInvokeSync((CHIP_ERROR(*)(void *)) func, (void *) userData);
7673
}
7774

7875
#endif
@@ -97,21 +94,24 @@ class PlatformManagerImpl final : public PlatformManager, public Internal::Gener
9794

9895
#if CHIP_DEVICE_CONFIG_WITH_GLIB_MAIN_LOOP
9996

100-
class CallbackIndirection
97+
struct GLibMatterContextInvokeData
10198
{
102-
public:
103-
CallbackIndirection(GSourceFunc callback, void * userData) : mCallback(callback), mUserData(userData) {}
104-
void Wait(std::unique_lock<std::mutex> & lock);
105-
static gboolean Callback(CallbackIndirection * self);
106-
107-
private:
108-
GSourceFunc mCallback;
109-
void * mUserData;
110-
// Sync primitives to wait for the callback to be executed.
99+
CHIP_ERROR (*mFunc)(void *);
100+
void * mFuncUserData;
101+
CHIP_ERROR mFuncResult;
102+
// Sync primitives to wait for the function to be executed
111103
std::condition_variable mDoneCond;
112104
bool mDone = false;
113105
};
114106

107+
/**
108+
* @brief Invoke a function on the Matter GLib context.
109+
*
110+
* @note This function does not provide type safety for the user data. Please,
111+
* use the GLibMatterContextInvokeSync() template function instead.
112+
*/
113+
CHIP_ERROR _GLibMatterContextInvokeSync(CHIP_ERROR (*func)(void *), void * userData);
114+
115115
// XXX: Mutex for guarding access to glib main event loop callback indirection
116116
// synchronization primitives. This is a workaround to suppress TSAN warnings.
117117
// TSAN does not know that from the thread synchronization perspective the

src/platform/Linux/bluez/ChipDeviceScanner.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ CHIP_ERROR ChipDeviceScanner::StartScan(System::Clock::Timeout timeout)
123123
ReturnErrorCodeIf(mIsScanning, CHIP_ERROR_INCORRECT_STATE);
124124

125125
mIsScanning = true; // optimistic, to allow all callbacks to check this
126-
if (PlatformMgrImpl().ScheduleOnGLibMainLoopThread(MainLoopStartScan, this, true) != CHIP_NO_ERROR)
126+
if (PlatformMgrImpl().GLibMatterContextInvokeSync(MainLoopStartScan, this) != CHIP_NO_ERROR)
127127
{
128128
ChipLogError(Ble, "Failed to schedule BLE scan start.");
129129
mIsScanning = false;
@@ -174,7 +174,7 @@ CHIP_ERROR ChipDeviceScanner::StopScan()
174174
mInterfaceChangedSignal = 0;
175175
}
176176

177-
if (PlatformMgrImpl().ScheduleOnGLibMainLoopThread(MainLoopStopScan, this, true) != CHIP_NO_ERROR)
177+
if (PlatformMgrImpl().GLibMatterContextInvokeSync(MainLoopStopScan, this) != CHIP_NO_ERROR)
178178
{
179179
ChipLogError(Ble, "Failed to schedule BLE scan stop.");
180180
return CHIP_ERROR_INTERNAL;
@@ -183,7 +183,7 @@ CHIP_ERROR ChipDeviceScanner::StopScan()
183183
return CHIP_NO_ERROR;
184184
}
185185

186-
int ChipDeviceScanner::MainLoopStopScan(ChipDeviceScanner * self)
186+
CHIP_ERROR ChipDeviceScanner::MainLoopStopScan(ChipDeviceScanner * self)
187187
{
188188
GError * error = nullptr;
189189

@@ -199,7 +199,7 @@ int ChipDeviceScanner::MainLoopStopScan(ChipDeviceScanner * self)
199199
// references to 'self' here)
200200
delegate->OnScanComplete();
201201

202-
return 0;
202+
return CHIP_NO_ERROR;
203203
}
204204

205205
void ChipDeviceScanner::SignalObjectAdded(GDBusObjectManager * manager, GDBusObject * object, ChipDeviceScanner * self)
@@ -266,7 +266,7 @@ void ChipDeviceScanner::RemoveDevice(BluezDevice1 * device)
266266
}
267267
}
268268

269-
int ChipDeviceScanner::MainLoopStartScan(ChipDeviceScanner * self)
269+
CHIP_ERROR ChipDeviceScanner::MainLoopStartScan(ChipDeviceScanner * self)
270270
{
271271
GError * error = nullptr;
272272

@@ -308,7 +308,7 @@ int ChipDeviceScanner::MainLoopStartScan(ChipDeviceScanner * self)
308308
self->mDelegate->OnScanComplete();
309309
}
310310

311-
return 0;
311+
return CHIP_NO_ERROR;
312312
}
313313

314314
} // namespace Internal

src/platform/Linux/bluez/ChipDeviceScanner.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ class ChipDeviceScanner
7979

8080
private:
8181
static void TimerExpiredCallback(chip::System::Layer * layer, void * appState);
82-
static int MainLoopStartScan(ChipDeviceScanner * self);
83-
static int MainLoopStopScan(ChipDeviceScanner * self);
82+
static CHIP_ERROR MainLoopStartScan(ChipDeviceScanner * self);
83+
static CHIP_ERROR MainLoopStopScan(ChipDeviceScanner * self);
8484
static void SignalObjectAdded(GDBusObjectManager * manager, GDBusObject * object, ChipDeviceScanner * self);
8585
static void SignalInterfaceChanged(GDBusObjectManagerClient * manager, GDBusObjectProxy * object, GDBusProxy * aInterface,
8686
GVariant * aChangedProperties, const gchar * const * aInvalidatedProps,

0 commit comments

Comments
 (0)