[GH-ISSUE #690] QueueUpdate(Draw) deadlocks in goroutine #504

Closed
opened 2026-03-04 01:05:33 +03:00 by kerem · 2 comments
Owner

Originally created by @craftyguy on GitHub (Jan 19, 2022).
Original GitHub issue: https://github.com/rivo/tview/issues/690

I'm trying to update the text in a textview, and wrapping it in a QueueUpdate (or QueueUpdateDraw), per the wiki page on concurrency, deadlocks 100% of the time:

package main

import (
        "fmt"
        "time"

        "github.com/rivo/tview"
)

var (
        tv  *tview.TextView    = tview.NewTextView()
        app *tview.Application = tview.NewApplication()
)

func update() {
        var i int
        for {
                app.QueueUpdateDraw(func() {
                        tv.SetText(fmt.Sprintf("%d", i))
                })

                time.Sleep(1 * time.Second)
                i++
        }
}

func main() {
        go update()

        if err := tview.NewApplication().SetRoot(tv, true).EnableMouse(true).Run(); err != nil {
                panic(err)
        }
}

The wiki says I should call Application.Draw in the "changed" handler for a textview, however this does not result in the screen being updated with any changes when using SetText:

...(snipped unchanged code)...

func update() {
        var i int
        for {
                tv.SetText(fmt.Sprintf("%d", i))

                time.Sleep(400 * time.Millisecond)
                i++
        }
}

func changed() {
        app.Draw()
}

func main() {
        tv.SetChangedFunc(changed)
        go update()

...(snipped unchanged code)...

What's the correct way to update a textview from a goroutine?

I'm using Go 1.17.4 on Alpine Linux (amd64)

Originally created by @craftyguy on GitHub (Jan 19, 2022). Original GitHub issue: https://github.com/rivo/tview/issues/690 I'm trying to update the text in a textview, and wrapping it in a `QueueUpdate` (or `QueueUpdateDraw`), per the wiki page on concurrency, deadlocks 100% of the time: ``` package main import ( "fmt" "time" "github.com/rivo/tview" ) var ( tv *tview.TextView = tview.NewTextView() app *tview.Application = tview.NewApplication() ) func update() { var i int for { app.QueueUpdateDraw(func() { tv.SetText(fmt.Sprintf("%d", i)) }) time.Sleep(1 * time.Second) i++ } } func main() { go update() if err := tview.NewApplication().SetRoot(tv, true).EnableMouse(true).Run(); err != nil { panic(err) } } ``` The wiki says I should call `Application.Draw` in the "changed" handler for a textview, however this does not result in the screen being updated with any changes when using `SetText`: ``` ...(snipped unchanged code)... func update() { var i int for { tv.SetText(fmt.Sprintf("%d", i)) time.Sleep(400 * time.Millisecond) i++ } } func changed() { app.Draw() } func main() { tv.SetChangedFunc(changed) go update() ...(snipped unchanged code)... ``` What's the correct way to update a textview from a goroutine? I'm using Go 1.17.4 on Alpine Linux (amd64)
kerem closed this issue 2026-03-04 01:05:33 +03:00
Author
Owner

@darkhz commented on GitHub (Jan 21, 2022):

Hello @craftyguy,
The first code snippet does not particularly deadlock for me when I tried it. The reason why the screen is not being updated is because you put your textview tv in a newly created Application:

if err := tview.NewApplication().SetRoot(tv, true).EnableMouse(true).Run(); err != nil {
                panic(err)
}

but you're using another already created Application instance (app, which was not initialized by calling Run() to begin with) to concurrently update your textview:

var app *tview.Application = tview.NewApplication()

app.QueueUpdateDraw(func() {
       tv.SetText(fmt.Sprintf("%d", i))
})

So your textview will be updated in a different Application than you originally intended. Therefore, use the same Application instance you declared before for all your UI related operations.

Here is the corrected code:

package main

import (
        "fmt"
        "time"

        "github.com/rivo/tview"
)

var (
        tv  *tview.TextView    = tview.NewTextView()
        app *tview.Application = tview.NewApplication()
)

func update() {
        var i int
        for {
                app.QueueUpdateDraw(func() {
                        tv.SetText(fmt.Sprintf("%d", i))
                })

                time.Sleep(1 * time.Second)
                i++
        }
}

func main() {
        go update()

        // Corrected here
        if err := app.SetRoot(tv, true).EnableMouse(true).Run(); err != nil {
                panic(err)
        }
}

This worked as intended when I tested it.

<!-- gh-comment-id:1018129453 --> @darkhz commented on GitHub (Jan 21, 2022): Hello @craftyguy, The first code snippet does not particularly deadlock for me when I tried it. The reason why the screen is not being updated is because you put your textview `tv` in a newly created Application: ``` if err := tview.NewApplication().SetRoot(tv, true).EnableMouse(true).Run(); err != nil { panic(err) } ``` but you're using another already created Application instance (app, which was not initialized by calling Run() to begin with) to concurrently update your textview: ``` var app *tview.Application = tview.NewApplication() app.QueueUpdateDraw(func() { tv.SetText(fmt.Sprintf("%d", i)) }) ``` So your textview will be updated in a different Application than you originally intended. Therefore, use the same Application instance you declared before for all your UI related operations. Here is the corrected code: ``` package main import ( "fmt" "time" "github.com/rivo/tview" ) var ( tv *tview.TextView = tview.NewTextView() app *tview.Application = tview.NewApplication() ) func update() { var i int for { app.QueueUpdateDraw(func() { tv.SetText(fmt.Sprintf("%d", i)) }) time.Sleep(1 * time.Second) i++ } } func main() { go update() // Corrected here if err := app.SetRoot(tv, true).EnableMouse(true).Run(); err != nil { panic(err) } } ``` This worked as intended when I tested it.
Author
Owner

@craftyguy commented on GitHub (Jan 21, 2022):

oof, I see the problem now. Sorry for the noise and thank you for taking the time to explain my problem :)

<!-- gh-comment-id:1018909873 --> @craftyguy commented on GitHub (Jan 21, 2022): oof, I see the problem now. Sorry for the noise and thank you for taking the time to explain my problem :)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/tview#504
No description provided.