From 43c34c9ffcf9931207a703371d7f51655bef0bb8 Mon Sep 17 00:00:00 2001 From: Amy Gale Ruth Bowersox Date: Sun, 22 Feb 2026 21:59:36 -0700 Subject: [PATCH] fixed up use of context to stop loops by using a select with case <-ctx.Done() --- email/subscription.go | 27 ++-- htmlcheck/checker.go | 291 +++++++++++++++++++++--------------------- 2 files changed, 161 insertions(+), 157 deletions(-) diff --git a/email/subscription.go b/email/subscription.go index 654b26d..5c07d4f 100644 --- a/email/subscription.go +++ b/email/subscription.go @@ -101,19 +101,22 @@ func AmDeliverSubscription(ctx context.Context, comm *database.Community, conf * // The delivery loop; build each message and send it. Note that sending a message puts the Message structure on // the sender goroutine channel, so we have to create a new Message each time, unlike what we did in Venice. +SendLoop: for i := range recipUids { - err = ctx.Err() - if err != nil { - log.Errorf("AmDeliverSubscription: aborted on send loop iter %d with %v", i+1, err) - } - if ci, err := database.AmGetContactInfoForUser(ctx, recipUids[i]); err == nil { - msg := AmNewEmailMessage(poster.Uid, ipaddr) - msg.SetSubject(subjectSink.GetSubject()) - msg.SetText(sendText) - msg.AddTo(*ci.Email, ci.FullName(false)) - msg.Send() - } else { - log.Warnf("AmDeliverSubscription skipped uid %d because no contact info retrieved (%v)", recipUids[i], err) + select { + case <-ctx.Done(): + log.Errorf("AmDeliverSubscription: aborted on send loop iter %d with %v", i+1, ctx.Err()) + break SendLoop + default: + if ci, err := database.AmGetContactInfoForUser(ctx, recipUids[i]); err == nil { + msg := AmNewEmailMessage(poster.Uid, ipaddr) + msg.SetSubject(subjectSink.GetSubject()) + msg.SetText(sendText) + msg.AddTo(*ci.Email, ci.FullName(false)) + msg.Send() + } else { + log.Warnf("AmDeliverSubscription skipped uid %d because no contact info retrieved (%v)", recipUids[i], err) + } } } } diff --git a/htmlcheck/checker.go b/htmlcheck/checker.go index 692e5d1..905cd70 100644 --- a/htmlcheck/checker.go +++ b/htmlcheck/checker.go @@ -801,168 +801,169 @@ func (ht *htmlCheckerImpl) finishParen() error { func (ht *htmlCheckerImpl) parse(str string) error { i := 0 for i < len(str) { - err := ht.ctx.Err() - if err != nil { - return err - } - ch := str[i] - switch ht.state { - case stateWhitespace: - switch ch { - case ' ', '\t': // append space and tab verbatim - ht.tempBuffer.WriteByte(ch) - i++ - case '\r', '\n': // flush and go to Newline state - ht.doFlushWhitespace() - ht.state = stateNewline - ht.tempBuffer.WriteByte(ch) - i++ - case '<': - ht.doFlushWhitespace() - if ht.config.Angles { - ht.state = stateLeftAngle - } else { - // process < as ordinary character + select { + case <-ht.ctx.Done(): + return ht.ctx.Err() + default: + ch := str[i] + switch ht.state { + case stateWhitespace: + switch ch { + case ' ', '\t': // append space and tab verbatim + ht.tempBuffer.WriteByte(ch) + i++ + case '\r', '\n': // flush and go to Newline state + ht.doFlushWhitespace() + ht.state = stateNewline + ht.tempBuffer.WriteByte(ch) + i++ + case '<': + ht.doFlushWhitespace() + if ht.config.Angles { + ht.state = stateLeftAngle + } else { + // process < as ordinary character + ht.state = stateChars + ht.tempBuffer.WriteByte(ch) + } + i++ + case '(': + ht.doFlushWhitespace() + if ht.config.Parens { + ht.state = stateParen + } else { + // process ( as ordinary character) + ht.state = stateChars + ht.tempBuffer.WriteByte(ch) + } + i++ + case '\\': // backslash processing is tricky - go to Chars state to handle it + ht.doFlushWhitespace() + ht.state = stateChars + default: + ht.doFlushWhitespace() ht.state = stateChars ht.tempBuffer.WriteByte(ch) + i++ } - i++ - case '(': - ht.doFlushWhitespace() - if ht.config.Parens { - ht.state = stateParen - } else { - // process ( as ordinary character) - ht.state = stateChars - ht.tempBuffer.WriteByte(ch) - } - i++ - case '\\': // backslash processing is tricky - go to Chars state to handle it - ht.doFlushWhitespace() - ht.state = stateChars - default: - ht.doFlushWhitespace() - ht.state = stateChars - ht.tempBuffer.WriteByte(ch) - i++ - } - case stateChars: - switch ch { - case ' ', '\t': // go to Whitespace state - _, err := ht.doFlushString() - if err != nil { - return err - } - ht.state = stateWhitespace - ht.tempBuffer.WriteByte(ch) - i++ - case '\r', '\n': // go to Newline state - _, err := ht.doFlushString() - if err != nil { - return err - } - ht.state = stateNewline - ht.tempBuffer.WriteByte(ch) - i++ - case '<': // may be a start of tag - if ht.config.Angles { + case stateChars: + switch ch { + case ' ', '\t': // go to Whitespace state _, err := ht.doFlushString() if err != nil { return err } - ht.state = stateLeftAngle - } else { - ht.tempBuffer.WriteByte(ch) - } - i++ - case '\\': - if i < (len(str) - 1) { - i++ - ch = str[i] - if (ch == '(' && ht.config.Parens) || (ch == '<' && ht.config.Angles) { - // append the escaped character, omitting the backslash - ht.tempBuffer.WriteByte(ch) - i++ - } else { - // append the backslash and hit the new character - ht.tempBuffer.WriteByte('\\') - } - } else { - // just append the backslash normally + ht.state = stateWhitespace ht.tempBuffer.WriteByte(ch) i++ - } - default: // just append the next character - ht.tempBuffer.WriteByte(ch) - i++ - } - case stateLeftAngle: - switch ch { - case ' ', '\t', '\r', '\n': // output <, go to Whitespace state - ht.emitRune('<', ht.outputFilters, true) - ht.state = stateWhitespace - case '<': // output < and stay in this state - ht.emitRune('<', ht.outputFilters, true) - i++ - default: // begin processing tag - ht.state = stateTag - ht.tempBuffer.WriteByte(ch) - i++ - } - case stateTag: - switch ch { - case '>': // finish the tag - this changes the state, and possibly calls parse() recursively - err := ht.finishTag() - if err != nil { - return err - } - i++ - case '\'', '"': // go into "quote string" state inside the tag - ht.tempBuffer.WriteByte(ch) - ht.state = stateTagQuote - ht.quoteChar = ch - i++ - default: // just append the character - ht.tempBuffer.WriteByte(ch) - i++ - } - case stateParen: - switch ch { - case '(': // nest parentheses one level deeper - ht.tempBuffer.WriteByte(ch) - ht.parenLevel++ - i++ - case ')': - if ht.parenLevel == 0 { - err := ht.finishParen() // finish paren, changing state and recursively parsing if necessary + case '\r', '\n': // go to Newline state + _, err := ht.doFlushString() if err != nil { return err } - } else { - // nest parentheses one LESS level deeper + ht.state = stateNewline ht.tempBuffer.WriteByte(ch) - ht.parenLevel-- + i++ + case '<': // may be a start of tag + if ht.config.Angles { + _, err := ht.doFlushString() + if err != nil { + return err + } + ht.state = stateLeftAngle + } else { + ht.tempBuffer.WriteByte(ch) + } + i++ + case '\\': + if i < (len(str) - 1) { + i++ + ch = str[i] + if (ch == '(' && ht.config.Parens) || (ch == '<' && ht.config.Angles) { + // append the escaped character, omitting the backslash + ht.tempBuffer.WriteByte(ch) + i++ + } else { + // append the backslash and hit the new character + ht.tempBuffer.WriteByte('\\') + } + } else { + // just append the backslash normally + ht.tempBuffer.WriteByte(ch) + i++ + } + default: // just append the next character + ht.tempBuffer.WriteByte(ch) + i++ + } + case stateLeftAngle: + switch ch { + case ' ', '\t', '\r', '\n': // output <, go to Whitespace state + ht.emitRune('<', ht.outputFilters, true) + ht.state = stateWhitespace + case '<': // output < and stay in this state + ht.emitRune('<', ht.outputFilters, true) + i++ + default: // begin processing tag + ht.state = stateTag + ht.tempBuffer.WriteByte(ch) + i++ + } + case stateTag: + switch ch { + case '>': // finish the tag - this changes the state, and possibly calls parse() recursively + err := ht.finishTag() + if err != nil { + return err + } + i++ + case '\'', '"': // go into "quote string" state inside the tag + ht.tempBuffer.WriteByte(ch) + ht.state = stateTagQuote + ht.quoteChar = ch + i++ + default: // just append the character + ht.tempBuffer.WriteByte(ch) + i++ + } + case stateParen: + switch ch { + case '(': // nest parentheses one level deeper + ht.tempBuffer.WriteByte(ch) + ht.parenLevel++ + i++ + case ')': + if ht.parenLevel == 0 { + err := ht.finishParen() // finish paren, changing state and recursively parsing if necessary + if err != nil { + return err + } + } else { + // nest parentheses one LESS level deeper + ht.tempBuffer.WriteByte(ch) + ht.parenLevel-- + } + i++ + default: + ht.tempBuffer.WriteByte(ch) + i++ + } + case stateTagQuote: + ht.tempBuffer.WriteByte(ch) + if ch == ht.quoteChar { + ht.state = stateTag } i++ + case stateNewline: + if ch == '\r' || ch == '\n' { + ht.tempBuffer.WriteByte(ch) + i++ + } else { + ht.doFlushNewlines() + } default: - ht.tempBuffer.WriteByte(ch) - i++ + log.Fatalf("invalid parser state: %d", ht.state) } - case stateTagQuote: - ht.tempBuffer.WriteByte(ch) - if ch == ht.quoteChar { - ht.state = stateTag - } - i++ - case stateNewline: - if ch == '\r' || ch == '\n' { - ht.tempBuffer.WriteByte(ch) - i++ - } else { - ht.doFlushNewlines() - } - default: - log.Fatalf("invalid parser state: %d", ht.state) } } return nil