SSE: Fix goroutine leak in math operation expression parsing (#102380)

---------

Co-authored-by: Sam Jewell <sam.jewell@grafana.com>
This commit is contained in:
Kyle Brandt
2025-03-19 08:34:49 -04:00
committed by GitHub
co-authored by Sam Jewell
parent 216b6e96a9
commit 3f2ef07872
3 changed files with 109 additions and 11 deletions
+36 -10
View File
@@ -67,13 +67,14 @@ type stateFn func(*lexer) stateFn
// lexer holds the state of the scanner.
type lexer struct {
input string // the string being scanned
state stateFn // the next lexing function to enter
pos Pos // current position in the input
start Pos // start position of this item
width Pos // width of last rune read from input
lastPos Pos // position of most recent item returned by nextItem
items chan item // channel of scanned items
input string // the string being scanned
state stateFn // the next lexing function to enter
pos Pos // current position in the input
start Pos // start position of this item
width Pos // width of last rune read from input
lastPos Pos // position of most recent item returned by nextItem
items chan item // channel of scanned items
done chan struct{} // channel to signal lexer shutdown
}
// next returns the next rune in the input.
@@ -103,7 +104,11 @@ func (l *lexer) backup() {
// emit passes an item back to the client.
func (l *lexer) emit(t itemType) {
l.items <- item{t, l.start, l.input[l.start:l.pos]}
select {
case l.items <- item{t, l.start, l.input[l.start:l.pos]}:
case <-l.done:
return
}
l.start = l.pos
}
@@ -139,7 +144,11 @@ func (l *lexer) lineNumber() int {
// errorf returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.nextItem.
func (l *lexer) errorf(format string, args ...any) stateFn {
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
select {
case l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}:
case <-l.done:
return nil
}
return nil
}
@@ -155,15 +164,32 @@ func lex(input string) *lexer {
l := &lexer{
input: input,
items: make(chan item),
done: make(chan struct{}),
}
go l.run()
return l
}
// Close terminates the lexer goroutine.
func (l *lexer) Close() {
select {
case <-l.done:
// already closed
default:
close(l.done)
}
}
// run runs the state machine for the lexer.
func (l *lexer) run() {
defer close(l.items)
for l.state = lexItem; l.state != nil; {
l.state = l.state(l)
select {
case <-l.done:
return
default:
l.state = l.state(l)
}
}
}