无法连接时停止频道

Posted

技术标签:

【中文标题】无法连接时停止频道【英文标题】:Stop channels when ws not able to connect 【发布时间】:2019-11-03 15:26:21 【问题描述】:

我有以下代码可以正常工作,问题是当socket.Connect() fails 连接我想停止该进程,我尝试使用以下代码 但它不起作用,即如果套接字连接失败,程序仍然运行。 我想要发生的是,如果 connect 失败,进程 stopschanne ......我在这里错过了什么?

func run (appName string) (err error) 


        done = make(chan bool)
        defer close(done)


        serviceURL, e := GetContext().getServiceURL(appName)

        if e != nil 
            err = errors.New("process failed" + err.Error())
            LogDebug("Exiting %v func[err =%v]", methodName, err)
            return err
        

        url := "wss://" + serviceURL + route


        socket := gowebsocket.New(url)
        addPass(&socket, user, pass)


        socket.OnConnectError = OnConnectErrorHandler
        socket.OnConnected = OnConnectedHandler
        socket.OnTextMessage = socketTextMessageHandler
        socket.OnDisconnected = OnDisconnectedHandler

        LogDebug("In %v func connecting to URL  %v", methodName, url)
        socket.Connect()

        jsonBytes, e := json.Marshal(payload)
        if e != nil 
            err = errors.New("build process failed" + e.Error())
            LogDebug("Exiting %v func[err =%v]", methodName, err)
            return err
        

        jsonStr := string(jsonBytes)

        LogDebug("In %v Connecting to payload JSON is  %v", methodName, jsonStr)
        socket.SendText(jsonStr)

        <-done
        LogDebug("Exiting %v func[err =%v]", methodName, err)
        return err

    


    func OnConnectErrorHandler(err error, socket gowebsocket.Socket) 
        methodName := "OnConnectErrorHandler"
        LogDebug("Starting %v parameters [err = %v , socket = %v]", methodName, err, socket)
        LogInfo("Disconnected from server ")
        done <- true
    

该进程应该为大约运行 60-90 sec 的进程打开一个 ws 连接(例如执行 npm install),并通过 web socket 获取进程的日志,并在它完成时处理,当然可以处理可能的问题像网络问题或运行进程的一些错误一样发生

【问题讨论】:

你在这里使用的 gowebsocket 包是什么? (看起来像这个:github.com/sacOO7/GoWebsocket) @Slabgorb - 是的,如果你认为它是相关的,你能提供一种如何替换它的方法吗?因为答案会很棒 【参考方案1】:

所以,@Slabgorb 是正确的 - 如果您查看此处 (https://github.com/sacOO7/GoWebsocket/blob/master/gowebsocket.go#L87),您会看到在执行对 Connect() 的调用期间同步调用了 OnConnectErrorHandlerConnect() 函数不会启动一个单独的 goroutine 来处理 websocket,直到连接完全建立并且 OnConnected 回调完成之后。因此,当您尝试写入无缓冲通道 done 时,您将阻塞调用 run() 函数的 same goroutine,并且您会陷入僵局,因为永远不会有 goroutine能够从频道中读取以解除对您的阻止。

因此,您可以采用他的解决方案并将其转换为缓冲通道,这将起作用,但我的建议是不要为这种一次性标志行为写入通道,而是使用close 信号反而。为要终止的每个条件定义一个通道run(),并在相应的 websocket 处理程序函数中,close 条件发生时的通道。在run()的底部,您可以在所有频道上select,并在第一个频道关闭时退出。它看起来像这样:

package main

import "errors"

func run(appName string) (err error) 

    // first, define one channel per socket-closing-reason (DO NOT defer close these channels.)
    connectErrorChan := make(chan struct)
    successDoneChan := make(chan struct)
    surpriseDisconnectChan := make(chan struct)

    // next, wrap calls to your handlers in a closure `https://gobyexample.com/closures`
    // that captures a reference to the channel you care about
    OnConnectErrorHandler := func(err error, socket gowebsocket.Socket) 
        MyOnConnectErrorHandler(connectErrorChan, err, socket)
    
    OnDisconnectedHandler := func(err error, socket gowebsocket.Socket) 
        MyOnDisconectedHandler(surpriseDisconnectChan, err, socket)
    
    // ... declare any other handlers that might close the connection here

    // Do your setup logic here
    // serviceURL, e := GetContext().getServiceURL(appName)
    // . . .
    // socket := gowebsocket.New(url)

    socket.OnConnectError = OnConnectErrorHandler
    socket.OnConnected = OnConnectedHandler
    socket.OnTextMessage = socketTextMessageHandler
    socket.OnDisconnected = OnDisconnectedHandler

    // Prepare and send your message here...
    // LogDebug("In %v func connecting to URL  %v", methodName, url)
    // . . .
    // socket.SendText(jsonStr)

    // now wait for one of your signalling channels to close.
    select  // this will block until one of the handlers signals an exit
    case <-connectError:
        err = errors.New("never connected  :( ")
    case <-successDone:
        socket.Close()
        LogDebug("mission accomplished! :) ")
    case <-surpriseDisconnect:
        err = errors.New("somebody cut the wires!  :O ")
    

    if err != nil 
        LogDebug(err)
    
    return err


// *Your* connect error handler will take an extra channel as a parameter
func MyOnConnectErrorHandler(done chan struct, err error, socket gowebsocket.Socket) 
    methodName := "OnConnectErrorHandler"
    LogDebug("Starting %v parameters [err = %v , socket = %v]", methodName, err, socket)
    LogInfo("Disconnected from server ")
    close(done) // signal we are done.

这有几个优点:

1) 您无需猜测哪些回调发生在进程中,哪些发生在后台 goroutines 中(并且您不必让所有通道“以防万一”进行缓冲)

2) 在多个频道上进行选择可以让您了解为什么您要退出,并且可能会以不同的方式处理清理或日志记录。

注意 1:如果您选择使用 close 信号,您必须为每个源使用不同的通道,以避免可能导致通道从不同的 goroutine 关闭两次的竞争条件(例如,当您返回响应时会发生超时,并且两个处理程序都会触发;关闭同一通道的第二个处理程序会导致panic。)这也是您不想defer close 的所有通道的原因函数的顶部。

注意 2:与您的问题没有直接关系,但是 - 您不需要 关闭每个频道 - 一旦它的所有句柄超出范围,该频道将被垃圾收集是否已关闭。

【讨论】:

谢谢Note 1,我不确定我是否完全明白,这就是我们遇到问题时得到的panic,你建议如何避免它? 这是你得到的恐慌吗? panic: close of closed channel 在 Go 中,关闭已关闭的通道会导致恐慌。这就是为什么我建议为每个关闭的原因创建一个不同的通道:每个通道应该只关闭一次(即你的 OnDisconnect 处理程序应该只被调用一次。)找出哪些通道被关闭不止一次以及为什么。 #### 旁注:我复制了您的示例并对其进行了修改以显示通道关闭的位置,但我现在意识到我错过了 如何 通道被传递给回调。将更新这部分... @RaynD - 我更新了示例代码以展示如何使用闭包使通道引用可用于回调处理程序。 非常感谢! ,我提供赏金,因为你已经做了很棒的工作!目前我经常使用此代码,我认为它与开源本身有关,所以我想切换到gorrila/ws,如果您可以调整代码以使用gorrila/ws 作为示例unit-test,那就太好了就像我可以用作 e2e 来更好地调试和理解的代码:)。 我不确定我是否有时间在这里写一个完整的 E2E 示例,但如果这是您正在寻找的,我认为您需要提供更多关于您的真实应用程序的正常工作流程的上下文:您是否希望通过 websocket 连接、发送消息、获得响应并退出?还是保持联系并服务请求?你真的希望你调用一个方法并让它阻塞直到 websocket 连接关闭吗?等等。【参考方案2】:

好的,当您尝试向其添加内容时,频道被阻塞了。尝试使用这样的缓冲区(我使用 1)初​​始化 done 通道:

done = make(chan bool, 1)

【讨论】:

谢谢,使用您的建议导致出现连接问题的恐慌,有没有办法避免它? 嗨@RaynD - 恐慌来自哪里,websocket 包还是你的代码? 嗨,来自网络套接字 对不起@raynd,需要一个堆栈跟踪或其他东西来帮助你

以上是关于无法连接时停止频道的主要内容,如果未能解决你的问题,请参考以下文章

订阅 Action Cable 频道时如何设置参数

当服务器连接丢失时,如何自行停止和重新启动客户端的服务?

频道应用程序无故停止工作 unitil ASGI 服务器重新启动

无法使用与 Gradle 分发“xxxx”的连接,因为它已停止

发生binance api(连接)错误时无法继续程序

Adobe Flash Builder 调试器无法连接,无法进行调试,构建停止在57%