WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Conversation

@syllith
Copy link

@syllith syllith commented Sep 12, 2025

I’ve been maintaining this patch manually for several years. Without it, if an app tab contains a completion widget, entering data and then leaving the tab idle for around five minutes triggers garbage collection, which clears cnv and causes a crash when switching back to that tab.

I’ve reapplied this fix across many updates and have yet to encounter any negative side effects. Given its stability, I’d like to propose integrating it into the official repository so others can benefit without needing to patch it locally.

@Jacalz
Copy link
Member

Jacalz commented Sep 13, 2025

Without it, if an app tab contains a completion widget, entering data and then leaving the tab idle for around five minutes triggers garbage collection, which clears cnv and causes a crash when switching back to that tab.

It sounds more like you are working around a bug somewhere else than a GC cycle garbage collecting an object. What you are describing only happens for weak pointers other than that case, the GC never reclaims objects which have a valid reference to them. Something is setting the object to nil somewhere and causing the crash.

@syllith
Copy link
Author

syllith commented Sep 13, 2025

Here's a minimal recreatable example:

package main

import (
	"strings"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/widget"
	xwidget "fyne.io/x/fyne/widget"
)

func main() {
	a := app.New()
	w := a.NewWindow("Fyne Tabs Example")

	// CompletionEntry setup
	options := []string{"Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape", "Honeydew"}
	completion := xwidget.NewCompletionEntry(options)
	completion.PlaceHolder = "Type to search fruits..."

	// Live filtering as you type
	completion.OnChanged = func(s string) {
		var filtered []string
		lower := strings.ToLower(s)
		for _, opt := range options {
			if strings.Contains(strings.ToLower(opt), lower) {
				filtered = append(filtered, opt)
			}
		}
		completion.SetOptions(filtered)
		completion.ShowCompletion()
	}

	// Tab 1: CompletionEntry
	tab1 := container.NewVBox(
		widget.NewLabel("Completion Entry Demo"),
		completion,
	)

	// Tab 2: Placeholder
	tab2 := container.NewVBox(
		widget.NewLabel("Second Tab (empty)"),
	)

	tabs := container.NewAppTabs(
		container.NewTabItem("Search", tab1),
		container.NewTabItem("Other", tab2),
	)
	tabs.SetTabLocation(container.TabLocationLeading)

	w.SetContent(tabs)
	w.Resize(fyne.NewSize(500, 300))
	w.ShowAndRun()
}

To recreate the issue, select one of the items from the completion widget, then switch to the second tab. Allow around 5 minutes to pass, then switch back to the original tab.

It will crash with the following:

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0xa8 pc=0x7ff6b0fe6996]

goroutine 1 [running, locked to thread]:
fyne.io/x/fyne/widget.(*CompletionEntry).maxSize(0xc00014c008)
        C:/Users/syllith/go/pkg/mod/fyne.io/x/[email protected]/widget/completionentry.go:106 +0x96
fyne.io/x/fyne/widget.(*CompletionEntry).Move(0xc00014c008, {0xb11df4e0?, 0x7ff6?})
        C:/Users/syllith/go/pkg/mod/fyne.io/x/[email protected]/widget/completionentry.go:42 +0x79
fyne.io/fyne/v2/layout.vBoxLayout.Layout({0x7ff6b1185680?}, {0xc00013a020, 0x2, 0xc00004f701?}, {0xb1303ec0?, 0x7ff6?})
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/layout/boxlayout.go:102 +0x26b
fyne.io/fyne/v2.(*Container).layout(...)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/container.go:185
fyne.io/fyne/v2.(*Container).Refresh(0xc000152000)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/container.go:109 +0x47
fyne.io/fyne/v2/container.(*baseTabsRenderer).applyTheme(0xc00011a1e0, {0x7ff6b13027d0?, 0xc000154000})
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/container/tabs.go:326 +0x1c4
fyne.io/fyne/v2/container.(*baseTabsRenderer).refresh(0xc00011a1e0, {0x7ff6b13027d0?, 0xc000154000?})
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/container/tabs.go:491 +0x25
fyne.io/fyne/v2/container.(*appTabsRenderer).Refresh(0xc00011a1e0)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/container/apptabs.go:319 +0x3a
fyne.io/fyne/v2/widget.(*BaseWidget).Refresh(0x0?)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/widget/widget.go:127 +0x52
fyne.io/fyne/v2/container.selectIndex({0x7ff6b13027d0, 0xc000154000}, 0x0)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/container/tabs.go:207 +0x103
fyne.io/fyne/v2/container.selectItem({0x7ff6b13027d0, 0xc000154000}, 0xc000152080)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/container/tabs.go:218 +0x57
fyne.io/fyne/v2/container.(*AppTabs).Select(...)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/container/apptabs.go:163
fyne.io/fyne/v2/container.(*appTabsRenderer).buildTabButtons.func1()
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/container/apptabs.go:381 +0x26
fyne.io/fyne/v2/container.(*tabButton).Tapped(0x7ff6b1f09500?, 0x7ff6b11ba2c0?)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/container/tabs.go:584 +0x1b
fyne.io/fyne/v2/internal/driver/glfw.(*window).mouseClickedHandleTapDoubleTap(0xc00012c000, {0x7ff6b12febc0, 0xc00014e460}, 0xc00152a010)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/internal/driver/glfw/window.go:574 +0x182
fyne.io/fyne/v2/internal/driver/glfw.(*window).processMouseClicked(0xc00012c000, 0x1, 0x0, 0x0)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/internal/driver/glfw/window.go:535 +0x725
fyne.io/fyne/v2/internal/driver/glfw.(*window).mouseClicked(0xc00012c000, 0xc00004fa48?, 0x7ff6b0f812a0?, 0x0, 0xc00004fa28?)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/internal/driver/glfw/window_desktop.go:410 +0x5d
github.com/go-gl/glfw/v3.3/glfw.goMouseButtonCB(0xc0000021c0?, 0x0, 0x0, 0x0)
        C:/Users/syllith/go/pkg/mod/github.com/go-gl/glfw/v3.3/[email protected]/input.go:333 +0x4e
github.com/go-gl/glfw/v3.3/glfw._Cfunc_glfwPollEvents()
        _cgo_gotypes.go:1545 +0x45
github.com/go-gl/glfw/v3.3/glfw.PollEvents()
        C:/Users/syllith/go/pkg/mod/github.com/go-gl/glfw/v3.3/[email protected]/window.go:931 +0x13
fyne.io/fyne/v2/internal/driver/glfw.(*gLDriver).pollEvents(...)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/internal/driver/glfw/loop_desktop.go:22
fyne.io/fyne/v2/internal/driver/glfw.(*gLDriver).runGL(0xc0015f9e28?)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/internal/driver/glfw/loop.go:152 +0x1aa
fyne.io/fyne/v2/internal/driver/glfw.(*gLDriver).Run(0xc000316c60)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/internal/driver/glfw/driver.go:162 +0x72
fyne.io/fyne/v2/app.(*fyneApp).Run(0xc000316d10)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/app/app.go:77 +0x102
fyne.io/fyne/v2/internal/driver/glfw.(*window).ShowAndRun(0xc00012c000)
        C:/Users/syllith/go/pkg/mod/fyne.io/fyne/[email protected]/internal/driver/glfw/window.go:217 +0x64
main.main()
        C:/Users/syllith/Desktop/test/main.go:54 +0x56d
exit status 2

Do you see anything that might be useful for understanding why this is happening? From what I can tell, based off this code, I'm not manipulating anything I shouldn't be and don't understand why cnv is nil. Is this perhaps a bug in the main Fyne package? Or perhaps am I filtering incorrectly and it's resulting in this side effect?

@syllith

This comment has been minimized.

@Jacalz
Copy link
Member

Jacalz commented Sep 13, 2025

While I did bail out quickly from that AI generated stuff (please avoid flooding the comments with that in the future), I think you are right. I think I may have read the code incorrectly last time I looked at this. I'll look at how the Entry widget uses CanvasForObject in the code there.

@Jacalz
Copy link
Member

Jacalz commented Sep 13, 2025

However, I still do not agree with the statement that the object just suddenly becomes nil because it is garbage collected. The widget may not have a canvas before and after it is part of the list of visible objects but I don't see how it ever could just become nil randomly.

Comment on lines +100 to +102
if cnv == nil {
return fyne.NewSize(0, 0)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into entry and select_entry, it does indeed look like you may wish to check for it being nil. I do however wonder if this function is absolutely necessary? Look at the bottom of select_entry.go in the regular fyne widgets and you'll see that we can just set the height of the dropdown to be MinSize and fyne should crop it automatically at the end of the canvas. Maybe we can solve this issue by refactoring the code to avoid this function entirely instead? :)

@syllith
Copy link
Author

syllith commented Sep 13, 2025

Yea sorry for the AI stuff, I just figured that was the most effective way of getting the gist across. I was a bit out of my depth. The GC thing was my early speculation, just because it seemed to be very time dependent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants