How to abort a net.Dial call in Go? - go

I have a client who try to connect to a server.
I need to be able to terminate the client and abort this Dial attempt. Is this possible ? How could I do that ?
The timeout is apparently longer than 30s since the test blocks until the 30s elapse without a failure of the Dial call.
Can we specify a timeout ourself ?

The net.Dialer has Timeout and Deadline fields, and also can use a context with DialContext which allows for timeout and cancelation.
You can refer to DialTimeout to see how to setup the basic Dialer:
func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
d := Dialer{Timeout: timeout}
return d.Dial(network, address)
}
And an example with a context.Context:
var d Dialer
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return d.DialContext(ctx, network, address)

Related

Context timeout not working as expected in golang

Helo All,
New to golang and was debugging timeout issues in a production environment. Before making a call to the server we add a timeout of 50ms to context and fire a server call. If the response is not received within 50 ms we expect the application to move on and not wait for the response.
But while debugging, I capture the duration between we fire a server call and the response received (or error out), to my surprise the value at the time is much higher than 50 ms.
Client syntax -
ctx, cancel := context.WithTimeout(ctx, e.opts.Timeout)
defer cancel()
fireServerCall(ctx)
..
..
def fireServerCall(ctx context:Context){
startTime:=time.Now()
//call to the server
res, err:=callToServer(ctx)
if err!=nil{
//capture failure latency
return ....
}
//capture success latency
return ....
}
Has anyone ever faced any similar issue? Is this expected behaviour? How did you handle such cases?
Am I doing something incorrectly? Suggestions are welcome :)
Edit:
I am passing context in my original code but forgot to mention it here, just added it. That mean, I am passing the same context on which my client is waiting for server to respond within 50 ms.
You should pass created context to fireServerCall and callToServer functions
callToServer should consider passed context and monitor ctx.Done() channel to stop its execution accordingly
Answering to comment by #Bishnu:
Don't think this is needed. Did a test and even without passing ctx to callToServer() it works. The behaviour is not as expected under high load. Can you kindly share some document/test what you have pointed here?
Context timeout just can't work without context passing and checking its Done() channel. Context is not some kind of magic — simplifying it is just a struct with done channel which is closed by calling cancel function or when timeout occurs. Monitoring this channel — is responsibility of the innermost function that accepts it.
Example:
package main
import (
"context"
"fmt"
"time"
)
func callToServer(ctx context.Context) {
now := time.Now()
select {
case <-ctx.Done(): // context cancelled via cancel or deadline
case <-time.After(1 * time.Second): // emulate external call
}
fmt.Printf("callToServer: %v\n", time.Since(now))
}
func callToServerContextAgnostic(ctx context.Context) {
now := time.Now()
select {
case <-time.After(2 * time.Second): // emulate external call
}
fmt.Printf("callToServerContextAgnostic: %v\n", time.Since(now))
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
callToServer(ctx)
ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel2()
callToServerContextAgnostic(ctx2)
}
Results:
callToServer: 100ms
callToServerContextAgnostic: 2s
You can launch it on Go Playground: https://go.dev/play/p/tIxjHxUzYfh
Note that many of the clients (from standard or third party libraries) monitors Done channel by themselves.
For example standard HTTP client:
c := &http.Client{} // client for all requests
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*100))
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://google.com", nil)
if err != nil {
log.Fatal(err)
}
resp, err := c.Do(req) // will monitor `Done` channel for you
Some docs and articles:
https://pkg.go.dev/context
https://www.digitalocean.com/community/tutorials/how-to-use-contexts-in-go

binance websocket not responding to ping

Using gorilla/websocket I dial the binance websocket endpoint, which succeeds without error. After setting the pong handler on the connection, I write a ping control message and wait for a pong to arrive at the pong handler, which never seems to happen. I use a channel, a context with timeout and a select block to check if the pong arrived.
The code:
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/gorilla/websocket"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn, resp, err := websocket.DefaultDialer.DialContext(ctx, "wss://stream.binance.com:9443/ws", nil)
if resp != nil {
log.Printf("status: %s", resp.Status)
}
if err != nil {
panic(err)
}
pong := make(chan struct{})
conn.SetPongHandler(func(appData string) error {
log.Println(appData)
pong <- struct{}{}
return nil
})
if err := conn.WriteControl(websocket.PingMessage, []byte("Hello, world!"), time.Now().Add(5*time.Second)); err != nil {
panic(err)
}
select {
case <-ctx.Done():
panic(fmt.Errorf("pong wait: %w", ctx.Err()))
case <-pong:
}
}
Output:
$ go run ./cmd/ping
2022/02/07 20:01:23 status: 101 Switching Protocols
panic: pong wait: context deadline exceeded
goroutine 1 [running]:
main.main()
/workspaces/yatgo/cmd/ping/ping.go:39 +0x2ba
exit status 2
As per rfc6455, section-5.5.2:
5.5.2. Ping
The Ping frame contains an opcode of 0x9.
A Ping frame MAY include "Application data".
Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
response, unless it already received a Close frame. It SHOULD
respond with Pong frame as soon as is practical. Pong frames are
discussed in Section 5.5.3.
An endpoint MAY send a Ping frame any time after the connection is
established and before the connection is closed.
NOTE: A Ping frame may serve either as a keepalive or as a means to
verify that the remote endpoint is still responsive.
I kind off expected this to work. Binance websocket API limits doc does mentions ping messages:
WebSocket connections have a limit of 5 incoming messages per second. A message is considered:
A PING frame
So I wonder:
Is something wrong with my code?
Or is binance not respecting RFC6455?
The Gorilla Websocket documentation says:
The application must read the connection to process close, ping and pong messages sent from the peer. If the application is not otherwise interested in messages from the peer, then the application should start a goroutine to read and discard messages from the peer.
Fix the application by starting a goroutine to read the connection before the select statement:
go func() {
defer cancel()
for {
if _, _, err := conn.NextReader(); err != nil {
fmt.Println(err)
return
}
}
}()
select {
⋮
This is a fix for the application shown in the question. If your actual application reads data from the connection in a loop, then you should not add the goroutine shown here. The application should use one read loop to handle control and data messages.

Context.WithTimeout() and os.exit in gorilla/mux

I'm using the Golang gorilla/mux package, and one of the examples is as follows:
func main() {
var wait time.Duration
flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
flag.Parse()
r := mux.NewRouter()
// Add your routes as needed
srv := &http.Server{
Addr: "0.0.0.0:8080",
// Good practice to set timeouts to avoid Slowloris attacks.
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: r, // Pass our instance of gorilla/mux in.
}
// Run our server in a goroutine so that it doesn't block.
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Println(err)
}
}()
c := make(chan os.Signal, 1)
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
signal.Notify(c, os.Interrupt)
// Block until we receive our signal.
<-c
// Create a deadline to wait for.
ctx, cancel := context.WithTimeout(context.Background(), wait)
defer cancel()
// Doesn't block if no connections, but will otherwise wait
// until the timeout deadline.
srv.Shutdown(ctx)
// Optionally, you could run srv.Shutdown in a goroutine and block on
// <-ctx.Done() if your application should wait for other services
// to finalize based on context cancellation.
log.Println("shutting down")
os.Exit(0)
}
This seems simple enough, but my understanding is that defers do not run when os.Exit() is called (as per https://gobyexample.com/exit). I notice that there's a CancelFunc() returned by context.WithTimeout(), which is then deferred. My guess is that this is supposed to cancel the context created above if main() finishes before the deadline, but I don't see how that can happen with a call to os.Exit() at the end. Could anyone help me see what I'm missing?
You right, the deferred cancel function is never called. The author want probably point out that in real application, you should never forget to cancel your context.

Timer implementation for rpc method

I have a Go RPC server that serves client requests. A client requests work (or task) from the server and the server assigns a task to the client. The server expects workers (or clients) to finish any task within a time limit. Therefore a timeout event callback mechanism is required on the server-side.
Here is what I tried so far.
func (l *Listener) RequestHandler(request string, reply string) error {
// some other work
// ....
_timer := time.NewTimer(time.Second * 5) // timer for 2 seconds
go func() {
// simulates a client not replying case, with timeout of 2 sec
y := <-_timer.C
fmt.Println("TimeOut for client")
// revert state changes becasue of client fail
}()
// set reply
// update some states
return nil
}
In the above snippet for each request from a worker (or a client) the handler in the server-side starts a timer and a goroutine. The goroutine reverts the changes done by the handler function before sending a reply to the client.
Is there any way of creating a "set of timers" and blocking wait on the "set of timers" ? Further, whenever a timer expires the blocking wait wakes up and provides us with the timer handles. Depending on the timer type we can perform different expiry handler functions in the runtime.
I am trying to implement a similar mechanism in Go that we can implement in C++ with timerfd with epoll.
Full code for the sample implementation of timers in Go. server.go and client.go.
I suggest you to explored the context package
it can be be done like this:
func main() {
c := context.Background()
wg := &sync.WaitGroup{}
f(c, wg)
wg.Wait()
}
func f(c context.Context, wg *sync.WaitGroup) {
c, _ = context.WithTimeout(c, 3*time.Second)
wg.Add(1)
go func(c context.Context) {
defer wg.Done()
select {
case <-c.Done():
fmt.Println("f() Done:", c.Err())
return
case r := <-time.After(5 * time.Second):
fmt.Println("f():", r)
}
}(c)
}
basically you initiate a base context and then derive other contexts from it, when a context is terminated, either by passing the time or a call to its close, it closes its Done channel and the Done channel of all the contexts that are derived from it.

Context timeout implementation on every request using golang

I am trying to handle a context timeout for every request. We have the following server structures:
Flow overview:
Go Server: Basically, acts as a [Reverse-proxy].2
Auth Server: Check for requests Authentication.
Application Server: Core request processing logic.
Now if Authorization server isn't able to process a request in stipulated time, then I want to close the goroutine from memory.
Here is what I tried:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequest("GET", authorizationServer, nil)
req.Header = r.Header
req.WithContext(ctx)
res, error := client.Do(req)
select {
case <-time.After(10 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
Over here, context returns as "deadline exceeded", if the request is not processed in stipulated time. But it continues to process that request and return response in more than the specified time. So, how can I stop the request flow (goroutine), when time is exceeded.
I've also the complete request needs to be processed in 60 seconds with this code:
var netTransport = &http.Transport{
Dial: (&net.Dialer{
Timeout: 60 * time.Second,
}).Dial,
TLSHandshakeTimeout: 60 * time.Second,
}
client := &http.Client{
Timeout: time.Second * 60,
Transport: netTransport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
So do I need any separate context implementations as well?
Note1: It would be awesome, if we can manage the timeout on every requests (goroutine) created by HTTP server, using context.
What happens in your code is very correct and behaves as expected.
You create a context with 5 seconds timeout. You pass it to the request and make that request. Let's say that request returns in 2 seconds. You then do a select and either wait 10 seconds or wait for the context to finish. Context will always finish in the initial 5 seconds from when it was created and will also give that error every time it reaches the end.
The context is independent of the request and it will reach its deadline unless cancelled previously. You cancel the request when the function finishes using defer.
In your code the request takes your timeout in consideration. But the ctx.Err() will return deadline exceeded every time it reaches the timeout. Since that's what happens inside the context. calling ctx.Err() multiple times will return the same error.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go func () {
select {
case <-time.After(10 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
}()
req, _ := http.NewRequest("GET", authorizationServer, nil)
req.Header = r.Header
req = req.WithContext(ctx)
res, error := client.Do(req)
From the context documentation:
// Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled or DeadlineExceeded if the
// context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
In your code, the timeout will always be reached and not cancelled, that is why you receive DeadlineExceeeded. Your code is correct except the select part which will block until either 10 seconds pass or context timeout is reached. In your case always the context timeout is reached.
You should check the error returned by the client.Do call and not worry about the context error in here. You are the one controlling the context. If the request times out, a case you should test of course, then a proper error would be returned for you to verify.

Resources