当连接关闭时,为什么此接收器转到例程拒绝终止
这将按预期运行,但是随后每调用20-10,000x次,接收器将无法关闭,从而导致go例程泄漏,从而导致100%cpu。
注意:如果记录所有错误,则如果conn.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
}
}
我需要一种可靠的方法来使接收器在收到关闭广播或关闭conn时关闭。从功能上讲,关闭通道应该足够了,根据go软件包documentation,这是首选方法,请参见Conn接口。
我升级到最新版本,即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)的阻塞,似乎基本上是同一回事。
当这是一个合法且可验证的可重复问题时,降级我的问题无济于事,该问题仍未得到答复。
答案 0 :(得分:0)
感谢大家的答复。
所以。从来没有堆栈跟踪。实际上,我根本没有任何错误,没有种族检测或其他任何东西,并且没有死锁,执行例程不会关闭并退出,并且不能始终如一地再现。我已经运行了两个星期的相同数据。
当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...