{"thread":{"id":"65501","subject":"gitk: race condition when changing view","startedAt":"2026-04-16T21:36:42Z","lastAt":"2026-04-18T18:36:17Z","messageCount":2,"participants":["Michael Warmuth-Uhl","Johannes Sixt"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"541780","messageId":"20260416233639.32e5b481@rosa.suljee.de","threadId":"65501","inReplyTo":null,"subject":"gitk: race condition when changing view","fromName":"Michael Warmuth-Uhl","fromEmail":"michael.warmuth-uhl@suljee.de","sentAt":"2026-04-16T21:36:39Z","receivedAt":"2026-04-16T21:36:42Z","isPatch":false,"body":"Hi.\n\nwhen displaying a bigger commit and then changing the view, gitk reports \n\n        can't read \"treediffs(b107a60c5bed3002ecef93b4405a44d809776e6f)\": no such variable\n        can't read \"treediffs(b107a60c5bed3002ecef93b4405a44d809776e6f)\": no such variable\n            while executing\n        \"lsearch -exact $treediffs($ids) $fname\"\n            (procedure \"makediffhdr\" line 7)\n            invoked from within\n        \"makediffhdr $fname $ids\"\n            (procedure \"parseblobdiffline\" line 56)\n            invoked from within\n        \"parseblobdiffline $ids $line\"\n            (procedure \"getblobdiffline\" line 16)\n            invoked from within\n        \"getblobdiffline file6 b107a60c5bed3002ecef93b4405a44d809776e6f\"\n            (\"eval\" body line 1)\n            invoked from within\n        \"eval $script\"\n            (procedure \"dorunq\" line 11)\n            invoked from within\n        \"dorunq\"\n            (\"after\" script)\n\nIt is reproducible with:\n\n        TCDIR=/tmp/testcase\n        N=512\n\n        rm -rf $TCDIR\n        git init $TCDIR\n        for i in `seq $N` ; do \n            dd if=/dev/random status=none bs=1024 count=16 | uuencode \"\" > $TCDIR/foo_$i \n        done\n        git -C $TCDIR add . \n        git -C $TCDIR commit -m \"commit\" \n\n        cd $TCDIR\n        gitk --all\n\nfollowed by an immediate change to \"All Files\" in gitk menu \"View\".\n\nThat happens because proc showview (and some others) clear treediffs which is \nstill needed by tasks pending in runq.\n\nThe patch below makes showview wait until runq is empty which is slow (waits for \nuseless operations) and feels wrong. I have no idea how to cleanly flush runq \ninstead.\n\nThe same issue has been mentioned here before: \n\thttps://stackoverflow.com/questions/17977996/gitk-cant-read-treediffs-error\n\nMichael\n\n---\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex 2730274966..f40bb1aa8c 100755\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -281,10 +281,11 @@ proc gitworktree {} {\n # run before X event handlers, so reading from a fast source can\n # make the GUI completely unresponsive.\n proc run args {\n-    global isonrunq runq currunq\n+    global isonrunq runq currunq runq_active\n \n     set script $args\n     if {[info exists isonrunq($script)]} return\n+    set $runq_active 1\n     if {$runq eq {} && ![info exists currunq]} {\n         after idle dorunq\n     }\n@@ -297,8 +298,9 @@ proc filerun {fd script} {\n }\n \n proc filereadable {fd script} {\n-    global runq currunq\n+    global runq currunq runq_active\n \n+    set $runq_active 1\n     fileevent $fd readable {}\n     if {$runq eq {} && ![info exists currunq]} {\n         after idle dorunq\n@@ -318,12 +320,20 @@ proc nukefile {fd} {\n     }\n }\n \n+proc wait_runq_empty {} {\n+    global runq runq_active\n+    while {$runq ne {} || $runq_active > 0} {\n+        dorunq\n+    }\n+}\n+\n proc dorunq {} {\n-    global isonrunq runq currunq\n+    global isonrunq runq currunq runq_active\n \n     set tstart [clock clicks -milliseconds]\n     set t0 $tstart\n     while {[llength $runq] > 0} {\n+        set $runq_active 1\n         set fd [lindex $runq 0 0]\n         set script [lindex $runq 0 1]\n         set currunq [lindex $runq 0]\n@@ -343,6 +353,7 @@ proc dorunq {} {\n         } elseif {$fd eq {}} {\n             unset isonrunq($script)\n         }\n+        set $runq_active 0\n         set t0 $t1\n         if {$t1 - $tstart >= 80} break\n     }\n@@ -825,6 +836,7 @@ proc reloadcommits {} {\n     global showneartags treediffs commitinterest cached_commitrow\n     global targetid commitinfo\n \n+    wait_runq_empty\n     set selid {}\n     if {$selectedline ne {}} {\n         set selid $currentid\n@@ -3660,6 +3672,7 @@ proc pop_flist_menu {w X Y x y} {\n     global ctext cflist cmitmode flist_menu flist_menu_file\n     global treediffs diffids\n \n+    wait_runq_empty\n     stopfinding\n     set l [lindex [split [$w index \"@$x,$y\"] \".\"] 0]\n     if {$l <= 1} return\n@@ -4664,6 +4677,8 @@ proc showview {n} {\n     global hlview selectedhlview commitinterest\n \n     if {$n == $curview} return\n+\n+    wait_runq_empty\n     set selid {}\n     set ymax [lindex [$canv cget -scrollregion] 3]\n     set span [$canv yview]\n@@ -12909,6 +12924,7 @@ if {[file exists $themeloader]} {\n set appname \"gitk\"\n \n set runq {}\n+set runq_active 0\n set history {}\n set historyindex 0\n set fh_serial 0\n\n\n\n"},{"id":"541867","messageId":"ee2599a5-13b6-469a-a284-4a5d26b2c721@kdbg.org","threadId":"65501","inReplyTo":"20260416233639.32e5b481@rosa.suljee.de","subject":"Re: gitk: race condition when changing view","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-04-18T17:48:59Z","receivedAt":"2026-04-18T18:36:17Z","isPatch":false,"body":"Am 16.04.26 um 23:36 schrieb Michael Warmuth-Uhl:\n> when displaying a bigger commit and then changing the view, gitk reports \n> \n>         can't read \"treediffs(b107a60c5bed3002ecef93b4405a44d809776e6f)\": no such variable\n>         can't read \"treediffs(b107a60c5bed3002ecef93b4405a44d809776e6f)\": no such variable\n>             while executing\n>         \"lsearch -exact $treediffs($ids) $fname\"\n>             (procedure \"makediffhdr\" line 7)\n>             invoked from within\n>         \"makediffhdr $fname $ids\"\n>             (procedure \"parseblobdiffline\" line 56)\n>             invoked from within\n>         \"parseblobdiffline $ids $line\"\n>             (procedure \"getblobdiffline\" line 16)\n>             invoked from within\n>         \"getblobdiffline file6 b107a60c5bed3002ecef93b4405a44d809776e6f\"\n>             (\"eval\" body line 1)\n>             invoked from within\n>         \"eval $script\"\n>             (procedure \"dorunq\" line 11)\n>             invoked from within\n>         \"dorunq\"\n>             (\"after\" script)\n> \n> It is reproducible with:\n> \n>         TCDIR=/tmp/testcase\n>         N=512\n> \n>         rm -rf $TCDIR\n>         git init $TCDIR\n>         for i in `seq $N` ; do \n>             dd if=/dev/random status=none bs=1024 count=16 | uuencode \"\" > $TCDIR/foo_$i \n>         done\n>         git -C $TCDIR add . \n>         git -C $TCDIR commit -m \"commit\" \n> \n>         cd $TCDIR\n>         gitk --all\n> \n> followed by an immediate change to \"All Files\" in gitk menu \"View\".\n> \n> That happens because proc showview (and some others) clear treediffs which is \n> still needed by tasks pending in runq.\n> \n> The patch below makes showview wait until runq is empty which is slow (waits for \n> useless operations) and feels wrong. I have no idea how to cleanly flush runq \n> instead.\n> \n> The same issue has been mentioned here before: \n> \thttps://stackoverflow.com/questions/17977996/gitk-cant-read-treediffs-error\n> \n> Michael\n\nThank you for the report, the reproducer, and for digging into the issue.\n\nWaiting for the process to complete is certainly not the most desirable\nway to handle the situation. I've attempted to clear the run queue with\nthe patch below, but the reproduction recipe still triggers an error\n'can't unset \"currunq\": no such variable', the reason being that proc\ndorunq is entered recursively via one of the queued scripts, and I\nhaven't found why that is the case.\n\nI'll look into this further as time permits, but don't hold your breath.\n\n-- Hannes\n\ndiff --git a/gitk b/gitk\nindex dc042a5..51d9fe5 100755\n--- a/gitk\n+++ b/gitk\n@@ -4693,6 +4693,17 @@ proc showview {n} {\n     }\n     unselectline\n     normalline\n+    global runq\n+    foreach curq $runq {\n+        set fd [lindex $curq 0]\n+        if {$fd ne {}} {\n+            catch {close $fd}\n+        } else {\n+            set script [lindex $curq 1]\n+            unset isonrunq($script)\n+        }\n+    }\n+    set runq {}\n     unset -nocomplain treediffs\n     clear_display\n     if {[info exists hlview] && $hlview == $n} {\n\n"}]}