草庐IT

go - 为什么这个 go 例程在关闭阻塞读取连接时随机无法退出?

coder 2024-07-08 原文

为什么这个接收者在连接关闭时进入例程拒绝终止

这按预期运行,但随后随机地,每调用 20-10,000 次,接收器将无法关闭,然后导致 go routine 泄漏,导致 100% cpu。

注意:如果我记录所有错误,如果 conn.SetReadDeadline 被注释掉,我将在关闭的 channel 上看到读取。使用时,我将 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 关闭时关闭。从功能上讲,关闭 channel 应该足够了,并且根据 go 包 documentation 是首选方法。 ,请参见 Conn 接口(interface)。

我升级到最新的 go,即 1.12.1,没有任何变化。 在开发中运行在 MacOS 上,在生产中运行在 CentOS 上。

有人遇到过这个问题吗? 如果是这样,您是如何可靠地修复它的?


可能的解决方案

我的非常冗长和棘手的解决方案似乎可能有效,作为一种解决方法,就是这样做:

1) 在 go routine 中启动发送器,像这样(如上,不变)

2) 在 go routine 中启动接收器,像这样(如下)

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...

关于go - 为什么这个 go 例程在关闭阻塞读取连接时随机无法退出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55486000/

有关go - 为什么这个 go 例程在关闭阻塞读取连接时随机无法退出?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  5. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  7. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  8. ruby-on-rails - 无法使用 Rails 3.2 创建插件? - 2

    我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby​​1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在

  9. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  10. ruby-on-rails - 无法在centos上安装therubyracer(V8和GCC出错) - 2

    我正在尝试在我的centos服务器上安装therubyracer,但遇到了麻烦。$geminstalltherubyracerBuildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingtherubyracer:ERROR:Failedtobuildgemnativeextension./usr/local/rvm/rubies/ruby-1.9.3-p125/bin/rubyextconf.rbcheckingformain()in-lpthread...yescheckingforv8.h...no***e

随机推荐