为什么这个接收器在连接关闭时例行拒绝终止
这按预期运行,但随后随机地,每调用 20-10,000 倍,接收器将无法关闭,从而导致 go 例程泄漏,导致 100% CPU。
注意:如果我记录所有错误,如果连接,我将在关闭的频道上看到读取。SetReadDeadline被注释掉了。使用时,我认为 I/O 超时是错误。
这运行了 10k 个周期,其中主进程启动 11 对这些发送/接收器,并在主进程发送关闭信号之前处理 1000 个作业。这个设置运行了> 6 小时,一夜之间到 10k 个周期标记没有任何问题,但今天早上我无法让它运行超过 20 个周期而不会将接收器标记为未关闭和退出。
func sender(w worker, ch channel) {
var j job
for {
select {
case <-ch.quit: // shutdown broadcast, exit
w.Close()
ch.stopped <- w.id // debug, send stop confirmed
return
case j = <-w.job: // worker designated jobs
case j = <-ch.spawner: // FCFS jobs
}
... prepare job ...
w.WriteToUDP(buf, w.addr)
}
func receiver(w worker, ch channel) {
deadline := 100 * time.Millisecond
out:
for {
w.SetReadDeadline(time.Now().Add(deadline))
// blocking read, should release on close (or deadline)
n, err = w.c.Read(buf)
select {
case <-ch.quit: // shutdown broadcast, exit
ch.stopped <- w.id+100 // debug, receiver stop confirmed
return
default:
}
if n == 0 || err != nil {
continue
}
update := &update{id: w.id}
... process update logic ...
select {
case <-ch.quit: // shutting down
break out
case ch.update <- update
}
}
我需要一种可靠的方法让接收器在关闭广播或连接关闭时关闭。从功能上讲,关闭通道应该足够了,并且根据 go 包文档是首选方法,请参阅 Conn 接口。
我升级到最新的go,即1.12.1,没有变化。 在开发中的 MacOS 和生产中的 CentOS 上运行。
遇到这个问题了吗? 如果是这样,您是如何可靠地修复它的?
可能的解决方案
我非常冗长和令人讨厌的解决方案似乎可能起作用,作为一种解决方法,是这样做:
1) 在 Go 例程中启动发送方,像这样(上面,不变)
2) 在 Go 例程中启动接收器,如下所示(下图)
func receive(w worker, ch channel) {
request := make(chan []byte, 1)
reader := make(chan []byte, 1)
defer func() {
close(request) // exit signaling
w.c.Close() // exit signaling
//close(reader)
}()
go func() {
// untried senario, for if we still have leaks -> 100% cpu
// we may need to be totally reliant on closing request or ch.quit
// defer w.c.Close()
deadline := 100 * time.Millisecond
var n int
var err error
for buf := range request {
for {
select {
case <-ch.quit: // shutdown signal
return
default:
}
w.c.SetReadDeadline(time.Now().Add(deadline))
n, err = w.c.Read(buf)
if err != nil { // timeout or close
continue
}
break
}
select {
case <-ch.quit: // shutdown signal
return
case reader <- buf[:n]:
//default:
}
}
}()
var buf []byte
out:
for {
request <- make([]byte, messageSize)
select {
case <-ch.quit: // shutting down
break out
case buf = <-reader:
}
update := &update{id: w.id}
... process update logic ...
select {
case <-ch.quit: // shutting down
break out
case ch.update <- update
}
}
我的问题是,为什么这个可怕的版本 2,它生成一个新的 go 例程以从阻塞中读取 c.Read(buf) 似乎工作得更可靠,这意味着当发送关闭信号时它不会泄漏,而更简单的第一个版本没有......由于阻塞 c.Read(buf),它似乎本质上是相同的。
当这是一个合法且可验证的可重复问题时,降级我的问题没有帮助,这个问题仍然没有答案。
感谢大家的回复。
所以。从来没有堆栈跟踪。事实上,我根本没有收到任何错误,没有种族检测或任何东西,它没有死锁,一个 go 例程不会关闭和退出,而且它不能始终如一地重现。我已经运行相同的数据两周了。
当 go 例程无法报告正在退出时,它会简单地失控并将 CPU 驱动到 100%,但只有在所有其他例程退出并且系统继续前进之后。我从未看到记忆增长。CPU将逐渐上升到200%,300%,400%,这就是系统必须重新启动的时候。
我在发生泄漏时记录,它总是不同的,我会在之前成功运行 380 次(统一运行的 23 对 go 例程)后出现一次泄漏,下一次 1832 次在一个接收器泄漏之前,下一次只有 23 次,在同一起点咀嚼完全相同的数据。泄漏的接收器刚刚失控,但只有在其他 22 名同伴全部关闭并成功退出并且系统移动到下一批之后。它不会始终如一地失败,除了保证在某个时候泄漏。
经过许多天,无数次重写,以及每次操作前后的一百万个日志,这似乎终于是问题所在,在挖掘库之后,我不确定为什么,也不确定为什么它只是随机发生的。
无论出于何种原因,如果您解析并直接跳过问题而不先阅读问题,golang.org/x/net/dns/dnsmessage 库都会随机吓坏。不知道为什么这很重要,你好,跳过问题意味着您不关心该标题部分并将其标记为已处理,并且它连续一百万次工作正常,但随后并非如此,因此您似乎需要先阅读一个问题,然后才能跳过所有问题,因为这似乎是解决方案。我有 18,525 批,添加后关闭了泄漏。
var p dnsmessage.Parser
h, err := p.Start(buf[:n])
if err != nil {
continue // what!?
}
switch {
case h.RCode == dnsmessage.RCodeSuccess:
q, err := p.Question() // should only have one question
if q.Type != w.Type || err != nil {
continue // what!?, impossible
}
// Note: if you do NOT do the above first, you're asking for pain! (tr)
if err := p.SkipAllQuestions(); err != nil {
continue // what!?
}
// Do not count as "received" until we have passed above point and
// validated that response had a question that we could skip...