Introdução
O balcão precisa fechar sem cortar a transferência de quem já recebe as notas de campo. Você substituirá o fechamento imediato de conexões pela coordenação comum de encerramento HTTP de Go e conservará um download real completo do processo original.
A unidade começa independentemente com o handler preparado e os mesmos dois documentos. São necessárias as observações de PID exato e requisição aceita da unidade anterior, ou experiência equivalente, além de handlers, erros, goroutines e canais. Entrega de sinais, contextos finitos, reconhecimento de erros e a responsabilidade de main esperar são explicados abaixo. O assunto é HTTP comum, não WebSockets, troca distribuída de tráfego ou garantia de indisponibilidade zero em produção. Reserve cerca de 60 minutos; é uma estimativa do autor.
Faça main esperar pelo encerramento
Nesta etapa, implemente uma coordenação finita de encerramento preservando o handler HTTP preparado.
Comece em Downloads e entre no espaço de trabalho independente:
cd /home/labex/project/download-desk
cat lifecycle.go
Você só pode alterar lifecycle.go. Preserve o handler de main.go, ambos os documentos, go.mod, os observadores preparados e suas observações. A função atual captura SIGTERM, mas chama Close, interrompendo conexões ativas. Tratar um sinal, sozinho, não torna o encerramento gracioso.
Um http.Server controla o listener e as conexões HTTP ativas. ListenAndServe bloqueia enquanto aceita trabalho. Shutdown primeiro fecha listeners e conexões ociosas existentes e depois espera as requisições ativas comuns ficarem ociosas. Iniciar o encerramento faz ListenAndServe retornar imediatamente http.ErrServerClosed, embora handlers aceitos ainda possam estar executando. Se main retornar nesse momento, todas as goroutines desaparecem e a transferência é cortada. Portanto, o caminho principal deve esperar o próprio Shutdown retornar.
Um contexto carrega cancelamento ou um orçamento de prazo. context.Background() fornece o contexto inicial. context.WithTimeout(..., 45*time.Second) cria um orçamento finito de 45 segundos e uma função cancel; defer cancel() libera os recursos associados quando a função retorna, mesmo se o trabalho acabar antes. Cancelamento não comprova conclusão. O máximo público é 60 segundos após o pedido de parada: se uma resposta aceita não terminar no orçamento escolhido, informe um erro e saia com status diferente de zero dentro desse máximo. A referência usa 45 segundos; orçamentos finitos equivalentes de até 60 segundos são permitidos. A verificação inclui uma resposta longa finita que não consegue terminar em 60 segundos; usar apenas context.Background() não basta. Nesse teste isolado de prazo, transferência parcial e saída não zero informada pelo servidor são o resultado correto de falha, não a conclusão bem-sucedida de todo o trabalho. Se o orçamento vencer, Shutdown retorna erro; nossa função o retorna e main sai com status não zero. Não chamamos isso de conclusão bem-sucedida nem reiniciamos o servidor silenciosamente.
signal.Notify(stopped, syscall.SIGTERM) pede a Go que envie o sinal a um canal. Seu buffer tem capacidade 1 porque a entrega de sinal não deve esperar por um receptor; uma solicitação de parada enfileirada basta aqui. signal.Stop(stopped) cancela o registro desse canal ao terminar a função; isso é diferente de parar o servidor HTTP. Da mesma forma, o canal de resultado com buffer permite à goroutine servidora informar seu único resultado enquanto main encerra o serviço.
O primeiro select espera um erro de serviço ou sinal de parada. Assim, um erro de associação à porta ou de início não fica escondido para sempre atrás da espera por sinal. Após um encerramento bem-sucedido, errors.Is(err, http.ErrServerClosed) reconhece o resultado esperado do serviço. Outro erro é retornado, em vez de ser chamado de sucesso. main chama runServer; fazê-la retornar somente depois do encerramento é o que mantém o processo vivo.
Substitua este único arquivo. O delimitador entre aspas do here-document faz o shell escrever literalmente o código Go até o GO final em sua própria linha; > substitui somente lifecycle.go:
cat > lifecycle.go <<'GO'
package main
import (
"context"
"errors"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func runServer(server *http.Server) error {
stopped := make(chan os.Signal, 1)
signal.Notify(stopped, syscall.SIGTERM)
defer signal.Stop(stopped)
result := make(chan error, 1)
go func() { result <- server.ListenAndServe() }()
select {
case err := <-result:
return err
case <-stopped:
}
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
return err
}
err := <-result
if !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
}
GO
Formate o código com gofmt -w; -w grava o arquivo formatado. Compile o pacote atual usando o caminho explícito do executável para que um terminal novo não dependa de um PATH oculto:
/usr/local/go/bin/gofmt -w lifecycle.go
/usr/local/go/bin/go build -o desk .
Um erro de sintaxe indica arquivo e linha. Compare o bloco completo e substitua apenas lifecycle.go se necessário; não reinicie a VM. A verificação também compila sua função sem modificações em ambientes temporários isolados e varia durações reais dos downloads e ordens de conclusão. Testa tanto a falha da resposta longa quanto o sucesso normal; espere cerca de um minuto. Não envia sinal ao balcão em execução nem modifica seu código.
Preserve o download e feche o listener
Nesta etapa, preserve um download real aceito enquanto recusa novas conexões e espera o servidor original.
Mude para Server, entre no espaço de trabalho e inicie uma nova tentativa observada. O terminal permanece ocupado até o processo original terminar:
cd /home/labex/project/download-desk
python3 desk.py serve drained
Aguarde a mensagem real de prontidão e mude para Downloads. Leia toda a etapa, incluindo a falha esperada da sonda e a recuperação, antes de iniciar a transferência lenta finita seguinte. Depois execute os blocos em sequência; não aprenda um novo conceito durante a janela de transferência. Inicie um novo download lento finito em segundo plano, como antes. Os 300ms entre gravações de 32 bytes deixam tempo normal para leitura; não são uma espera ilimitada pelo sinal.
python3 desk.py download drained completed north-pier.txt 300 & download_job=$!
python3 desk.py accepted drained completed
O observador lê a requisição real aceita antes de qualquer parada. Obtenha e inspecione a identidade do processo original compilado e envie SIGTERM deste terminal:
server_pid=$(python3 desk.py pid drained)
python3 desk.py identity drained
kill -TERM "$server_pid"
Enquanto a transferência aceita continua, teste uma conexão nova. A sonda preparada faz tentativas reais limitadas por até dois segundos, preservando cada resultado nativo até surgir uma recusa. Isso deixa Go tratar o sinal entregue antes da primeira falha observada; a sonda não presume que kill feche o listener de forma síncrona. O próximo comando deve retornar 7, com mensagem de conexão recusada:
python3 desk.py probe drained
É uma recusa para criar uma nova conexão, não um HTTP 403 nem uma transferência aceita danificada. O download original deve continuar. Espere seu trabalho em segundo plano e compare os bytes reais salvos com o original:
wait "$download_job"
cmp documents/north-pier.txt downloads/completed.txt
cat downloads/completed.txt
Espere curl nativo 0, nenhuma saída de cmp e o capítulo final completo. Mude para Server: agora deve informar Original server native exit: 0. O servidor pode terminar de enviar antes que o cliente termine de ler ou descarregar seu buffer no arquivo; o encerramento não espera confirmação da aplicação cliente. São observações distintas, não uma ordem obrigatória entre duas mensagens de terminal.
Volte a Downloads. Preserve lifecycle.go, o download completo e as observações nativas. A verificação lê esse resultado real e testa o mesmo código em portas aleatórias isoladas, com uma conexão ociosa existente e requisições aceitas em ordens diferentes. Close direto, retorno de main durante Shutdown, listener aberto até o trabalho terminar ou dormir por um intervalo adivinhado não cumprem o contrato.
Se o download terminou antes de accepted ou da parada, preserve o arquivo e escolha novo rótulo na tentativa ainda em execução. Se o servidor já saiu, inicie explicitamente outra tentativa em Server e use o nome na nova saída lenta e nos comandos identity, accepted e probe. Uma sonda recusada só depois da saída não demonstra fechamento antecipado do listener; repita o fluxo ativo observado com nomes novos. Nunca sobrescreva o arquivo anterior bem-sucedido nem reinicie o histórico.
Para repetir o fluxo completo depois que a tentativa anterior sair, use Server:
python3 desk.py serve drained-retry
Após a mensagem de prontidão, use Downloads. Leia todos os blocos primeiro. Os nomes novos preservam os arquivos e observações anteriores:
python3 desk.py download drained-retry completed-retry north-pier.txt 300 & download_job=$!
python3 desk.py accepted drained-retry completed-retry
server_pid=$(python3 desk.py pid drained-retry)
python3 desk.py identity drained-retry
kill -TERM "$server_pid"
A próxima sonda deve retornar 7:
python3 desk.py probe drained-retry
wait "$download_job"
cmp documents/north-pier.txt downloads/completed-retry.txt
Espere curl 0, bytes completos iguais e saída nativa original0 em Server. Se o servidor anterior ainda funcionar, termine de ler antes de iniciar nova saída lenta nessa tentativa; use novo rótulo em download e accepted, mantendo o nome da tentativa para identity e probe. Se uma sonda anterior já existir, preserve-a, pare explicitamente sua própria tentativa antiga e deixe-a sair antes da repetição completa com nova tentativa acima.
Um erro real de início aparece no stderr existente do servidor e retorna status não zero; corrija um erro de código/compilação já explicado ou espere sua tentativa anterior sair antes de reutilizar 8080. Não mate um processo da plataforma com o mesmo nome. Um erro de prazo de encerramento significa que o orçamento finito não confirmou conclusão; leia-o honestamente em vez de substituir o processo ou copiar um documento anterior.
Resumo
Você coordenou o listener, os handlers HTTP aceitos, um contexto finito de encerramento e main. O processo original deixou de aceitar conexões enquanto terminava o trabalho existente. Bytes finais exatos e resultados nativos tornaram o resultado observável.



