ChatGPT for Go: stop the producer when the consumer exits early

When asking ChatGPT to repair a Go channel pipeline, state whether the consumer reads every value. If it stops after the first useful result, the producer needs a cancellation path around its send. Otherwise it can remain blocked trying to deliver a value nobody will receive.

Provide the complete producer and consumer together. A send that looks reasonable inside one function can depend on another goroutine continuing to receive. Ask for an explanation of how each goroutine exits, then check that explanation with an explicit completion signal.

Locate the send that no longer has a receiver

This original teaching setup has one producer sending 2, 4 and 6 through an unbuffered channel. The consumer needs only the first value, 2. It then waits for the producer's completion signal.

A producer that simply loops over the sends reaches 4 after the first receive. With no second receiver, that send cannot complete. Waiting for the producer to finish does not make a receiver appear. If those are the program's only goroutines, this creates a deadlock rather than a useful shutdown.

Adding a buffer of two would let this three-value fixture finish, but it encodes how many unread values happen to remain. It does not establish a shutdown rule for a larger or unbounded producer.

The Go project's pipelines and cancellation article, checked October 24, 2026, explains early downstream exit, blocked upstream sends and a shared cancellation signal. The practical question is which blocked operation can observe that signal.

Use a cancellation case around the send

Save this complete authored program as main.go in a scratch folder. It uses only the standard library and does not access files or a network:

package main

import "fmt"

func produce(done <-chan struct{}) (<-chan int, <-chan struct{}) {
    out := make(chan int)
    exited := make(chan struct{})
    go func() {
        defer close(exited)
        defer close(out)
        for _, value := range []int{2, 4, 6} {
            select {
            case out <- value:
            case <-done:
                return
            }
        }
    }()
    return out, exited
}

func main() {
    done := make(chan struct{})
    out, exited := produce(done)

    value := <-out
    fmt.Println(value)
    close(done)
    <-exited
    fmt.Println("producer stopped")
}

For this fixture, the expected output is:

2
producer stopped

The consumer closes done once it has its first value. A producer blocked at the next select can choose the now-ready cancellation receive and return. Its deferred closures signal that the output is finished and that the producer goroutine reached its exit path.

The main goroutine waits on exited before printing the final line. A sleep would only wait for time to pass; this channel waits for the specific completion event. The output above is an authored expectation, not a recorded compiler run.

Keep channel ownership explicit

In this program, main owns the cancellation decision and closes done once. The producer owns and closes out and exited. The consumer must not close out to stop the producer: the producer could still attempt a send to that closed channel.

Cancellation is cooperative. Adding a done parameter cannot interrupt a blocking operation that never checks it. This fixture checks cancellation around channel sends; a real pipeline with network calls, subprocesses or other waits needs a suitable cancellation mechanism at those operations too.

The example deliberately keeps one sender and one cancellation owner. Several senders need coordinated output-channel closure, and several callers cannot independently close the same cancellation channel. Ask for those ownership rules before expanding the pattern.

Ask ChatGPT for an exit trace

Review this Go pipeline for early-consumer-exit behavior.
Go version: [ENTER]
Complete producer,consumer and shutdown code: [PASTE]
Channel buffering and who owns each close: [ENTER]
When the consumer may stop and why: [ENTER]
Expected result and explicit completion condition: [ENTER]
Actual output,panic or blocked location: [PASTE OR NOT YET RUN]

Trace which send or receive can block after the consumer stops.
Propose a small cooperative-cancellation repair preserving required
values and channel ownership. Show how cancellation reaches each
blocking operation and how the caller observes actual completion.
Do not close a sender's output from the consumer, use sleeps as proof
of shutdown, invent a buffer bound or claim you ran my program.
Return a complete runnable fixture and explain remaining lifecycle
assumptions before adapting it to my application.

OpenAI's prompting guidance supports supplying context, constraints and the intended output. Here, “the consumer may stop early” is a required part of the contract, rather than an exceptional detail to leave out.

Where Go is installed, check go version, then run go run main.go from the scratch folder. Compare both output lines and confirm the process terminates. For a more complex failure, use the debugging workflow to preserve the original trigger. Keep the cancellation owner and completion check beside the accepted code so a later change to the consumer cannot leave the producer waiting indefinitely.