In a gRPC client-streaming call, what does half-close mean, and what may the server do afterwards?
answer
- one direction, not the call
- no more requests, still waiting
- the server's read loop finishes
- the status is what ends it
- missing half-close hangs the upload
basics
~20 sHalf-close means the sending side has finished its sequence of request messages while the call stays open. The server can keep reading what it buffered, then send its response and the single status that ends the call.
solid answer
~40 sHalf-close closes **one direction** of a call, not the call. On a client-streaming method the client writes its sequence of request messages and then half-closes, which tells the server "there are no more requests" — the response direction is untouched. The server drains whatever it has not read yet, computes its single response, sends it, and ends the call with one status. Until the client half-closes, a server handler that reads until end-of-sequence simply waits; it has no way to know the client is finished. That is the classic client-streaming hang: the upload looks stuck, and only the call's deadline eventually ends it. Half-close is an orderly finish, not an abort.
code
pseudocode · 10 linescall = start uploadRun() // client-streaming method
for each sample in run:
call.send(sample)
call.halfClose() // no more request messages
// the call is STILL OPEN here
summary = call.awaitResponse() // server answers after draining
status = call.awaitStatus() // one status ends the whole callgo deeper
Remember the shape: the uploading side says "that was my last message" and then waits. The call is not over — the answer still has to come back.
Explain that half-close finishes one direction while the call and its response direction stay open, and that the server's read loop ending is exactly how it learns the client is done.
Reach for the failure: an upload that hangs with an idle server handler is a missing half-close, and the deadline is the only thing that will end it. Name the mechanism, not just the symptom.
The judgment worth voicing is that a long call has two independent endings — one per direction — and any protocol built on it needs both of them to be reachable from every error path in your code.
A gRPC call has two directions: requests flowing from the caller to the server, and responses flowing back. **Half-close** is one side saying "I have finished sending on my direction" while the call as a whole stays open. It is the normal, orderly way a client-streaming or bidirectional upload finishes, and it is not the same thing as ending the call. ## What the client is actually signalling On a client-streaming method the caller's side of the call is a sequence it writes into. It writes each message, and when there are no more it half-closes. Three things are true at that instant: - **No further request messages may be sent.** Writing after a half-close is a programming error on that call. - **The response direction is completely unaffected.** The server's answer has not been sent yet, and the client is still waiting for it. - **The call has no outcome yet.** No status has been delivered, so nothing has succeeded or failed. On unary and server-streaming methods the same thing happens, invisibly: the client has exactly one request message, so the library finishes the request direction as soon as that message goes out. You only *manage* half-close by hand on the two kinds where the client sends a sequence. ## What the server does next A server handler for a client-streaming method typically reads messages until the sequence ends. The end of that sequence is precisely the client's half-close. So the handler: 1. Reads any messages that are still in flight or buffered — half-close does not discard them; it comes *after* them in the sequence. 2. Sees its read loop finish, which is how it learns the client is done. 3. Computes and sends its single response message. 4. Ends the call with one status covering the whole call. Only step 4 ends the call. Between the half-close and the status, the call is alive with one live direction — which is exactly why the shape is called *half*-close. | Event | Request direction | Response direction | Call outcome | |---|---|---|---| | Client half-closes | finished | open | not decided yet | | Server sends its response | finished | still open | not decided yet | | Server ends with a status | finished | finished | decided, once, for the whole call | ## The failure this causes in production The classic client-streaming bug is a client that stops writing without half-closing — an upload loop that finishes its samples and then just waits for the summary. From the server's point of view nothing has happened: the request sequence has not ended, so the read loop is still waiting for a message that will never come. The handler is occupied, the client is blocked waiting for a response that cannot be computed, and nothing in either program is obviously wrong. The call ends when its deadline expires, which is often the only reason anyone notices at all. The symptom is worth memorising because it is so specific: **a client-streaming upload that hangs after the last message, with the server handler alive and idle, is almost always a missing half-close.** ## Half-close on a bidirectional method Bidirectional calls have the same mechanism with more freedom. The client can half-close its request sequence while the server keeps sending responses for as long as it likes — that is the "upload everything, then listen" pattern. The server does not have a symmetric half-close to perform: its way of finishing its own direction is to send the call's final status, and that ends the call entirely. ## What half-close is not - **Not a cancellation.** A cancel aborts the whole call in both directions and produces a failed outcome. Half-close is a successful, expected part of a normal call. - **Not a close of the underlying connection.** The long-lived connection carries many calls; one call finishing a direction does nothing to it. - **Not a flush or an acknowledgement.** Half-closing does not mean the server has read, processed or accepted anything. It only means nothing more is coming. - **Not reversible.** There is no way to reopen the request direction; sending more requires a new call. A candidate who can say "half-close ends my direction, the call is still open, and the server's status is what actually ends it" has the mechanism right.
- May the server send its response before the client half-closes?On a client-streaming method, no in practice: the method's contract is one response for the whole request sequence, so the server has nothing to answer until the sequence ends. What the server *can* do at any moment is end the call early with a status — for example when an early message is invalid — and that ends the call outright rather than answering it.
- Does the client have to half-close on a unary call?Not explicitly. A unary caller has exactly one request message, so the library finishes the request direction as soon as that message is sent. Half-close is only something you perform yourself on the two kinds where the client sends a sequence: client-streaming and bidirectional.
- What happens if the client sends another request message after half-closing?It is an error on that call, not a new call. The request direction is finished and cannot be reopened, so the send fails locally or aborts the call; the server will never see the message. Sending more data requires starting a new call.
Finishing your side of a letter with "that is everything from me" and then sitting by the letterbox: you have stopped writing, the correspondence has not ended.
saying these in an interview costs you the question
- Thinks half-close ends the call and no response can follow
- Believes half-close closes the underlying long-lived connection
- Uses half-close as the way to abort a call
- Assumes messages still in flight are discarded at half-close
- Thinks the server half-closes its own direction instead of sending a status
- Expects the server to notice the last message without a half-close