# #go #backend
Вопрос:
У меня есть следующий код для модуля, который я разрабатываю, и я не уверен, почему provider.Shutdown()
функция никогда не вызывается при вызове .Stop()
Основной процесс останавливается, но я не понимаю, почему это не работает?
package pluto
import (
"context"
"fmt"
"log"
"sync"
)
type Client struct {
name string
providers []Provider
cancelCtxFunc context.CancelFunc
}
func NewClient(name string) *Client {
return amp;Client{name: name}
}
func (c *Client) Start(blocking bool) {
log.Println(fmt.Sprintf("Starting the %s service", c.name))
ctx, cancel := context.WithCancel(context.Background())
c.cancelCtxFunc = cancel // assign for later use
var wg sync.WaitGroup
for _, p := range c.providers {
wg.Add(1)
provider := p
go func() {
provider.Setup()
select {
case <-ctx.Done():
// THIS IS NEVER CALLED?!??!
provider.Shutdown()
return
default:
provider.Run(ctx)
}
}()
}
if blocking {
wg.Wait()
}
}
func (c *Client) RegisterProvider(p Provider) {
c.providers = append(c.providers, p)
}
func (c *Client) Stop() {
log.Println("Attempting to stop service")
c.cancelCtxFunc()
}
Код клиента
package main
import (
"pluto/pkgs/pluto"
"time"
)
func main() {
client := pluto.NewClient("test-client")
testProvider := pluto.NewTestProvider()
client.RegisterProvider(testProvider)
client.Start(false)
time.Sleep(time.Second * 3)
client.Stop()
}
Комментарии:
1. Вполне вероятно, что программа завершится до того, как оператор select сможет запуститься.
2. Не говоря уже о том , что вы почти гарантированно окажетесь в
default
деле, заблокированномprovider.Run
, так как же вы могли когда-либо вернуться к этому<-ctx.Done():
делу?3. @BurakSerdar Я думаю, что ты прав. Я только что понял, что если я проведу время. Спите после вызова функции отмены, затем код работает. Можно ли установить 30-секундный максимальный тайм-аут, но отменить его раньше, если все закончено?
Ответ №1:
Потому что он уже выбрал другой case
, прежде чем контекст будет отменен. Вот ваш код с комментариями:
// Start a new goroutine
go func() {
provider.Setup()
// Select the first available case
select {
// Is the context cancelled right now?
case <-ctx.Done():
// THIS IS NEVER CALLED?!??!
provider.Shutdown()
return
// No? Then call provider.Run()
default:
provider.Run(ctx)
// Run returned, nothing more to do, we're not in a loop, so our goroutine returns
}
}()
После provider.Run
вызова отмена контекста ничего не сделает в показанном коде. provider.Run
также получает контекст, поэтому он может обрабатывать отмену так, как считает нужным. Если вы хотите, чтобы в вашей программе также наблюдалась отмена, вы можете включить это в цикл:
go func() {
provider.Setup()
for {
select {
case <-ctx.Done():
// THIS IS NEVER CALLED?!??!
provider.Shutdown()
return
default:
provider.Run(ctx)
}
}
}()
Таким образом, как только provider.Run
он вернется, он повторится select
снова, и если контекст был отменен, это обращение будет вызвано. Однако, если контекст не был отменен, он вызовет provider.Run
снова, что может быть или не быть тем, что вы хотите.
Редактировать:
Более типично, что у вас будет один из нескольких сценариев, в зависимости от того, как provider.Run
и provider.Shutdown
как это работает, что не было ясно указано в вопросе, поэтому вот ваши варианты:
Shutdown
должен вызываться при отмене контекста и Run
должен вызываться только один раз:
go func() {
provider.Setup()
go provider.Run(ctx)
go func() {
<- ctx.Done()
provider.Shutdown()
}()
}
Или Run
, который уже получает контекст, уже делает то же самое, Shutdown
что и при отмене контекста, и поэтому вызов Shutdown
при отмене контекста совершенно не нужен:
go provider.Run(ctx)
Комментарии:
1. Спасибо! у тебя есть какие-нибудь идеи, как бы я просто добрался до провайдера. Бежать, чтобы бежать один раз? (провайдер. Запуск будет потенциально заблокирован, например, он может запустить HTTP-сервер внутри)
2. @Calum: есть ли причина, по которой вы не просто работаете
<-ctx.Done(); provider.Shutdown()
одновременно?3. Вы могли бы использовать флаг, на самом деле существует множество решений, но, по сути, этот дизайн, скорее всего, неверен с самого начала. В частности, я задаюсь вопросом, нужно ли вам звонить
provider.Shutdown
: а) еслиprovider.Run
уже был вызван и возвращен, и б) если переданный контекстprovider.Run
был отменен. Я бы подумал, что в обоих случаях ответ «Нет», что означает, что вы можете заменить весь цикл иselect
простоprovider.Run(ctx)
.4. @Calum см. правки — в типичном коде Go здесь есть две общие возможности, одна из которых заключается в том, что Джим, а другая в том, что ни один из этого кода не нужен.
5. Спасибо, Адриан, я ценю твою помощь.