Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hangs on repeated calls to SetSelected in table. #3684

Closed
2 tasks done
dhowlett99 opened this issue Feb 26, 2023 · 25 comments
Closed
2 tasks done

Hangs on repeated calls to SetSelected in table. #3684

dhowlett99 opened this issue Feb 26, 2023 · 25 comments
Assignees
Labels
bug Something isn't working

Comments

@dhowlett99
Copy link

dhowlett99 commented Feb 26, 2023

Checklist

  • I have searched the issue tracker for open issues that relate to the same problem, before opening a new one.
  • This issue only relates to a single bug. I will open new issues for any other problems.

Describe the bug

Using the latest version fyne.io/fyne/v2 v2.3.1 on Mac Catalina 10.15.7, go version go1.20 darwin/amd64

I'm building a table which has multiple widget types (wrapped in a container) in each field of the table.
Hide and Show are called to build a table with multiple widget types see screenshot.

I use SetSelected to set the correct option from an array of options in the select widgets of the table.

If you replace the SetSelected calls with just Selected the problem goes away, but the UI shows 'Set Selected' instead of the correct option.

How to reproduce

On a MAC Build the example code and simply scroll up and down and within a few minutes the program will hang and never return.

Screenshots

image

Example code

package main

import (
	"fmt"

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

const id int = 0
const tpe int = 1
const group int = 2
const number int = 3
const name int = 4
const label int = 5
const desc int = 6
const addr int = 7
const del int = 8
const add int = 9
const channel int = 10

func main() {
	myApp := app.New()
	myWindow := myApp.NewWindow("Table Widget")

	groupOptions := []string{"1", "2", "3", "4"}
	numberOptions := []string{"1", "2", "3", "4", "5", "6", "7", "8"}
	typeOptions := []string{"rgb", "scanner", "switch", "projector"}

	var data = [][]string{}

	// scan the fixtures structure for the selected fixture.
	for fixture := 0; fixture < 32; fixture++ {
		newFixture := []string{}
		newFixture = append(newFixture, fmt.Sprintf("%d", fixture))
		newFixture = append(newFixture, "rgb")
		newFixture = append(newFixture, fmt.Sprintf("%d", 1))
		newFixture = append(newFixture, fmt.Sprintf("%d", 1))
		newFixture = append(newFixture, fmt.Sprintf("name#%d", fixture))
		newFixture = append(newFixture, fmt.Sprintf("label#%d", fixture))
		newFixture = append(newFixture, fmt.Sprintf("desc#%d", fixture))
		newFixture = append(newFixture, fmt.Sprintf("%d", fixture+100))
		newFixture = append(newFixture, "-")
		newFixture = append(newFixture, "+")
		newFixture = append(newFixture, "channel")
		data = append(data, newFixture)
	}

	list := widget.NewTable(

		// Find lenghths.
		func() (int, int) {
			return len(data), len(data[0])
		},

		// Create Table
		func() (o fyne.CanvasObject) {
			return container.NewMax(
				widget.NewLabel(""), //id 0
				widget.NewSelect(typeOptions, func(value string) {}),   // Type 1
				widget.NewSelect(groupOptions, func(value string) {}),  // group 2
				widget.NewSelect(numberOptions, func(value string) {}), //  number 3
				widget.NewEntry(),               // name 4
				widget.NewEntry(),               // label 5
				widget.NewEntry(),               // Desc 6
				widget.NewEntry(),               // address 7
				widget.NewButton("", func() {}), // Delete 8
				widget.NewButton("", func() {}), // Add 9
				widget.NewButton("", func() {}), // Channels 10
			)
		},

		// // Update Tabel
		func(i widget.TableCellID, o fyne.CanvasObject) {
			hideAllFields(o)

			if i.Col == id {
				o.(*fyne.Container).Objects[id].(*widget.Label).Hidden = false
				o.(*fyne.Container).Objects[id].(*widget.Label).SetText(data[i.Row][i.Col])
			}

			if i.Col == tpe {
				o.(*fyne.Container).Objects[tpe].(*widget.Select).Hidden = false
				o.(*fyne.Container).Objects[tpe].(*widget.Select).SetSelected(data[i.Row][i.Col])
			}

			if i.Col == group {
				o.(*fyne.Container).Objects[group].(*widget.Select).Hidden = false
				o.(*fyne.Container).Objects[group].(*widget.Select).SetSelected(data[i.Row][i.Col])
			}
			if i.Col == number {
				o.(*fyne.Container).Objects[number].(*widget.Select).Hidden = false
				o.(*fyne.Container).Objects[number].(*widget.Select).SetSelected(data[i.Row][i.Col])
			}

			if i.Col == name {
				o.(*fyne.Container).Objects[name].(*widget.Entry).Hidden = false
				o.(*fyne.Container).Objects[name].(*widget.Entry).SetText(data[i.Row][i.Col])
			}

			if i.Col == label {
				o.(*fyne.Container).Objects[label].(*widget.Entry).Hidden = false
				o.(*fyne.Container).Objects[label].(*widget.Entry).SetText(data[i.Row][i.Col])
			}

			if i.Col == desc {
				o.(*fyne.Container).Objects[desc].(*widget.Entry).Hidden = false
				o.(*fyne.Container).Objects[desc].(*widget.Entry).SetText(data[i.Row][i.Col])
			}

			if i.Col == addr {
				o.(*fyne.Container).Objects[desc].(*widget.Entry).Hidden = false
				o.(*fyne.Container).Objects[desc].(*widget.Entry).SetText(data[i.Row][i.Col])
			}

			if i.Col == del {
				o.(*fyne.Container).Objects[del].(*widget.Button).Hidden = false
				o.(*fyne.Container).Objects[del].(*widget.Button).SetText(data[i.Row][i.Col])
			}

			if i.Col == add {
				o.(*fyne.Container).Objects[add].(*widget.Button).Hidden = false
				o.(*fyne.Container).Objects[add].(*widget.Button).SetText(data[i.Row][i.Col])
			}

			if i.Col == channel {
				o.(*fyne.Container).Objects[channel].(*widget.Button).Hidden = false
				o.(*fyne.Container).Objects[channel].(*widget.Button).SetText(data[i.Row][i.Col])
			}
		})

	list.SetColumnWidth(id, 40)
	list.SetColumnWidth(tpe, 120)
	list.SetColumnWidth(group, 60)
	list.SetColumnWidth(number, 60)
	list.SetColumnWidth(name, 120)
	list.SetColumnWidth(label, 120)
	list.SetColumnWidth(desc, 300)
	list.SetColumnWidth(addr, 50)
	list.SetColumnWidth(del, 40)
	list.SetColumnWidth(add, 40)
	list.SetColumnWidth(channel, 80)

	myWindow.SetContent(list)

	myWindow.Resize(fyne.NewSize(1100, 400))
	myWindow.ShowAndRun()

}

func hideAllFields(o fyne.CanvasObject) {
	// Hide everything.
	o.(*fyne.Container).Objects[id].(*widget.Label).Hidden = true
	o.(*fyne.Container).Objects[tpe].(*widget.Select).Hidden = true
	o.(*fyne.Container).Objects[group].(*widget.Select).Hidden = true
	o.(*fyne.Container).Objects[number].(*widget.Select).Hidden = true
	o.(*fyne.Container).Objects[name].(*widget.Entry).Hidden = true
	o.(*fyne.Container).Objects[label].(*widget.Entry).Hidden = true
	o.(*fyne.Container).Objects[desc].(*widget.Entry).Hidden = true
	o.(*fyne.Container).Objects[addr].(*widget.Entry).Hidden = true
	o.(*fyne.Container).Objects[del].(*widget.Button).Hidden = true
	o.(*fyne.Container).Objects[add].(*widget.Button).Hidden = true
	o.(*fyne.Container).Objects[channel].(*widget.Button).Hidden = true
}

Fyne version

v2.3.1

Go compiler version

go1.20

Operating system and version

macOS Catalina

Additional Information

Date/Time: 2023-02-26 19:45:27 +0000
End time: 2023-02-26 19:45:47 +0000
OS Version: Mac OS X 10.15.7 (Build 19H2026)
Architecture: x86_64h
Report Version: 29

Data Source: Stackshots
Shared Cache: 0x3471000 B17C1CBE-BC73-34BB-A9C4-DE7487BA1631

Command: table
Path: /Users/USER/*/table
Version: ??? (???)
Parent: bash [6482] [unique pid 106254]
Responsible: Terminal [90652]
PID: 9275

Event: hang
Duration: 19.70s
Duration Sampled: 4.90s (process was unresponsive for 15 seconds before sampling)
Steps: 49 (100ms sampling interval)

Hardware model: MacBookAir7,2
Active cpus: 4

Time Awake Since Boot: 790000s
Time Since Wake: 33000s

Fan speed: 1192 rpm


Timeline format: stacks are sorted chronologically
Use -i and -heavy to re-report with count sorting

Heaviest stack for the main thread of the target process:
49 runtime.mcall + 67 (table + 421731) [0x100066f63]
49 runtime.park_m + 301 (table + 263053) [0x10004038d]
49 runtime.schedule + 61 (table + 261629) [0x10003fdfd]
49 runtime.stoplockedm + 101 (table + 254021) [0x10003e045]
49 runtime.mPark + 37 (table + 246629) [0x10003c365]
49 runtime.notesleep + 133 (table + 59749) [0x10000e965]
49 runtime.semasleep + 173 (table + 211565) [0x100033a6d]
49 runtime.pthread_cond_wait.abi0 + 52 (table + 353268) [0x1000563f4]
49 runtime.asmcgocall.abi0 + 161 (table + 429793) [0x100068ee1]
49 runtime.pthread_cond_wait_trampoline.abi0 + 16 (table + 438416) [0x10006b090]
49 __psynch_cvwait + 10 (libsystem_kernel.dylib + 14450) [0x7fff6a383872]
*49 psynch_cvcontinue + 0 (pthread + 18722) [0xffffff7f82d15922]

Process: table [9275] [unique pid 109046]
UUID: A1A20DF9-B4D3-3378-B665-1053C178D0E2
Path: /Users/USER/*/table
Architecture: x86_64
Parent: bash [6482] [unique pid 106254]
Responsible: Terminal [90652]
UID: 501
Footprint: 73.28 MB
Start time: 2023-02-26 19:45:42 +0000
End time: 2023-02-26 19:45:47 +0000
Num samples: 49 (1-49)
CPU Time: 0.055s (54.3M cycles, 12.7M instructions, 4.26c/i)
Note: Unresponsive for 15 seconds before sampling
Note: 1 idle work queue thread omitted

@dhowlett99 dhowlett99 added the unverified A bug that has been reported but not verified label Feb 26, 2023
@andydotxyz
Copy link
Member

It looks like you're not updating the options on the Select before you can SetSelected, maybe try fixing that and see if it's still an issue?

@dhowlett99
Copy link
Author

dhowlett99 commented Feb 27, 2023

I'm not sure why you would need to update the options but I did this and I still have the hang.

                        if i.Col == tpe {
				o.(*fyne.Container).Objects[tpe].(*widget.Select).Hidden = false
				o.(*fyne.Container).Objects[tpe].(*widget.Select).Options = typeOptions
				o.(*fyne.Container).Objects[tpe].(*widget.Select).SetSelected(data[i.Row][i.Col])
			}

			if i.Col == group {
				o.(*fyne.Container).Objects[group].(*widget.Select).Hidden = false
				o.(*fyne.Container).Objects[group].(*widget.Select).Options = groupOptions
				o.(*fyne.Container).Objects[group].(*widget.Select).SetSelected(data[i.Row][i.Col])
			}
			if i.Col == number {
				o.(*fyne.Container).Objects[number].(*widget.Select).Hidden = false
				o.(*fyne.Container).Objects[number].(*widget.Select).Options = numberOptions
				o.(*fyne.Container).Objects[number].(*widget.Select).SetSelected(data[i.Row][i.Col])
			}

@andydotxyz
Copy link
Member

I can't replicate.
Is it possible that it is due to a change in mouse position? The entry widgets (as configured) will swallow the scroll event.
If you leave it over one of the columns that are just buttons it should not fail to scroll...

@dhowlett99
Copy link
Author

I can't get it to scroll without hanging no matter where I scroll from. Here is a short video that shows scrolling with the mouse positioned over the buttons. What can I do to improve and get the entry widgets, what do you mean about swollowing scroll events ?
https://user-images.githubusercontent.com/78086928/221803594-f1aa2109-88fc-49b5-8102-ec398ab362b8.mov

@dhowlett99
Copy link
Author

dhowlett99 commented Feb 28, 2023

I have simplified the example down to just having a select and an entry widget in my container.

package main

import (
	"fmt"

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

const tpe int = 0
const name int = 1

func main() {
	myApp := app.New()
	myWindow := myApp.NewWindow("Table Widget")

	typeOptions := []string{"rgb", "scanner", "switch", "projector"}

	var data = [][]string{}

	// scan the fixtures structure for the selected fixture.
	for fixture := 0; fixture < 32; fixture++ {
		newFixture := []string{}
		newFixture = append(newFixture, "rgb")
		newFixture = append(newFixture, fmt.Sprintf("name#%d", fixture))
		data = append(data, newFixture)
	}

	list := widget.NewTable(

		// Find lenghths.
		func() (int, int) {
			return len(data), len(data[0])
		},

		// Create Table
		func() (o fyne.CanvasObject) {
			return container.NewMax(
				widget.NewSelect(typeOptions, func(value string) {}), // Type 1
				widget.NewEntry(), // name
			)
		},

		// // Update Tabel
		func(i widget.TableCellID, o fyne.CanvasObject) {

			hideAllFields(o)

			if i.Col == tpe {
				o.(*fyne.Container).Objects[tpe].(*widget.Select).Hidden = false
				o.(*fyne.Container).Objects[tpe].(*widget.Select).SetSelected(data[i.Row][i.Col])
				// o.(*fyne.Container).Objects[tpe].(*widget.Select).Selected = data[i.Row][i.Col]
				// o.(*fyne.Container).Objects[tpe].(*widget.Select).Refresh()
			}

			if i.Col == name {
				o.(*fyne.Container).Objects[name].(*widget.Entry).Hidden = false
				o.(*fyne.Container).Objects[name].(*widget.Entry).SetText(data[i.Row][i.Col])
			}

		})

	list.SetColumnWidth(tpe, 120)
	list.SetColumnWidth(name, 120)

	myWindow.SetContent(list)

	myWindow.Resize(fyne.NewSize(300, 400))
	myWindow.ShowAndRun()

}

func hideAllFields(o fyne.CanvasObject) {
	o.(*fyne.Container).Objects[tpe].(*widget.Select).Hidden = true
	o.(*fyne.Container).Objects[name].(*widget.Entry).Hidden = true
}

@dhowlett99
Copy link
Author

dhowlett99 commented Feb 28, 2023

If I call just the selected method, it does not hang but doesn't show the correct option.

o.(*fyne.Container).Objects[tpe].(*widget.Select).Selected = data[i.Row][i.Col]

if I add a refresh to that it shows the correct option but again hangs when I scroll. in the same way as a SetSelected method

o.(*fyne.Container).Objects[tpe].(*widget.Select).Selected = data[i.Row][i.Col]
o.(*fyne.Container).Objects[tpe].(*widget.Select).Refresh()
o.(*fyne.Container).Objects[tpe].(*widget.Select).SetSelected(data[i.Row][i.Col])

@dhowlett99
Copy link
Author

I've ruled out any interaction with the entry widget as I have replaced with a label and still hangs.

@dhowlett99
Copy link
Author

If I simply and remove the repeated calls to Hide() it takes a little longer to hang but still hangs.

package main

import (
	"fmt"

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

func main() {

	myApp := app.New()
	myWindow := myApp.NewWindow("Table Widget")

	typeOptions := []string{"rgb", "scanner", "switch", "projector"}

	var data = [][]string{}

	for fixture := 0; fixture < 32; fixture++ {
		newFixture := []string{}
		newFixture = append(newFixture, "rgb")
		newFixture = append(newFixture, fmt.Sprintf("name#%d", fixture))
		data = append(data, newFixture)
	}

	list := widget.NewTable(

		// Find lenghths.
		func() (int, int) {
			return len(data), len(data[0])
		},

		// Create Table
		func() (o fyne.CanvasObject) {
			return container.NewMax(
				widget.NewSelect(typeOptions, func(value string) {}), // Type 1
				widget.NewLabel("label"),                             // name
			)
		},

		// // Update Tabel
		func(i widget.TableCellID, o fyne.CanvasObject) {
			if i.Col == 0 {
				o.(*fyne.Container).Objects[0].(*widget.Select).Hidden = false
				o.(*fyne.Container).Objects[0].(*widget.Select).SetSelected(data[i.Row][i.Col])
				o.(*fyne.Container).Objects[1].(*widget.Label).Hidden = true
			}

			if i.Col == 1 {
				o.(*fyne.Container).Objects[0].(*widget.Select).Hidden = true
				o.(*fyne.Container).Objects[1].(*widget.Label).Hidden = false
				o.(*fyne.Container).Objects[1].(*widget.Label).SetText(data[i.Row][i.Col])

			}
		})

	myWindow.SetContent(list)

	myWindow.Resize(fyne.NewSize(300, 400))
	myWindow.ShowAndRun()

}

@dhowlett99
Copy link
Author

simplified again, this takes a longer to hang but still hangs

package main

import (
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/widget"
)

func main() {

	myApp := app.New()
	myWindow := myApp.NewWindow("Table Widget")

	typeOptions := []string{"1", "2", "3"}

	var data = [][]string{}

	for fixture := 0; fixture < 15; fixture++ {
		newFixture := []string{}
		newFixture = append(newFixture, "1")
		data = append(data, newFixture)
	}

	list := widget.NewTable(

		// Find lenghths.
		func() (int, int) {
			return len(data), len(data[0])
		},

		// Create Table
		func() (o fyne.CanvasObject) {
			return container.NewMax(
				widget.NewSelect(typeOptions, func(value string) {}), // Type 1
			)
		},

		// // Update Tabel
		func(i widget.TableCellID, o fyne.CanvasObject) {
			o.(*fyne.Container).Objects[0].(*widget.Select).SetSelected(data[i.Row][i.Col])
		})

	myWindow.SetContent(list)
	myWindow.ShowAndRun()

}

@andydotxyz
Copy link
Member

I cannot seem to replicate at all. Perhaps it is somehow sensitive to the version of macOS or the hardware?
Perhaps someone else on intel Mac or Linux could see if this can be replicated?

@dhowlett99
Copy link
Author

Fails on my Imac 21" and Macbook air both on Catalina 10.15.7

@dhowlett99
Copy link
Author

Interestingly I tried different versions of Fyne.io until I got back to version v2.1.1 which doesn't seem to hang. (athough scroll is slower)

@dhowlett99
Copy link
Author

Further testsing I find that //fyne.io/fyne/v2 v2.3.0-rc2 is OK but //fyne.io/fyne/v2 v2.3.0-rc3 FAILS so the problem is some change made between these two tags.

@andydotxyz
Copy link
Member

I cannot seem to replicate at all. Perhaps it is somehow sensitive to the version of macOS or the hardware? Perhaps someone else on intel Mac or Linux could see if this can be replicated?

I got to my Linux box and cannot replicate i there either. Could be macOS Intel specific somehow?

@dhowlett99
Copy link
Author

Perhaps this change , seems to be related ????
5cc7bc7

@dhowlett99
Copy link
Author

dhowlett99 commented Feb 28, 2023

It seems that removing the defer r.cells.propertyLock.Unlock() on line 726 of table.go and adding explict unlocks
creates a situation (albeit on Mac) where there is some case where the unlock isn't called.

I reinstated the defer and removed the explict unlocks and I no longer have the hang on scroll ( athough I think it looks slower)
Would you like to have a call to discuss ?

@dhowlett99
Copy link
Author

dhowlett99 commented Feb 28, 2023

I think the issue is because you continue to update objects and cells after releasing the lock. Which causes the hang on mac.

So I think the fix is to move the final unlock to the very bottom, before you return.

        // r.cells.propertyLock.Unlock()
	r.SetObjects(cells)

	if updateCell != nil {
		for id, cell := range visible {
			updateCell(TableCellID{id.Row, id.Col}, cell)
		}
	}

	r.cells.propertyLock.Unlock()

@dhowlett99
Copy link
Author

dhowlett99 commented Feb 28, 2023

Athough having the defer at the begining of the function would probably be more idiomatic Go code.

widget/table.go

724: func (r *tableCellsRenderer) Refresh() {
	r.cells.propertyLock.Lock()
	defer r.cells.propertyLock.Unlock()

@dhowlett99
Copy link
Author

dhowlett99 commented Mar 2, 2023

SOLVED - So i have confirmed that reverting back to the defer at the top of the refresh function in
table.go line 724 and removing the implicit calls to unlock fixes the hang on mac in fyne.io/fyne/v2 v2.3.1
Can someone else test on other platforms that this doesn't cause other problems.

@Jacalz
Copy link
Member

Jacalz commented Mar 2, 2023

@dhowlett99 Please open a PR instead of sharing code changes in comments. It makes things a lot easier to review and test :)

@dhowlett99
Copy link
Author

dhowlett99 commented Mar 2, 2023

@Jacalz I'm not a contributer to this project and I don't appear to have permission, But i tried

#3693

And I'm sorry but I have no idea how to run tests on this,

@dhowlett99 dhowlett99 mentioned this issue Mar 2, 2023
7 tasks
@dhowlett99
Copy link
Author

dhowlett99 commented Mar 4, 2023

@andydotxyz These seems to be another possibly related issue here as well. Tested With or without the patch described above in place or NOT.

If you click on a select widget and you can see the options, without selecting anything click away at that point
intermittently causes a hang as well. on MAC.

Cant make any sense of whats happening.

@andydotxyz
Copy link
Member

I have found a way to replicate it. Just scrolling on its own never seems to happen for me - even on an intel Mac.

Scroll, open select, scroll then try to dismiss it. Seems to hang most times using that. Is it no possible that is the culprit for the overall bug - or have you for certain seem it with only scrolling a list?

@dhowlett99
Copy link
Author

dhowlett99 commented Mar 6, 2023

@andydotxyz, Hello. I see a hang on two different scenarios with the simple example above #3684 (comment). Looks like your seeing scenario 2. My MacOs is catalina 10.5.7. intel

  1. start the App, grab the scroll bar and simply scroll up and down between 1 and 10 times, and it hangs every time.
    As I said that seems to be some kind of lock in the table.go (r *tableCellsRenderer) Refresh() hence my PR suggestion above ( which BTW fixes this scenario but not the second scenario 2below) So I'm certain I've seen it with only scrolling a TABLE?

  2. start the App, open a select statement so you can see the options. Simply click away i.e on the scroll bar and that hangs.
    In the Refresh of widget.go 133 of func (w *BaseWidget) Refresh()

I'm unable to understand how all the locking works on all these various methods. So I can't go any further without some help from some one who understands Fyne internals.

Let me know how I can help.

andydotxyz added a commit to andydotxyz/fyne that referenced this issue Jul 30, 2023
@andydotxyz andydotxyz removed the unverified A bug that has been reported but not verified label Aug 2, 2023
@andydotxyz andydotxyz added the bug Something isn't working label Aug 2, 2023
@andydotxyz andydotxyz self-assigned this Aug 2, 2023
@andydotxyz
Copy link
Member

The remaining item of this is now fixed on develop ready for v2.4.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants