From 2a0673f99f81dbc91750fba1ee669bd037c59363 Mon Sep 17 00:00:00 2001 From: stffabi Date: Tue, 31 May 2022 12:28:37 +0200 Subject: [PATCH] [v2, linux] Improve switching to main thread for callbacks (#1392) Make sure no pointers to the stack are passed to g_idle_add, because at the time the callback gets executed on the main thread, the pointer might be invalid. Go might have reused the stack or grown the stack and the pointer is invalid. The concept used on Windows has been ported to Linux. --- v2/internal/frontend/desktop/linux/invoke.go | 78 ++++++++++++++++++++ v2/internal/frontend/desktop/linux/window.go | 23 ++---- 2 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 v2/internal/frontend/desktop/linux/invoke.go diff --git a/v2/internal/frontend/desktop/linux/invoke.go b/v2/internal/frontend/desktop/linux/invoke.go new file mode 100644 index 000000000..16d5e73d2 --- /dev/null +++ b/v2/internal/frontend/desktop/linux/invoke.go @@ -0,0 +1,78 @@ +//go:build linux +// +build linux + +package linux + +/* +#cgo linux pkg-config: gtk+-3.0 + +#include +#include "gtk/gtk.h" + +extern gboolean invokeCallbacks(void *); + +static inline void triggerInvokesOnMainThread() { + g_idle_add((GSourceFunc)invokeCallbacks, NULL); +} +*/ +import "C" +import ( + "runtime" + "sync" + "unsafe" + + "golang.org/x/sys/unix" +) + +var ( + m sync.Mutex + mainTid int + dispatchq []func() +) + +func invokeOnMainThread(f func()) { + if tryInvokeOnCurrentGoRoutine(f) { + return + } + + m.Lock() + dispatchq = append(dispatchq, f) + m.Unlock() + + C.triggerInvokesOnMainThread() +} + +func tryInvokeOnCurrentGoRoutine(f func()) bool { + m.Lock() + mainThreadID := mainTid + m.Unlock() + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if mainThreadID != unix.Gettid() { + return false + } + f() + return true +} + +//export invokeCallbacks +func invokeCallbacks(_ unsafe.Pointer) C.gboolean { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + m.Lock() + if mainTid == 0 { + mainTid = unix.Gettid() + } + + q := append([]func(){}, dispatchq...) + dispatchq = []func(){} + m.Unlock() + + for _, v := range q { + v() + } + return C.G_SOURCE_REMOVE +} diff --git a/v2/internal/frontend/desktop/linux/window.go b/v2/internal/frontend/desktop/linux/window.go index 693c0887c..47dc8233f 100644 --- a/v2/internal/frontend/desktop/linux/window.go +++ b/v2/internal/frontend/desktop/linux/window.go @@ -249,11 +249,10 @@ typedef struct JSCallback { char* script; } JSCallback; -gboolean executeJS(gpointer data) { +void executeJS(void *data) { struct JSCallback *js = data; webkit_web_view_run_javascript(js->webview, js->script, NULL, NULL, NULL); free(js->script); - return G_SOURCE_REMOVE; } void extern processMessageDialogResult(char*); @@ -265,8 +264,7 @@ typedef struct MessageDialogOptions { int messageType; } MessageDialogOptions; -gboolean messageDialog(gpointer data) { - +void messageDialog(void *data) { GtkDialogFlags flags; GtkMessageType messageType; MessageDialogOptions *options = (MessageDialogOptions*) data; @@ -307,8 +305,6 @@ gboolean messageDialog(gpointer data) { gtk_widget_destroy(dialog); free(options->title); free(options->message); - - return G_SOURCE_REMOVE; } void extern processOpenFileResult(void*); @@ -333,7 +329,7 @@ void freeFileFilterArray(GtkFileFilter** filters) { free(filters); } -gboolean opendialog(gpointer data) { +void opendialog(void *data) { struct OpenFileDialogOptions *options = data; char *label = "_Open"; if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE) { @@ -421,7 +417,6 @@ gboolean opendialog(gpointer data) { } gtk_widget_destroy(dlgWidget); free(options->title); - return G_SOURCE_REMOVE; } GtkFileFilter* newFileFilter() { @@ -438,12 +433,10 @@ typedef struct RGBAOptions { void *webview; } RGBAOptions; -gboolean setRGBA(gpointer* data) { +void setRGBA(void* data) { RGBAOptions* options = (RGBAOptions*)data; GdkRGBA colour = {options->r / 255.0, options->g / 255.0, options->b / 255.0, options->a / 255.0}; webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(options->webview), &colour); - - return G_SOURCE_REMOVE; } typedef struct SetTitleArgs { @@ -781,7 +774,7 @@ func (w *Window) SetRGBA(r uint8, g uint8, b uint8, a uint8) { a: C.uchar(a), webview: w.webview, } - C.ExecuteOnMainThread(C.setRGBA, C.gpointer(&data)) + invokeOnMainThread(func() { C.setRGBA(unsafe.Pointer(&data)) }) } @@ -840,7 +833,7 @@ func (w *Window) ExecJS(js string) { webview: w.webview, script: C.CString(js), } - C.ExecuteOnMainThread(C.executeJS, C.gpointer(&jscallback)) + invokeOnMainThread(func() { C.executeJS(unsafe.Pointer(&jscallback)) }) } func (w *Window) StartDrag() { @@ -902,7 +895,7 @@ func (w *Window) OpenFileDialog(dialogOptions frontend.OpenDialogOptions, multip data.defaultDirectory = C.CString(dialogOptions.DefaultDirectory) } - C.ExecuteOnMainThread(C.opendialog, C.gpointer(&data)) + invokeOnMainThread(func() { C.opendialog(unsafe.Pointer(&data)) }) } func (w *Window) MessageDialog(dialogOptions frontend.MessageDialogOptions) { @@ -922,7 +915,7 @@ func (w *Window) MessageDialog(dialogOptions frontend.MessageDialogOptions) { case frontend.WarningDialog: data.messageType = C.int(3) } - C.ExecuteOnMainThread(C.messageDialog, C.gpointer(&data)) + invokeOnMainThread(func() { C.messageDialog(unsafe.Pointer(&data)) }) } func (w *Window) ToggleMaximise() {