Aula 30 – Tutorial Golang – Timeouts

Aula 30 – Tutorial Golang – Timeouts

Tutorial Golang - Channel Synchronization

Tutorial Golang – Channel Synchronization

Sempre compartilho com vocês conhecimento gratuito, é a missão principal do código fluente.

Mas, não posso deixar de compartilhar com vocês também, uma oportunidade única de elevar seus conhecimentos em programação para o próximo nível.

Estou falando do curso Master Full Stack 👇 Cliquem e Confiram!

Pacote Programador Fullstack

Pacote Programador Fullstack

Página principal do blog

Todas as aulas desse curso

Aula 29                        Aula 31

Redes Sociais:

facebook

Meus links de afiliados:

Hostinger

Digital Ocean

One.com

Melhore seu NETWORKING

https://digitalinnovation.one/

Participe de comunidades de desenvolvedores:

Fiquem a vontade para me adicionar ao linkedin.

E também para me seguir no https://github.com/toticavalcanti.

Código final da aula:

https://github.com/toticavalcanti

Canais do Youtube:

Toti

Backing Track / Play-Along

Código Fluente

Putz!

Vocal Techniques and Exercises

PIX para doações

PIX Nubank

PIX Nubank

Aula 30 – Tutorial Golang – Timeouts

Fontes usadas para esse post:

Timeouts

O Timeout é uma técnica utilizada em programação para especificar o tempo máximo que uma ação ou operação deve levar para ser concluída.

Ele é usado para evitar que uma ação bloqueada continue sendo executada indefinidamente e assim, preservar recursos de sistema como memória e CPU.

Um exemplo de caso de uso para Timeouts é quando uma aplicação precisa fazer uma solicitação a um serviço externo, como uma API.

Se o serviço demorar muito tempo para responder ou estiver inacessível, a aplicação pode ficar bloqueada esperando pela resposta.

Ao adicionar um Timeout à solicitação, a aplicação pode continuar a funcionar normalmente, sem ficar presa em uma solicitação pendente.

Exemplo

Neste exemplo, uma goroutine é iniciada para esperar por 5 segundos antes de enviar um sinal para o canaltimeout“.

A operação principal então usa a estrutura “select” para aguardar pelo sinal do canaltimeout” ou por outro evento, como uma interrupção de teclado.

Se o sinal chegar antes do final do timeout, a mensagem “Timeout reached” será impressa.


package main

import (
	"fmt"
	"time"
)

func main() {
	timeout := make(chan bool, 1)
	go func() {
		time.Sleep(5 * time.Second)
		timeout <- true
	}()

	select {
	case <-timeout:
		fmt.Println("Timeout reached")
	}
}

Uso em Requisições HTTP

Quando você faz uma requisição HTTP a um servidor externo, é importante ter um timeout para evitar que a aplicação fique presa esperando por uma resposta que nunca chegará.

Aqui está um exemplo usando o pacote “net/http” da biblioteca padrão:


package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"time"
)

func main() {
	client := &http.Client{
		Timeout: 5 * time.Second,
	}

	resp, err := client.Get("https://www.google.com")
	if err != nil {
		if err, ok := err.(*url.Error); ok && err.Timeout() {
			fmt.Println("Timeout reached")
		}
	} else {
		defer resp.Body.Close()
		fmt.Println("Response received")
		b, err := io.ReadAll(resp.Body)
		// b, err := ioutil.ReadAll(resp.Body)  Go.1.15 and earlier
		if err != nil {
			log.Fatalln(err)
		}
		fmt.Println(string(b))
	}
}

Uso em Operações de Banco de Dados

Quando você realiza uma operação de banco de dados, como uma consulta SQL, é importante ter um timeout para evitar que a aplicação fique presa esperando por uma resposta interminável.

Para rodar esse exemplo, você tem que tá com o mysql rodando, local ou na nuvem, trocar o nome do banco para um que você tenha no seu mysql, e a senha também.

Depois instalar o drive do mysqlcom:

go mod init my_example.com/module

go mod tidy 

go install github.com/go-sql-driver/mysql@latest

Exemplo usando o pacote “database/sql” da biblioteca padrão:


package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"time"
)

func main() {
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer db.Close()

	db.SetConnMaxLifetime(5 * time.Second)

	rows, err := db.Query("SELECT * FROM users")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer rows.Close()

	for rows.Next() {
		var id int64
		var first_name string
		var last_name string
		var email string
		var password string

		if err := rows.Scan(&id, &first_name, &last_name, &email, &password); err != nil {
			log.Fatalf("Could not scan the result: %v", err)
		}

		fmt.Println(first_name)

		if err := rows.Err(); err != nil {
			log.Fatalf("Error while processing the rows: %v", err)
		}
	}
}

Esse exemplo usa o time.After (Exemplo do https://gobyexample.com/timeouts)

Para o exemplo abaixo, suponha que estamos fazendo uma chamada externa que retorna o resultado em um canal c1 após 2s.

Observe que o canal é armazenado em buffer, portanto, o envio na goroutine é sem bloqueio.

Este é um padrão comum para evitar vazamentos de goroutine caso o canal nunca seja lido.

O select implementa um timeout.res := <-c1 que aguarda o resultado e o `<-time.After` aguarda um valor a ser enviado após o timeout de 1s.

Uma vez que select prossegue com o primeiro recebimento pronto, consideraremos o caso de timeout se a operação demorar mais do que os 1s permitidos.

Se permitirmos um tempo limite maior de 3s, o recebimento de c2 será bem-sucedido e imprimiremos o resultado.

A execução deste programa mostra o tempo limite (timeout) da primeira operação e o sucesso da segunda.


// _Timeouts_ are important for programs that connect to
// external resources or that otherwise need to bound
// execution time. Implementing timeouts in Go is easy and
// elegant thanks to channels and `select`.

package main

import (
	"fmt"
	"time"
)

func main() {

	// For our example, suppose we're executing an external
	// call that returns its result on a channel `c1`
	// after 2s. Note that the channel is buffered, so the
	// send in the goroutine is nonblocking. This is a
	// common pattern to prevent goroutine leaks in case the
	// channel is never read.
	c1 := make(chan string, 1)
	go func() {
		time.Sleep(2 * time.Second)
		c1 <- "result 1"
	}()

	// Here's the `select` implementing a timeout.
	// `res := <-c1` awaits the result and `<-time.After`
	// awaits a value to be sent after the timeout of
	// 1s. Since `select` proceeds with the first
	// receive that's ready, we'll take the timeout case
	// if the operation takes more than the allowed 1s.
	select {
	case res := <-c1:
		fmt.Println(res)
	case <-time.After(1 * time.Second):
		fmt.Println("timeout 1")
	}

	// If we allow a longer timeout of 3s, then the receive
	// from `c2` will succeed and we'll print the result.
	c2 := make(chan string, 1)
	go func() {
		time.Sleep(2 * time.Second)
		c2 <- "result 2"
	}()
	select {
	case res := <-c2:
		fmt.Println(res)
	case <-time.After(3 * time.Second):
		fmt.Println("timeout 2")
	}
}

Explicação do código acima

O time.After é uma função do pacote time do Go que retorna um canal que é preenchido com o valor true após um período de tempo especificado.

Isso pode ser usado para implementar temporizadores simples.

Na prática, time.After é útil quando você precisa aguardar por um determinado período de tempo, mas ainda quer ter a opção de interromper esse tempo se algum outro evento acontecer.

No exemplo acima, ele é usado para implementar um timeout na execução de uma operação.

Existem duas operações que estão sendo realizadas em paralelo: uma que retorna o resultado em um canal c1 após 2 segundos, e outra que retorna o resultado em um canal c2 após 2 segundos.

O select é usado para esperar por ambos os resultados ou pelo timeout.

Se a operação retornar o resultado antes de atingir o tempo limite, o resultado é impresso.

Caso contrário, a mensagem “timeout 1” ou “timeout 2” é impressa, dependendo do caso.

No primeiro caso, o tempo limite é de 1 segundo e, portanto, o resultado não é recebido a tempo e a mensagem “timeout 1” é impressa.

No segundo caso, o tempo limite é de 3 segundos e, portanto, o resultado é recebido e a mensagem “result 2” é impressa.

Uso em Leitura de Dados de Entrada

Quando você lê dados de entrada, como dados de teclado ou de uma conexão de rede, é importante ter um timeout para evitar que a aplicação fique presa esperando por uma entrada interminável.

Aqui está um exemplo usando o pacote “bufio” da biblioteca padrão:


package main

import (
	"bufio"
	"fmt"
	"os"
	"time"
)

func main() {
	timeout := time.After(5 * time.Second)
	input := make(chan string)

	go func() {
		reader := bufio.NewReader(os.Stdin)
		fmt.Print("Enter text: ")
		inputText, _ := reader.ReadString('\n')
		input <- inputText
	}()

	select {
	case <-timeout:
		fmt.Println("Timeout reached")
	case enteredText := <-input:
		fmt.Printf("Entered text: %s", enteredText)
	}
}

Neste exemplo, criamos uma goroutine que envia um sinal para o canaltimeout” após 5 segundos.

Em seguida, usamos a instrução “select” para aguardar tanto o sinal do canaltimeout” quanto a leitura dos dados de entrada pelo objeto “reader“.

Se o sinal do canaltimeout” chegar primeiro, é impressa a mensagem de “Timeout reached“.

Caso contrário, é impresso os dados de entrada lidos pelo objeto “reader“.

Eu fico por aqui e agradeço a você pela audiência.

Até mais. 🙂

Página principal do blog

Todas as aulas desse curso

Aula 29                        Aula 31

Meus links de afiliados:

Hostinger

Digital Ocean

One.com

Obrigado e bons estudos. 😉

About The Author
-

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>