Use context.WithTimeout with the caller’s context, defer the returned cancel function, and pass the child context to every generation stage that supports cancellation. This sets a deadline for cooperative work; it does not forcibly stop a renderer that ignores its context.
What a Go timeout does—and does not do
A Go context carries a deadline and cancellation signal across API boundaries. Calling context.WithTimeout(parent, limit) derives a child context whose deadline is the earlier of the new timeout and any deadline already on parent. If the parent is canceled, the child is canceled too.
That signal is a request to stop, not a mechanism for interrupting arbitrary code. The PDF renderer and any blocking work it invokes must check the context or otherwise support cancellation. If a library accepts no context and blocks synchronously, wrapping its call in a timeout context alone cannot guarantee that the call will return when the deadline expires. Check the specific library’s API and its handling of partial output, files, and cleanup.
The Go Project’s context documentation and cancellation guidance explain these context semantics. They do not prescribe a universal timeout for PDF generation. Choose a limit based on your service’s latency objective and measurements from representative documents and workloads.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems#1 Best Overall
Set a timeout at the generation boundary
Accept the upstream context and make the timeout visible at the point where PDF work begins. The following wrapper is a complete, compilable Go example; its callback represents the context-aware PDF-generation function supplied by your chosen library or adapter.
package main
import (
"context"
"errors"
"fmt"
"time"
)
// GeneratePDF applies a limit without discarding cancellation or deadlines
// inherited from parent. render must itself observe ctx for work to stop early.
func GeneratePDF(
parent context.Context,
limit time.Duration,
render func(context.Context) ([]byte, error),
) ([]byte, error) {
ctx, cancel := context.WithTimeout(parent, limit)
defer cancel()
data, err := render(ctx)
if err != nil {
// Preserve the renderer's error. The caller can inspect both err and
// the context state when deciding whether this was a timeout.
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return nil, fmt.Errorf("PDF generation deadline exceeded: %w", err)
}
return nil, err
}
return data, nil
}
func main() {
parent := context.Background()
// Replace this callback with an adapter that calls your PDF library and
// passes ctx to each operation that supports it.
pdf, err := GeneratePDF(parent, 10*time.Second, func(ctx context.Context) ([]byte, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
return []byte("PDF bytes from the renderer"), nil
}
})
if err != nil {
fmt.Println("generation failed:", err)
return
}
fmt.Printf("renderer returned %d bytesn", len(pdf))
}
The ten-second duration is illustrative only, not a recommended or measured setting. The callback shown demonstrates the wrapper contract; it does not create a real PDF. In production, connect it to a renderer that accepts or checks ctx, and decide whether to return partial bytes or discard them when the operation fails.
Keep the cancel call
Call the returned cancel function on every path, normally with defer cancel() immediately after creating the child context. The Go documentation says cancellation releases resources associated with the derived context; go vet checks that cancel functions are used on all control-flow paths.
Pass the context through the whole pipeline
Passing the context only to the final rendering call leaves earlier stages outside the timeout. Where APIs support it, carry the same child context through template and data preparation, remote asset retrieval, and other work required to produce the document. Any stage that does not observe the context remains outside the guarantee.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Preserve cancellation in an HTTP handler
When a request triggers PDF generation, use r.Context() as the parent rather than starting from context.Background(). The request context is canceled when the client disconnects or cancels the request. A shorter PDF-specific timeout can then add a limit without losing that upstream cancellation.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), h.pdfTimeout)
defer cancel()
pdf, err := h.renderer.Generate(ctx, h.inputFor(r))
if err != nil {
// Choose the HTTP response and logging policy appropriate to your service.
// Do not label every generation error a timeout.
h.handleGenerationError(w, r, ctx, err)
return
}
w.Header().Set("Content-Type", "application/pdf")
_, _ = w.Write(pdf)
}
This handler assumes the application’s Handler and renderer provide the named methods and fields; it illustrates the context relationship rather than a standalone server. A request cancellation or an earlier parent deadline can end the child context before the PDF-specific limit.
Tell a timeout from another generation error
A renderer can fail for reasons unrelated to time. Classify an error using the renderer’s returned error and the context state; do not report a timeout solely because generation returned an error. If the renderer wraps context errors, errors.Is can test for them through wrapping.
switch {
case errors.Is(err, context.DeadlineExceeded):
// A deadline error was returned or wrapped.
case errors.Is(err, context.Canceled):
// Cancellation occurred; it may have come from an upstream caller.
case err != nil:
// A different renderer or pipeline error occurred.
}
Retain the original error when adding context to logs or returning it to a caller. Also decide explicitly what happens to partial bytes and temporary files after failure: the behavior depends on the renderer, and should be verified for the exact package and version in use.
Free tools Windows power users keep installed
One-click scans. No signup required.
Browser-backed PDF generation needs a cleanup budget too
For a pipeline that drives Chrome through chromedp, derive the browser context from the request or job context so cancellation can propagate. The package documentation describes context cancellation as closing a tab or browser. It also documents that its Cancel function waits for cleanup and shows attaching a timeout context to bound the wait for browser shutdown.
Rank #4
Consider render time and shutdown time as separate concerns. A render deadline signals that work should stop; cleanup may still take time. Verify shutdown behavior against the chromedp version you deploy, and do not promise instantaneous termination in every browser state.
Check the PDF package’s actual context support
Go PDF libraries do not all expose cancellation in the same way. pdfcpu documents context-aware library operations, and its API documentation lists cancellation support for CreateFile. That is evidence for those documented operations, not a guarantee about other PDF packages or every operation within a package. Identify the exact package, version, and method before relying on a timeout.
- Does the operation accept a context? If so, pass the derived context to it.
- Does it check cancellation while work is underway? Accepting a context does not by itself establish how promptly in-progress work stops.
- What happens to output and temporary resources on cancellation? Confirm cleanup and partial-output behavior for the method you call.
- Does the operation preserve caller cancellation? Derive from the request or job context rather than replacing it with a new background context.
Choose the timeout from your service’s needs
Neither the Go context documentation nor the cited PDF package documentation sets a generally suitable PDF-generation duration. Measure representative document sizes and inputs in your own service, then choose a limit consistent with the latency your callers can tolerate. A single short value may be inappropriate if workloads vary; use workload-specific policies only when your application can define and maintain them clearly.
Best Value
For HTTP work, account for the fact that the request context may already carry an earlier deadline. For background jobs, pass the job’s context so worker shutdown or job cancellation can propagate. In both cases, ensure every relevant stage receives the same derived context where its API supports it.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshoot timeouts and stuck generation
- The request times out, but generation continues. The renderer or a blocking stage may not observe the context. Check the library API; a timeout wrapper cannot forcibly interrupt arbitrary synchronous code.
- Generation stops when the client disconnects, earlier than expected. The child inherits parent cancellation and the earlier parent deadline. That is expected when using
r.Context(). - An error is being labeled as a timeout incorrectly. Check both the returned error and context state. Other renderer failures do not become timeouts just because a timeout was configured.
- Temporary files or partial output remain after failure. Verify the renderer’s cleanup semantics and add application cleanup where needed. Do not assume all packages remove partial output automatically.
- Browser cleanup outlasts rendering. Treat the browser’s cancellation and shutdown wait separately from the render deadline, and check the deployed
chromedpversion’s documented behavior. - Cancellation appears ineffective in an earlier pipeline stage. Pass the derived context into that stage if its API accepts one; otherwise that stage may continue independently of the deadline.
Or skip the browser setup
If the job is to capture a web page rather than render arbitrary application data, ScreenshotNeo offers a website screenshot API and MCP server. It can return a clean screenshot or PDF from a URL; it is not a general-purpose replacement for a Go PDF library. For the basic image request, the one-call cURL example is:
See the ScreenshotNeo API documentation for the available capture options, including PDF settings.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The example saves a WebP image; consult the docs for PDF output settings rather than assuming this command requests a PDF. Equivalent request examples are:
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie and consent banners are accepted and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before capture; each step can be turned off.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers identify the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - The free plan includes 1,000 screenshots a month with no card required; paid plans start at $5 for 3,000. Every feature is available on every plan.
Sign up free for 1,000 screenshots a month, with no card required.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

