1
1
mirror of https://github.com/wader/fq.git synced 2024-11-29 23:27:12 +03:00
fq/internal/ctxstack/ctxstack.go
Mattias Wadman 568afff3f0 interp: Fix panic when trigger before any context has been pushed
Make sure there is a top cancel function before calling it.
Fixes panic caused when interrupting decode before interp context has been pushed.

Also cleanup confusing naming a bit.

Thanks @pldin601 for reporting
2022-11-29 17:38:58 +01:00

74 lines
1.6 KiB
Go

// Package ctxstack manages a stack of contexts. When triggerFn returns and closeCh is not closed
// it will cancel the top context. Stack is popped in top first order when returned cancel funcition
// is called.
// This can be used to keep track of contexts for nested REPL:s were you only want to cancel
// the current active "top" REPL.
// TODO: should New take a parent context?
package ctxstack
import (
"context"
)
// Stack is a context stack
type Stack struct {
cancelFns []func()
stopCh chan struct{}
}
// New context stack
func New(triggerCh func(stopCh chan struct{})) *Stack {
stopCh := make(chan struct{})
s := &Stack{stopCh: stopCh}
go func() {
for {
triggerCh(stopCh)
select {
case <-stopCh:
// stop if stopCh closed
default:
// ignore if triggered before any context pushed
if len(s.cancelFns) > 0 {
s.cancelFns[len(s.cancelFns)-1]()
}
continue
}
break
}
}()
return s
}
// Stop context stack
func (s *Stack) Stop() {
for i := len(s.cancelFns) - 1; i >= 0; i-- {
s.cancelFns[i]()
}
close(s.stopCh)
}
// Push creates, pushes and returns new context. Cancel pops it.
func (s *Stack) Push(parent context.Context) (context.Context, func()) {
stackCtx, stackCtxCancel := context.WithCancel(parent)
stackIdx := len(s.cancelFns)
s.cancelFns = append(s.cancelFns, stackCtxCancel)
cancelled := false
return stackCtx, func() {
if cancelled {
return
}
cancelled = true
for i := len(s.cancelFns) - 1; i >= stackIdx; i-- {
s.cancelFns[i]()
}
s.cancelFns = s.cancelFns[0:stackIdx]
stackCtxCancel()
}
}