Deadlock fix: no channel sends during frame rendering while mutex is locked.
This commit is contained in:
parent
b838b4c75f
commit
367e6e9052
|
|
@ -29,13 +29,16 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(w *app.Window) error {
|
func run(w *app.Window) error {
|
||||||
|
log.Printf("run: starting")
|
||||||
var ops op.Ops
|
var ops op.Ops
|
||||||
shaper := text.NewShaper(text.WithCollection(gofont.Collection()))
|
shaper := text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||||
logic := editor.NewLogic()
|
logic := editor.NewLogic()
|
||||||
renderer := ui.New(ui.Theme{FontSize: 14}, shaper, logic.State())
|
renderer := ui.New(ui.Theme{FontSize: 14}, shaper, logic.State())
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
var elems []ui.Element
|
var elems []ui.Element
|
||||||
|
log.Printf("run: starting frameReceiver")
|
||||||
go frameReceiver(w, &mu, &elems, logic.FrameChan())
|
go frameReceiver(w, &mu, &elems, logic.FrameChan())
|
||||||
|
log.Printf("run: starting logic.Run")
|
||||||
go logic.Run()
|
go logic.Run()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
@ -49,34 +52,43 @@ func run(w *app.Window) error {
|
||||||
PixelHeight: e.Config.Size.Y,
|
PixelHeight: e.Config.Size.Y,
|
||||||
}
|
}
|
||||||
case app.FrameEvent:
|
case app.FrameEvent:
|
||||||
|
log.Printf("run: FrameEvent")
|
||||||
gtx := app.NewContext(&ops, e)
|
gtx := app.NewContext(&ops, e)
|
||||||
newScale := gtx.Metric.PxPerDp
|
newScale := gtx.Metric.PxPerDp
|
||||||
curScale := logic.State().Scale()
|
curScale := logic.State().Scale()
|
||||||
if newScale != curScale {
|
if newScale != curScale {
|
||||||
logic.ConfigChan() <- editor.ScaleEvent{newScale}
|
logic.ConfigChan() <- editor.ScaleEvent{newScale}
|
||||||
}
|
}
|
||||||
|
// Deadlock risk: ensure no channel sends while lock is held
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
currentElems := elems
|
currentElems := elems
|
||||||
renderer.Draw(gtx, currentElems)
|
renderer.Draw(gtx, currentElems)
|
||||||
logic.DisplayLineChan() <- int(renderer.LastLineY())
|
lastLineY := int(renderer.LastLineY())
|
||||||
// Send search query update to the logic goroutine when it changes.
|
// Send search query update to the logic goroutine when it changes.
|
||||||
// The logic goroutine handles filtering and triggers a new frame.
|
// The logic goroutine handles filtering and triggers a new frame.
|
||||||
newQuery := logic.State().Browser.SearchEditor.Text()
|
newQuery := logic.State().Browser.SearchEditor.Text()
|
||||||
|
sendQuery := false
|
||||||
if newQuery != logic.State().Browser.Query {
|
if newQuery != logic.State().Browser.Query {
|
||||||
logic.SearchQueryChan() <- newQuery
|
sendQuery = true
|
||||||
}
|
}
|
||||||
if events := renderer.CheckGestures(e.Source, gtx.Metric); len(events) > 0 {
|
if events := renderer.CheckGestures(e.Source, gtx.Metric); len(events) > 0 {
|
||||||
logic.InputChan() <- events
|
logic.InputChan() <- events
|
||||||
}
|
}
|
||||||
e.Frame(&ops)
|
e.Frame(&ops)
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
if sendQuery {
|
||||||
|
logic.SearchQueryChan() <- newQuery
|
||||||
|
}
|
||||||
|
logic.DisplayLineChan() <- lastLineY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func frameReceiver(w *app.Window, mu *sync.Mutex, elems *[]ui.Element, frameChan <-chan []ui.Element) {
|
func frameReceiver(w *app.Window, mu *sync.Mutex, elems *[]ui.Element, frameChan <-chan []ui.Element) {
|
||||||
|
log.Printf("frameReceiver: loop starting")
|
||||||
for {
|
for {
|
||||||
frame := <-frameChan
|
frame := <-frameChan
|
||||||
|
log.Printf("frameReceiver: received frame")
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
*elems = frame
|
*elems = frame
|
||||||
w.Invalidate()
|
w.Invalidate()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package editor
|
package editor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"pad/internal/browser"
|
"pad/internal/browser"
|
||||||
|
|
@ -81,8 +82,8 @@ func NewLogic() *Logic {
|
||||||
configChan: make(chan ConfigUpdate),
|
configChan: make(chan ConfigUpdate),
|
||||||
frameChan: make(chan []ui.Element),
|
frameChan: make(chan []ui.Element),
|
||||||
inputChan: make(chan []ui.InputEvent),
|
inputChan: make(chan []ui.InputEvent),
|
||||||
lastLineYChan: make(chan int),
|
lastLineYChan: make(chan int),
|
||||||
resultChan: make(chan ResultEvent),
|
resultChan: make(chan ResultEvent),
|
||||||
searchQueryChan: make(chan string),
|
searchQueryChan: make(chan string),
|
||||||
openFileChan: make(chan string), // Initialized
|
openFileChan: make(chan string), // Initialized
|
||||||
workerPool: wp,
|
workerPool: wp,
|
||||||
|
|
@ -136,6 +137,7 @@ func (l *Logic) Scale() float32 {
|
||||||
// Run runs the logic goroutine loop.
|
// Run runs the logic goroutine loop.
|
||||||
func (l *Logic) Run() {
|
func (l *Logic) Run() {
|
||||||
// Dispatch initial directory index build on startup
|
// Dispatch initial directory index build on startup
|
||||||
|
log.Printf("Logic: Dispatching BuildIndexTask")
|
||||||
l.workerPool.Dispatch(pool.NewBuildIndexTask(l.state.Browser.CurrentPath, l.mockFS))
|
l.workerPool.Dispatch(pool.NewBuildIndexTask(l.state.Browser.CurrentPath, l.mockFS))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
@ -143,6 +145,7 @@ func (l *Logic) Run() {
|
||||||
case <-l.done:
|
case <-l.done:
|
||||||
return
|
return
|
||||||
case update := <-l.configChan:
|
case update := <-l.configChan:
|
||||||
|
log.Printf("Logic: ConfigEvent")
|
||||||
update.apply(l.state)
|
update.apply(l.state)
|
||||||
l.frameChan <- l.state.layout(l.browserManager)
|
l.frameChan <- l.state.layout(l.browserManager)
|
||||||
case y := <-l.lastLineYChan:
|
case y := <-l.lastLineYChan:
|
||||||
|
|
@ -151,11 +154,13 @@ func (l *Logic) Run() {
|
||||||
l.frameChan <- l.state.layout(l.browserManager)
|
l.frameChan <- l.state.layout(l.browserManager)
|
||||||
}
|
}
|
||||||
case events := <-l.inputChan:
|
case events := <-l.inputChan:
|
||||||
|
log.Printf("Logic: InputEvents")
|
||||||
for _, evt := range events {
|
for _, evt := range events {
|
||||||
evt.Handler(evt.Data)
|
evt.Handler(evt.Data)
|
||||||
}
|
}
|
||||||
l.frameChan <- l.state.layout(l.browserManager)
|
l.frameChan <- l.state.layout(l.browserManager)
|
||||||
case query := <-l.searchQueryChan:
|
case query := <-l.searchQueryChan:
|
||||||
|
log.Printf("Logic: SearchQuery")
|
||||||
if query != l.state.Browser.Query {
|
if query != l.state.Browser.Query {
|
||||||
l.state.Browser.Query = query
|
l.state.Browser.Query = query
|
||||||
if l.state.page == BrowserPage {
|
if l.state.page == BrowserPage {
|
||||||
|
|
@ -164,9 +169,11 @@ func (l *Logic) Run() {
|
||||||
}
|
}
|
||||||
l.frameChan <- l.state.layout(l.browserManager)
|
l.frameChan <- l.state.layout(l.browserManager)
|
||||||
case path := <-l.openFileChan:
|
case path := <-l.openFileChan:
|
||||||
|
log.Printf("Logic: OpenFileChan %s", path)
|
||||||
// Dispatch ReadFileTask to worker pool
|
// Dispatch ReadFileTask to worker pool
|
||||||
l.workerPool.Dispatch(pool.NewReadFileTask(path, l.mockFS))
|
l.workerPool.Dispatch(pool.NewReadFileTask(path, l.mockFS))
|
||||||
case res := <-l.workerPool.ResultChan():
|
case res := <-l.workerPool.ResultChan():
|
||||||
|
log.Printf("Logic: WorkerResult %s", res.TaskType)
|
||||||
l.handleWorkerResult(res)
|
l.handleWorkerResult(res)
|
||||||
case <-l.resultChan:
|
case <-l.resultChan:
|
||||||
l.frameChan <- l.state.layout(l.browserManager)
|
l.frameChan <- l.state.layout(l.browserManager)
|
||||||
|
|
@ -179,6 +186,7 @@ func (l *Logic) handleWorkerResult(res pool.Result) {
|
||||||
if res.IsBrowserResult() {
|
if res.IsBrowserResult() {
|
||||||
l.browserManager.HandleResult(res)
|
l.browserManager.HandleResult(res)
|
||||||
} else if res.TaskType == pool.TypeReadFile {
|
} else if res.TaskType == pool.TypeReadFile {
|
||||||
|
log.Printf("Logic: TypeReadFile result success=%v", res.Success)
|
||||||
if res.Success {
|
if res.Success {
|
||||||
if content, ok := res.Data.([]byte); ok {
|
if content, ok := res.Data.([]byte); ok {
|
||||||
l.state.ActiveFileContent = string(content)
|
l.state.ActiveFileContent = string(content)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user