Project challenges / verified progress
Beacon: build an SSL and uptime monitor in Go

The engineering notebook

Put the whole check on a deadline

How does a prober guarantee that one slow target cannot run forever?

Loading statusStage 7 of 10

  • Workspace not ready
  • Agent not ready
Focus25:00
A small focus ritual

0 focus sessions completed. Every fourth session offers a longer break. Start each phase when you are ready.

Study time never unlocks verified lesson progress.

Loading...

Loading verified progress...

Loading GitHub account...
Phase 3 — A check you can trust

Step 01 of 06

Learn the concept

A monitor with no deadline is just a goroutine waiting to become folklore. One stuck DNS query or silent peer can hold a worker forever, and schedules do not forgive forever. Beacon needs a worst case it can say out loud: five seconds, then a result.

ONE FIVE SECOND BUDGETsecondsDNSLookupIPAddr(ctx)0.1sTCPDialContext0.2sTLSHandshakeContext0.4sHTTPrequest with ctx4.3s left
The budget belongs to the whole check, not to whichever layer happens to be running. If HTTP consumes the remaining time, DNS does not get a private second chance. That is how a scheduler keeps its promise.
Step 01

The ideas this is made of

A deadline is the promise the scheduler can use

Five separate five-second timeouts can produce a 25-second probe. That may be correct per operation and useless operationally. An end-to-end deadline says the whole check stops by one instant, no matter where the time went. The scheduler can plan capacity around that maximum, and the result can honestly say the target consumed the full budget.

Context cancellation is a tree, not a global variable

context.WithTimeout(parent, 5*time.Second) returns a child context and a cancel function. When the child is cancelled, operations using it observe <-ctx.Done() and ctx.Err(). Children derived from it cancel too. Call defer cancel() immediately. It releases timer resources early when work finishes and avoids a quiet leak in loops.

A deadline only works where you pass it

DNS needs LookupIPAddr(ctx, host). TCP needs net.Dialer.DialContext. Manual TLS needs tls.Conn.HandshakeContext. HTTP needs http.NewRequestWithContext. Leave one blocking layer on context.Background() and that layer can outlive the check. Context is not atmospheric. It must be threaded through every wait, retry and cleanup path.

Deadline errors are values with wrappers around them

When a deadline expires, the category is context.DeadlineExceeded. Lower layers may wrap it with text such as Client.Timeout exceeded while awaiting headers. Use errors.Is(err, context.DeadlineExceeded) and check ctx.Err(). String matching breaks when Go, the OS or a dependency changes wording during exactly the incident you are debugging.

`http.Client.Timeout` is broad, but not enough here

http.Client.Timeout limits a request made by that client. It is useful and blunt. Beacon already measures DNS, TCP and TLS explicitly, so the budget must be visible above all of them. A context deadline composes with process shutdown, parent cancellation and manual layer timing. It is the one clock every layer can share.

A context deadline around one request
package main

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"time"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com", nil)
	if err != nil {
		panic(err)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		if errors.Is(err, context.DeadlineExceeded) || errors.Is(ctx.Err(), context.DeadlineExceeded) {
			fmt.Println("request exceeded its deadline")
			return
		}
		panic(err)
	}
	defer resp.Body.Close()

	fmt.Println("status:", resp.StatusCode)
}

The deadline is created once, cancelled exactly once, and attached to the request. Beacon extends the same pattern down into DNS, TCP and TLS so one budget covers the whole journey.

Three timeout tools people confuse

ToolWhat it meansBest use

WithTimeout

Duration from now

One check budget

WithDeadline

Specific instant

Shared external cutoff

Client.Timeout

Whole HTTP request

Simple client policy

Dialer.Timeout

Connect only

Narrow TCP cap

What these are called on the job

  • Deadline — An absolute instant after which work should stop.

  • Timeout — A duration used to derive a deadline.

  • Cancellation — A signal that callers no longer want the work.

  • Context propagation — Passing the same context to every API that may block.

  • Timer leak — Resources left by creating timeout contexts without cancelling them.