GetStream / stream-chat-go

Stream Chat official Golang API Client
https://getstream.io/chat/
BSD 3-Clause "New" or "Revised" License
67 stars 31 forks source link

unexpected EOF with successive requests #217

Closed dillonstreator closed 2 years ago

dillonstreator commented 2 years ago

Getting the error unexpected EOF when making successive calls.

I believe the open connection is being reused and is unexpectedly being closed by stream. We probably want to set the Close boolean to true when creating the request. https://cs.opensource.google/go/go/+/refs/tags/go1.18:src/net/http/request.go;l=208-218

ferhatelmas commented 2 years ago

It has a performance hit, that's why we don't want to do it by default.

https://github.com/GetStream/stream-chat-go/releases/tag/v5.0.0 and above has good defaults to handle it and if really needed, you can override the client as you wish https://github.com/GetStream/stream-chat-go/commit/328e7677ac5c32496c7cedaf7ca51e8fcf2dbed3#diff-4b667feae66c9d46b21b9ecc19e8958cf4472d162ce0a47ac3e8386af8bbd8cfR95

sw360cab commented 2 years ago

@ferhatelmas and @DillonStreator
I am experiencing a similar issue, when updating chat channels using batch tasks. In order to avoid hitting API Rate Limits I created a sort of timed-barrier which allows any task to proceed only if api rate limit has not been reached, otherwise the task will wait until a timer has expired.

It works but when the batch following the first proceeds, all the calls end unexpectedly with https://chat.stream-io-api.com/channels?api_key=API_KEY --> unexpected EOF

I tried increasing the HTTP client timeout via the env variable STREAM_CHAT_TIMEOUT, but it does not change anything. Can you suggest me how to deal with that or point me to any example. The official doc does not seem to be very consistent with this specific issue.

dillonstreator commented 2 years ago

@sw360cab My solution was to roll a custom http client and wrap the transport to intercept the request and set the Close property to true. The performance hit of not keeping the connection open is not a concern for my implementation.

This is our custom http client with the transport wrapping.

import (
    httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
)

func ...() {

    httpClient := &http.Client{
        Timeout: 30 * time.Second,

        // default transport options from https://go.dev/src/net/http/transport.go
        // don't use http.DefaultTransport as we don't want to set req.Close for every other request that uses the default transport
        Transport: &http.Transport{
            Proxy: http.ProxyFromEnvironment,
            DialContext: (&net.Dialer{
                Timeout:   30 * time.Second,
                KeepAlive: 30 * time.Second,
            }).DialContext,
            ForceAttemptHTTP2:     true,
            MaxIdleConns:          100,
            IdleConnTimeout:       90 * time.Second,
            TLSHandshakeTimeout:   10 * time.Second,
            ExpectContinueTimeout: 1 * time.Second,
        },
    }

    httpClient.Transport = httptrace.WrapRoundTripper(httpClient.Transport, []httptrace.RoundTripperOption{
        httptrace.WithBefore(func(req *http.Request, span ddtrace.Span) {
            // ensure the connection is closed after request ends to prevent EOF errors
            // https://github.com/GetStream/stream-chat-go/issues/217
            req.Close = true

            span.SetTag(ext.ServiceName, "our-service-name")
            span.SetTag(ext.SpanType, ext.SpanTypeHTTP)
            span.SetTag(ext.HTTPMethod, req.Method)
            span.SetTag(ext.HTTPURL, req.URL.Path)
        }),
    }...)

    streamClient.SetClient(httpClient)

    return streamClient
}

wrapping the transport was pretty easy with the httptrace.WrapRoundTripper and httptrace.WithBefore helper methods from gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http since we wanted to configure datadog tracing anyway.