Aula 30 – Tutorial Golang – Timeouts
Aula 30 – Tutorial Golang – Timeouts
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!
Página principal do blog
Todas as aulas desse curso
Aula 29 Aula 31
Redes Sociais:
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
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 canal “timeout“.
A operação principal então usa a estrutura “select” para aguardar pelo sinal do canal “timeout” 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 canal “timeout” após 5 segundos.
Em seguida, usamos a instrução “select” para aguardar tanto o sinal do canal “timeout” quanto a leitura dos dados de entrada pelo objeto “reader“.
Se o sinal do canal “timeout” chegar primeiro, é impressa a mensagem de “Timeout reached“.
Caso contrário, é impresso os dados de entrada lidos pelo objeto “reader“.