gen2brain / iup-go

Cross-platform UI library with native controls

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Frequent segmentation violations on Linux/Gtk3, cause unknown (event processing?)

rasteric opened this issue · comments

I get seemingly random crashes to desktop when clicking on interface elements and interacting with them in other ways. They might be related to iup.List , I have't noticed them before I started using iup.List with many entries. Here is a typical crash:

fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0x4 pc=0x7fb88ca19678]

runtime stack:
runtime.throw({0x8d8448?, 0x7fb84c5fe7e0?})
	/usr/local/go/src/runtime/panic.go:992 +0x71
runtime.sigpanic()
	/usr/local/go/src/runtime/signal_unix.go:802 +0x3a9

goroutine 6 [syscall]:
runtime.cgocall(0x67f2e0, 0xc0003adf30)
	/usr/local/go/src/runtime/cgocall.go:157 +0x5c fp=0xc0003adf08 sp=0xc0003aded0 pc=0x4393fc
github.com/gen2brain/iup-go/iup._Cfunc_IupMainLoop()
	_cgo_gotypes.go:2234 +0x48 fp=0xc0003adf30 sp=0xc0003adf08 pc=0x57d808
github.com/gen2brain/iup-go/iup.MainLoop(...)
	/home/nemo/go/pkg/mod/github.com/gen2brain/iup-go/iup@v0.0.0-20220530155049-dfde5aeec039/bind_events.go:20
main.(*Application).Run(0xc000200000)
	/home/nemo/go/src/github.com/rasteric/giuprj/application.go:281 +0x8b fp=0xc0003adf68 sp=0xc0003adf30 pc=0x67410b
main.run()
	/home/nemo/go/src/github.com/rasteric/giuprj/main.go:40 +0x7e fp=0xc0003adfb0 sp=0xc0003adf68 pc=0x6761de
golang.design/x/mainthread.Init.func1()
	/home/nemo/go/pkg/mod/golang.design/x/mainthread@v0.3.0/mainthread.go:96 +0x5b fp=0xc0003adfe0 sp=0xc0003adfb0 pc=0x619b7b
runtime.goexit()
	/usr/local/go/src/runtime/asm_amd64.s:1571 +0x1 fp=0xc0003adfe8 sp=0xc0003adfe0 pc=0x498241
created by golang.design/x/mainthread.Init
	/home/nemo/go/pkg/mod/golang.design/x/mainthread@v0.3.0/mainthread.go:92 +0x10a

goroutine 1 [runnable, locked to thread]:
golang.design/x/mainthread.Init(0x8df020)
	/home/nemo/go/pkg/mod/golang.design/x/mainthread@v0.3.0/mainthread.go:100 +0x16b
main.main()
	/home/nemo/go/src/github.com/rasteric/giuprj/main.go:29 +0x25

goroutine 18 [select]:
database/sql.(*DB).connectionOpener(0xc0002041a0, {0x922600, 0xc00021c0c0})
	/usr/local/go/src/database/sql/sql.go:1226 +0x8d
created by database/sql.OpenDB
	/usr/local/go/src/database/sql/sql.go:794 +0x18d

goroutine 60 [runnable]:
golang.design/x/mainthread.Call(0xc000741070)
	/home/nemo/go/pkg/mod/golang.design/x/mainthread@v0.3.0/mainthread.go:135 +0xc7
main.(*Application).makeAssetList(0xc000200000, {0x922600, 0xc000080a80}, {0x0, 0x0})
	/home/nemo/go/src/github.com/rasteric/giuprj/window.go:286 +0x11c
main.(*Application).initPubSub.func5.4({0x922600, 0xc000080a80})
	/home/nemo/go/src/github.com/rasteric/giuprj/application.go:233 +0xb8
created by main.(*Scheduler).GoWithCancel
	/home/nemo/go/src/github.com/rasteric/giuprj/scheduler.go:49 +0xaa

I thought it had something to do with goroutines so I've wrapped every call to iup into mainthread.Call from https://github.com/golang-design/mainthread, even when I'm just reading attributes.

OS: Linux Mint 20.3 Cinnamon
Compiled with gtk3 option.

Can you send gdb backtrace output? From here can only be seen that MainLoop is started. Best if you remove mainthread for now, so as to not interfere in any way.

I've checked and this is definitely goroutine / concurrency related. I've changed my scheduler to not create new goroutines for certain operations and crashes no longer occur. As far as I can see, mainthread.Call does not suffice to prevent problems. I'll send you the gdb trace later, this might take a few days since I'll be away.

The safest and recommended would be to not touch anything from UI from goroutines, only via PostMessage/POSTMESSAGE_CB. I changed that function to accept cgo.Handle instead of unsafe.Pointer, so it should be safer and easier to use.

PostMessage looks very cumbersome, especially since I want certain parts of the GUI to be threaded in the sense that some parts of it need to be updated while other parts are "loading". But I think I found a solution similar to what is common in Gtk3. I'm using IdleFunc and set it via iup.SetFunction("IDLE_FUNCTION", idleFunc). I set it up with a queue in my scheduler:

type Scheduler struct {
	mainContext context.Context         // the master context, from which others are derived
	wg          sync.WaitGroup          // for waiting until all goroutines have finished
	cancel      func()                  // for canceling the master context, stopping all derived goroutines
	once        map[string]func()       // for each key, only one goroutines can run at a time
	idleQueue   *goconcurrentqueue.FIFO // for idle functions
	mutex       sync.Mutex              // for synchronization
}

// NewScheduler returns a new scheduler with a cancelable context based on the given context.
func NewScheduler(ctx context.Context) *Scheduler {
	c, done := context.WithCancel(ctx)
	scheduler := &Scheduler{
		mainContext: c,
		cancel:      done,
		once:        make(map[string]func()),
		idleQueue:   goconcurrentqueue.NewFIFO(),
	}
	idleFunc := func() int {
		running := true
		for running {
			select {
			case <-scheduler.mainContext.Done():
				running = false
				break
			default:
				iup.Flush()
				fn, err := scheduler.idleQueue.Dequeue()
				if err != nil {
					continue
				}
				f, ok := fn.(func())
				if ok {
					f()
				}
			}
		}
		return 0
	}
	iup.SetFunction("IDLE_ACTION", iup.IdleFunc(idleFunc))
	return scheduler
}

and later:

// Idle processes a function in the IUP Idle queue which is safe with respect to GUI access.
func (s *Scheduler) Idle(fn func()) {
	s.idleQueue.Enqueue(fn)
}

Wrapping every call to iup GUI elements in this type of Idle function seems to suffice (unlike mainthread) to ensure there are no crashes to desktop. If for some reason the CPU usage was high in this idle function, I could always make it sleep between checking the queue for new functions. So far it works fine, no more crashes with iup.List.

Of course, it's slow, real concurrent GUI updates are unfortunately out of reach.

Yes, IDLE can be used for that, there are also Timer and PostMessage, the only available methods when threads/goroutines are involved.

Well, all UI toolkits are like that, i.e. in Qt you send the signal from the thread, and do the work on slot/callback (i.e. PostMessage), in Windows, there is native PostThreadMessage (probably they got the idea from that), macOS uses dispatch_async, etc. The problems always happen when the user is trying to ignore the rules, force something from threads, and similar. At first, I wanted to include mainthread and wrap it in the library, but I decided that is all up to the user, and now I see it didn't help you.

Yeah, I think you should update the Readme and remove the reference to mainthread. While it does not seem to do harm (I still have it to lock main to main thread), it doesn't prevent crashes - and IUP is very sensitive to concurrent access with lots of mysterious crashes to desktop, at least with Gtk3 as the backend. Processing functions as IdleFunc in a queue using iup.SetFunction("IDLE_ACTION", iup.IdleFunc(fn)) is the right way to go, I haven't had any crash since. I'm closing the issue.