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
Deadlocks when using widgets with data #2348
Comments
Please add a short code example that we can use to replicate. It makes it much easier asker to try and replicate and having one also means that we can rule out the potential that the error could be in your own code :) |
I have reproduced this deadlock on a smaller version of code. The call stack in VS Code is: In this case all the widgets are still rendering but do not respond to events, except the main window frame which can be resized and closed. This is the main window view when the deadlock happened: To replicate; the procedure is to move quickly between the tree nodes e.g: "sample6" and "sample9", but before the switch change the selection or other data on "sample6". I think hence the binding.(*listener).DataChanged. The event is trying to update - I assume the data changed - but it clashes with the new layout creation somehow. To replicate the dead lock one needs to exercise the node switch several times. This may not happen for hours, and then suddenly in less expected moment. I admit the indexing of the tree nodes is not the fastest, but it will do for now. Sample source code can be found here: https://github.com/namezis/fyne-tree-with-details-form I stopped using the widget components "*WithData" for critical parts of the code, and the app seems to be more stable. Any comments are welcome. Do not hesitate to ask for more help. |
Thanks this info is really helpful. |
That is correct. In another instance of this problem. List of goroutines: Goroutine 1 - Start: /usr/lib/go-1.16/src/runtime/proc.go:115 runtime.main (0x45dfc0) [select] Goroutine 21 frame 5 at /home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/widget.go:214 (PC: 0x6a0c65) Goroutine 37 frame 5 at /home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/widget.go:55 (PC: 0x6a023c) I have attached the whole stack: stack-trace-lock-r1.txt Does this mean that some code went over the w.propertyLock and left it locked? Or, it could be that the lock is not yet initialized? |
I noticed that in: Goroutine 21 frame 6 at /home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/check.go:53 (PC: 0x687050) I can get the label of the check widget, so I populated this with tree id for which the tree tap happens. I kept modifying only the checkbox, and the checkbox for which the lock is about is the same component in both goroutine 21 and goroutine 37. Goroutine 21 frame 6 at /home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/check.go:53 (PC: 0x687050) Goroutine 37 frame 10 at /work/go-tests/fyne/fyne-tree-with-details-form/objects/info.go:65 (PC: 0x710494) Why is the Goroutine 21 executing update when the layout is still being prepared? |
This may relate to the underlying race that I am looking at where render thread is calling at the same time as layout when initialised from an external update. |
Ok, Please forgot about changing any data on the form with data bound widgets to replicate this issue. The deadlock may happen just simply by creating a layout with widgets of kind *WithData. One of the threads that locks is created by any widget.New*WithData constructor, and within the Bind call. As far as I understand that, the execution of that call is nondeterministic. There is no tooling to control that. The following is the DataChanged callback executed in above code: At some point, this simple code: will start position the widgets (Move or Resize code in e.g formlayout.go function Layout) while another go routine will try to perform (*BaseWidget).Refresh on it: Now the check propertyLock is abused in (*checkRenderer).Refresh() - best to my understanding: down the stack frame we have: I checked the pointers to c.check.propertyLock and w.propertyLock and they are the same. There is a bit of material about usage of recursive read locking. The best summary can be found here: https://stackoverflow.com/questions/14670979/recursive-locking-in-go/14671462#14671462 and in the RWMutext code itself: [...] (RLock locks) should not be used for recursive read locking; a blocked Lock call excludes new readers from acquiring the lock. [...] Russ Cox describes solution to the problem under the following link: https://groups.google.com/d/msg/golang-nuts/XqW1qcuZgKg/Ui3nQkeLV80J
It looks like the simplest resolution to this particular deadlock is to use c.check.disabled, with lower case, if such was to exist: without the RLocks: Any thoughts? |
Thanks for tracking this down, excellent work. In the long term it is probable that renderer and widget should have different locks - but I don't think that would have fixed this particular issue. |
Andrew, Feel free to take it over. |
Thanks, this should be fixed on |
Thanks very much. I am looking forward to use it. |
This issue still exists in the Here is the flow for the issue:
I wonder if in the tests you could find this situation by replacing they mutex with a custom struct that can verify if the read lock was already taken in the call stack somehow. Or if there is some static analysis tool that can do it. @andydotxyz do you want me to create a separate issue for these 2 instances or reopen this one? |
Given the age of this issue can you open a new one please? I'd guess the code is more recent than the original fix and someone didn't notice that calling the public method from inside the private lock is a bad thing... |
Description:
I have an application with split horizontal layout:
Left is tree, and right is place for tree node details form.
When tapped on some of the tree nodes one can get the details to modify node properties.
Some forms have data bind text or checkbox entries. Data bind to global structure fields.
I seem to get very sporadic, I assume dead lock, in fyne.io/fyne/v2@v2.0.3/data/binding/queue.go.
The deadlock happens when I change e.g.: checkbox and quickly move to another node.
This particular fatal error was: sync: Unlock of unlocked RWMutex
In other cases the UI stops refreshing, but it can still be moved and be closed.
runtime.throw(0xb6bfd5, 0x20)
/usr/lib/go-1.16/src/runtime/panic.go:1117 +0x72 fp=0xc0003d3b60 sp=0xc0003d3b30 pc=0x4630f2
sync.throw(0xb6bfd5, 0x20)
/usr/lib/go-1.16/src/runtime/panic.go:1103 +0x35 fp=0xc0003d3b80 sp=0xc0003d3b60 pc=0x497bd5
sync.(*RWMutex).Unlock(0xc000cb34f8)
/usr/lib/go-1.16/src/sync/rwmutex.go:142 +0x6a fp=0xc0003d3bc8 sp=0xc0003d3b80 pc=0x4ab18a
fyne.io/fyne/v2/widget.(*entryRenderer).Refresh(0xc0010a9000)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/entry.go:1246 +0x7a5 fp=0xc0003d3e50 sp=0xc0003d3bc8 pc=0x82a145
fyne.io/fyne/v2/widget.(*BaseWidget).Refresh(0xc001927200)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/widget.go:139 +0x82 fp=0xc0003d3ea0 sp=0xc0003d3e50 pc=0x84e7a2
fyne.io/fyne/v2/widget.(*Entry).Bind.func2()
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/entry.go:140 +0x1dc fp=0xc0003d3f58 sp=0xc0003d3ea0 pc=0x84f6bc
fyne.io/fyne/v2/data/binding.(*listener).DataChanged(0xc001301890)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/data/binding/binding.go:55 +0x2a fp=0xc0003d3f68 sp=0xc0003d3f58 pc=0x814b6a
fyne.io/fyne/v2/data/binding.DataListener.DataChanged-fm()
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/data/binding/binding.go:40 +0x44 fp=0xc0003d3f90 sp=0xc0003d3f68 pc=0x818ee4
fyne.io/fyne/v2/data/binding.processItems()
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/data/binding/queue.go:56 +0x71 fp=0xc0003d3fe0 sp=0xc0003d3f90 pc=0x818711
runtime.goexit()
/usr/lib/go-1.16/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc0003d3fe8 sp=0xc0003d3fe0 pc=0x49c161
created by fyne.io/fyne/v2/data/binding.init.0
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/data/binding/queue.go:15 +0x35
from main form:
goroutine 14 [runnable]:
sync.(*RWMutex).RUnlock(0xc0001582d0)
/usr/lib/go-1.16/src/sync/rwmutex.go:75 +0x65
fyne.io/fyne/v2/app.(*settings).Theme(0xc0001582d0, 0xbaee98, 0xc00007e7e0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/app/settings.go:65 +0xa6
fyne.io/fyne/v2/theme.current(0x0, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/theme/theme.go:336 +0x9f
fyne.io/fyne/v2/theme.safeColorLookup(0xb54ee5, 0x6, 0x0, 0x0, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/theme/theme.go:579 +0x31
fyne.io/fyne/v2/theme.ShadowColor(0x0, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/theme/theme.go:437 +0x51
fyne.io/fyne/v2/widget.(*Entry).CreateRenderer(0xc001927500, 0x0, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/entry.go:164 +0xc9
fyne.io/fyne/v2/internal/cache.Renderer(0xbb4188, 0xc001927500, 0x0, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/internal/cache/widget.go:29 +0x2a4
fyne.io/fyne/v2/widget.(*BaseWidget).MinSize(0xc001927500, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/widget.go:86 +0x65
fyne.io/fyne/v2/widget.(*Entry).MinSize(0xc001927500, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/entry.go:374 +0x74
fyne.io/fyne/v2/layout.(*formLayout).tableCellsSize(0x1372f70, 0xc0002acf00, 0xa, 0xa, 0x0, 0x0, 0x0, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/layout/formlayout.go:58 +0x2d9
fyne.io/fyne/v2/layout.(*formLayout).MinSize(0x1372f70, 0xc0002acf00, 0xa, 0xa, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/layout/formlayout.go:111 +0x74
fyne.io/fyne/v2.NewContainerWithLayout(0xbabd90, 0x1372f70, 0xc0002acf00, 0xa, 0xa, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/container.go:47 +0x109
fyne.io/fyne/v2/container.New(0xbabd90, 0x1372f70, 0xc0002acf00, 0xa, 0xa, 0x0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/container/container.go:12 +0x5c
user.app/app/objects.(*Controller).GetDetailsForm(0xc000430710, 0xc000312da0, 0x1b, 0x0, 0x0) <- My function creating layout with data bind elements
/home/user/app/objects/controller.go:128 +0xe85
main.main.func23(0xc000312da0, 0x1b)
/home/user/app/main.go:182 +0x733
fyne.io/fyne/v2/widget.(*Tree).Select(0xc000166d00, 0xc000312da0, 0x1b)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/tree.go:224 +0x405
fyne.io/fyne/v2/widget.(*treeNode).Tapped(0xc00044b320, 0xc0014c9330)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/widget/tree.go:625 +0x5e
fyne.io/fyne/v2/internal/driver/glfw.(*window).mouseClicked.func7()
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/internal/driver/glfw/window.go:799 +0x53
fyne.io/fyne/v2/internal/driver/glfw.(*window).runEventQueue(0xc0001741c0)
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/internal/driver/glfw/window.go:1265 +0xc3
created by fyne.io/fyne/v2/internal/driver/glfw.(*gLDriver).createWindow.func1
/home/user/go/pkg/mod/fyne.io/fyne/v2@v2.0.3/internal/driver/glfw/window.go:1289 +0x1ac
It could be that I am doing something wrong, but currently I cannot see where. All my go routines are parked in correct places.
To Reproduce and Example code:
The application is not one that I can paste here. If it helps fyne I am willing to prepare simplified version and add reference here.
I want to highlight here that I can work with this app for hours without problems. At times I can have several 100s of nodes on the tree.
Screenshots:
Device:
The text was updated successfully, but these errors were encountered: