skip to content

A container's first process is a wrapper script that launches a report renderer as a child; why is the renderer force-killed on every stop?

level: middleimportance: must knowfreq 64%

answer

  1. only one process is told
  2. position one, not the application
  3. the wrapper is the addressee
  4. parents do not pass signals down
  5. no handler, no forwarding, no news

basics

~20 s

A stop request is delivered only to the container's first process, which here is the wrapper. The wrapper never passes it to the renderer, so the renderer works on unaware until the grace window expires and the forced kill ends it.

solid answer

~50 s

When a platform asks a container to stop, it delivers a termination signal to exactly one process: the first one started inside the container. Nothing else is notified — not children, not helpers, not grandchildren. Here that first process is the wrapper script, and a wrapper that just starts a child and waits installs no handler and forwards nothing, because being a parent does not pass a signal down a process tree. The renderer therefore never learns a stop was requested; the platform waits out the grace window; then the forced kill arrives, which nothing can catch, and both processes die mid-write. The two shapes that work are: make the renderer itself the container's first process so the signal lands directly on it, or keep a first process whose job is to forward the signal to its children and collect them as they end.

code

pseudocode · 11 lines
pseudocode
# the container's first process: a wrapper that swallows the request

prepare_configuration()
renderer = spawn("render-reports")
helper   = spawn("upload-results")

wait_for(renderer)       # blocks here for the life of the container

# a stop request arriving now is delivered to THIS process only.
# no handler is installed and nothing is forwarded, so the renderer
# keeps rendering until the forced kill cuts it off mid-write.

go deeper

for a junior

Remember the one fact everything else hangs on: a stop request goes to the container's first process and to nothing else. Whatever the image starts first is the only process that is told.

for a middle

Explain why a wrapper breaks this: signals are addressed to a process, not to a process tree, so a parent that installs no handler and forwards nothing leaves its child unaware until the forced kill arrives.

for a senior

Show that you read the timing as evidence. Stops that always consume the full window, with no shutdown line in the application's own output, point at delivery rather than at a slow handler — and lengthening the window will change nothing.

for a principal

The judgment call is whether you let each team get position one right or standardise a first process across every image. Weigh an extra process and an exit-status contract against a discipline that regresses silently in images nobody reviewed.

## What the platform actually delivers When a platform is asked to stop a container it does not stop a container: it asks one process to end. It delivers a **termination signal** — a request a process is free to catch and act on — to the **first process in the container**, the one started from the command the image declares. That process is the only addressee. Children it started, helpers started beside it, and grandchildren started by those are told nothing at all. The platform then waits out a **grace window**, and if the container has not ended by the time the window expires it issues a **forced kill**, which no process can catch, handle or postpone. Two consequences follow directly, and the rest of this subject is built on them: - whatever sits at position one decides whether the application ever hears that a stop was requested; - the container ends when that first process ends, whatever else is still running inside it. ## Why a wrapper script is the usual culprit A wrapper is the most natural thing in the world to write: it prepares a little state, starts the real process, sometimes starts a helper alongside it, and waits. It is also, unless you write it otherwise, a **signal sink**. Nothing about being a parent passes a signal down to a child — a signal is addressed to one process, never to a process subtree — and a plain wrapper installs no handler of its own. So the sequence on every stop is: 1. the platform signals the wrapper, because the wrapper is the first process; 2. the wrapper has no handler and no instruction to forward, so nothing happens; 3. the renderer, one rung down, keeps rendering, entirely unaware; 4. the window expires, the forced kill lands, and both processes end mid-write. The same defect has a second, quieter shape. A wrapper that *does* react to the signal by exiting, without forwarding it first, looks better from outside: the container stops promptly instead of burning the whole window. It is actually worse. The container ends the moment its first process ends, so the renderer is cut off immediately rather than at the end of the window, and the prompt stop is read as evidence that shutdown works. ## What the symptom looks like from outside - Stops consume the entire grace window, every time, never less. A workload that really shuts down cleanly usually exits well before the window closes. - The application's own shutdown log line never appears — not a truncated one, none at all. - The forced kill leaves no record of its own from inside the process, so the timeline just shows work stopping with nothing to explain it. - Work is lost at whatever point the window happened to land, so the damage looks arbitrary rather than tied to a phase of the work. - A rolling replacement takes far longer than anyone expected, because every replica pays the full window. - Lengthening the window changes nothing except how long each stop takes — the single clearest tell that the request is not reaching the application at all. ## The shapes that work | First process | What a stop request does | When it is the right choice | |---|---|---| | The application itself | Lands directly on it; it must have a handler installed, and it inherits the duty of collecting whatever ends beneath it | The image genuinely runs one process and you control its code | | A minimal init | Forwards the request to its children, collects them as they end, and passes the main child's exit status outward | The image needs a wrapper or a second process, or nobody can audit every image in the estate | | A wrapper that forwards on purpose | The same as a minimal init, except it is signal-handling code you now own and have to test | A small image with one setup step and a team willing to keep that handler correct | Note what is *not* on that list: adding a full process supervisor. Supervisors are built to keep children running and to restart them, which is the opposite of what you want at position one during a stop — a component that restarts the renderer while the platform is trying to take it away turns a slow stop into a stuck one. ## What this question does not settle That the application hears the request is a separate matter from what it should then do with it. Finishing in-flight work, refusing new work, leaving the routing set first, and how long a window all of that deserves are a different subject with different answers. The claim here is narrower and blunter: none of that runs at all if the request never arrives, and every minute spent tuning the window is wasted until delivery is proven. The other half of the first process's job — collecting the children that end underneath it, so that the container's table of process entries does not fill — is the same position's second duty, and it fails independently of this one.

  • The wrapper now forwards the request, and the renderer is still killed at the end of the window. What changed?
    Delivery is no longer the question — the renderer heard the request and did not finish in time. That is a shutdown-duration problem: either it installs no handler for the signal, or the work it was doing outlasts the window it is given. Those are different fixes from forwarding, and they only become worth arguing about once delivery is proven.
  • A container stops promptly on every stop request. Does that prove the request reached the application?
    No. It proves that the first process ended, which is what ends the container. A wrapper that exits on the signal without forwarding it produces exactly that timing, while the renderer underneath is cut off immediately with no chance to finish. Prompt is not the same as clean: look for the application's own shutdown line, not for a fast exit.
  • If the renderer itself is the container's first process, is anything left to worry about?
    Two things. It has to install a handler, because at position one an unhandled termination signal is commonly discarded rather than ending the process, so a workload that would die from it anywhere else keeps running. And it inherits the duty of collecting whatever ends beneath it, including processes it did not start directly.

A courier delivers to the front desk only. If the clerk signs for the package and never carries it upstairs, the person it was addressed to is still waiting when the building closes for the night.

saying these in an interview costs you the question

  • Thinks the stop request is broadcast to every process in the container
  • Believes a child automatically inherits or receives its parent's signals
  • Blames the application's shutdown handler that was never actually invoked
  • Assumes a longer grace window can fix a signal that is never delivered
  • Thinks the forced kill can be caught and cleaned up after
  • Reads a prompt container exit as proof that shutdown worked