Golang http: паническое обслуживание: закрытие закрытого канала

# #go

Вопрос:

Пытаясь очистить мой код, код GetDecisListeners() ранее был внутри LiveDecision() без панической ошибки.

 //GetDecisListener return listener subscribed to channels
func GetDecisListener(dStructs map[string]map[string]interface{}) (chan interface{}, error) {
    listener := make(chan interface{})
    for r, _ := range dStructs {
        DecisChannels[r].Register(listener)
        key := r
        defer func() {
            Decision(key).Unregister(listener)
            close(listener)
        }()
        fmt.Printf("Done %vn", r)
    }
    fmt.Print("Done insiden")
    return listener, nil
}
func LiveDecision(ctx *gin.Context) {
    ...
    listener, err := GetDecisListener(dStructs)
    fmt.Print("Done outsiden")    // cannot reach here
    if err != nil {return}

    ctx.Stream(func(w io.Writer) bool {
        select {
        case msg := <-listener:
            return true
            ...
    }}
}
 

dStructs это просто карта 2D-интерфейса; Decision и DecisChannels на нее ссылаются отсюда.

 var DecisChannels = make(map[string]broadcast.Broadcaster)
var DecisCols = make(map[string]string)

func OpenListener(decisid string) chan interface{} {
    ite := make(chan interface{})
    Decision(decisid).Register(ite)
    return ite
}

func CloseListener(decisid string, ite chan interface{}) {
    Decision(decisid).Unregister(ite)
    close(ite)
}

func Decision(decisid string) broadcast.Broadcaster {
    b, ok := DecisChannels[decisid]
    if !ok {
        b = broadcast.NewBroadcaster(10)
        DecisChannels[decisid] = b
    }
    return b
}

 

Журнал показывает, что он не достиг Done outside , но показывает http:panic после GetDecisListener() .

 Done item1
Done item2
Done item3
Done item4
Done inside
2021/09/14 14:50:45 http: panic serving 127.0.0.1:39450: close of closed channel
goroutine 40 [running]:
net/http.(*conn).serve.func1(0xc0000c4280)
        /usr/lib/go-1.13/src/net/http/server.go:1767  0x139
panic(0xd271c0, 0xf5c2b0)
        /usr/lib/go-1.13/src/runtime/panic.go:679  0x1b2
somewhere/internal/app/route/client.GetDecisListener.func1(0xc0001d8290, 0xe, 0xc0003660c0)
        elsewhere/internal/app/route/client/client.go:42  0x68
panic(0xd271c0, 0xf5c2b0)
        /usr/lib/go-1.13/src/runtime/panic.go:679  0x1b2
somewhere/internal/app/route/client.GetDecisListener.func1(0xc0001d8300, 0xd, 0xc0003660c0)
        elsewhere/internal/app/route/client/client.go:42  0x68
panic(0xd271c0, 0xf5c2b0)
        /usr/lib/go-1.13/src/runtime/panic.go:679  0x1b2
somewhere/internal/app/route/client.GetDecisListener.func1(0xc0001d8390, 0xd, 0xc0003660c0)
        elsewhere/internal/app/route/client/client.go:42  0x68
somewhere/internal/app/route/client.GetDecisListener(0xc0001228a0, 0xc0003660c0, 0x0, 0x0)
        elsewhere/internal/app/route/client/client.go:47  0x27f
somewhere/internal/app/route/client.LiveDecision(0xc0000f8200)
        elsewhere/internal/app/route/client/client.go:77  0xb0
github.com/gin-gonic/gin.(*Context).Next(0xc0000f8200)
        /home/simon/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165  0x3b
github.com/gin-gonic/gin.(*Engine).handleHTTPRequest(0xc000470000, 0xc0000f8200)
        /home/simon/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go:489  0x614
github.com/gin-gonic/gin.(*Engine).ServeHTTP(0xc000470000, 0xf7cdc0, 0xc0001020e0, 0xc0000f8100)
        /home/simon/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go:445  0x15d
net/http.serverHandler.ServeHTTP(0xc00046e0e0, 0xf7cdc0, 0xc0001020e0, 0xc0000f8100)
        /usr/lib/go-1.13/src/net/http/server.go:2802  0xa4
net/http.(*conn).serve(0xc0000c4280, 0xf7ff00, 0xc00004a040)
        /usr/lib/go-1.13/src/net/http/server.go:1890  0x875
created by net/http.(*Server).Serve
        /usr/lib/go-1.13/src/net/http/server.go:2928  0x384
 

Я подозреваю defer func() Decision(key) , что это вызвало панику. Я бы понятия не имел, куда его девать, если это так.

Комментарии:

1. Вам нужно закрыть и отменить регистрацию вашего слушателя в другом месте, прямо сейчас GetDecisListener возвращается закрытый канал.

2. о…я вижу….слушатели закрыты, как только он покинул GetDecisListener()…

Ответ №1:

Вы не можете закрыть канал дважды: это вызовет панику.

 listener := make(chan interface{})
for r, _ := range dStructs {
    DecisChannels[r].Register(listener)
    key := r
    defer func() {
        Decision(key).Unregister(listener)
        close(listener)
    }()
    fmt.Printf("Done %vn", r)
}
 

Здесь , если len(dStructs) > 1 два отложенных вызова попытаются закрыть listener

Может быть, вы сможете сделать что-то вроде:

 listener := make(chan interface{})
shouldClose := false
for r, _ := range dStructs {
    DecisChannels[r].Register(listener)
    key := r
    defer func() {
        Decision(key).Unregister(listener)
    }()
    shouldClose = true
    fmt.Printf("Done %vn", r)
}
if shouldClose {
    close(listener)    
}
 

Комментарии:

1. хорошо, спасибо, похоже, что отсрочка close(listener) должна быть добавлена после ее объявления. И нет смысла вытаскивать эти коды из LiveDecision()