英文:
Golang http: panic serving: close of closed channel
问题
尝试清理我的代码,GetDecisListeners() 函数中的代码以前是在 LiveDecision() 函数内部而没有恐慌错误的。
// GetDecisListener 返回订阅频道的监听器
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 %v\n", r)
    }
    fmt.Print("Done inside\n")
    return listener, nil
}
func LiveDecision(ctx *gin.Context) {
    ...
    listener, err := GetDecisListener(dStructs)
    fmt.Print("Done outside\n")    // 无法到达此处
    if err != nil {return}
    ctx.Stream(func(w io.Writer) bool {
        select {
        case msg := <-listener:
            return true
            ...
    }}
}
dStructs 只是一个二维接口映射;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
}
日志显示在 GetDecisListener() 之后没有到达 Done outside,而是显示了 http:panic 错误。
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
我怀疑 Decision(key) 的 defer func() 导致了 panic 错误。如果是这种情况,我不知道应该把它放在哪里。
英文:
Trying to clean up my code, the code in GetDecisListeners() was previously inside LiveDecision() without panic error.
//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 %v\n", r)
	}
	fmt.Print("Done inside\n")
	return listener, nil
}
func LiveDecision(ctx *gin.Context) {
    ...
	listener, err := GetDecisListener(dStructs)
	fmt.Print("Done outside\n")    // cannot reach here
	if err != nil {return}
	ctx.Stream(func(w io.Writer) bool {
		select {
		case msg := <-listener:
            return true
            ...
    }}
}
dStructs is just an 2D interface map; Decision and DecisChannels are referred from here.
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
}
The log shows it hadn't reach Done outside but shows http:panic after the 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
I suspect defer func() of the Decision(key) causing the panic. I would have no idea where to put it if this is the case.
答案1
得分: 1
你不能两次关闭一个通道,否则会引发 panic。
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 %v\n", 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 %v\n", r)
}
if shouldClose {
    close(listener)    
}
英文:
You cannot close a channel twice: it will panic.
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 %v\n", r)
}
Here if len(dStructs) > 1, two defered call will try to close listener
Maybe you can do something like:
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 %v\n", r)
}
if shouldClose {
    close(listener)    
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论