{"record":{"id":"375de4a1af6336e3","repo":"grpc/grpc-java","slug":"closed","errorCode":null,"errorMessage":"closed","messagePattern":"closed","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"warning","filePath":"okhttp/src/main/java/io/grpc/okhttp/AsyncSink.java","lineNumber":102,"sourceCode":"   * this method is scheduled in the executor. The socket is needed for closing.\n   *\n   * <p>should only be called once by thread of executor.\n   */\n  void becomeConnected(Sink sink, Socket socket) {\n    checkState(this.sink == null, \"AsyncSink's becomeConnected should only be called once.\");\n    this.sink = checkNotNull(sink, \"sink\");\n    this.socket = checkNotNull(socket, \"socket\");\n  }\n\n  FrameWriter limitControlFramesWriter(FrameWriter delegate) {\n    return new LimitControlFramesWriter(delegate);\n  }\n\n  @Override\n  public void write(Buffer source, long byteCount) throws IOException {\n    checkNotNull(source, \"source\");\n    if (closed) {\n      throw new IOException(\"closed\");\n    }\n    try (TaskCloseable ignore = PerfMark.traceTask(\"AsyncSink.write\")) {\n      boolean closeSocket = false;\n      synchronized (lock) {\n        buffer.write(source, byteCount);\n\n        queuedControlFrames += controlFramesInWrite;\n        controlFramesInWrite = 0;\n        if (!controlFramesExceeded && queuedControlFrames > maxQueuedControlFrames) {\n          controlFramesExceeded = true;\n          closeSocket = true;\n        } else {\n          if (writeEnqueued || flushEnqueued || buffer.completeSegmentByteCount() <= 0) {\n            return;\n          }\n          writeEnqueued = true;\n        }\n      }","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/okhttp/src/main/java/io/grpc/okhttp/AsyncSink.java#L84-L120","documentation":"okhttp's AsyncSink is a buffered sink that hands writes to a serializing writer thread. Once close() has been called, any further write(Buffer, long) throws IOException(\"closed\"). It indicates the stream was already terminated (normally, cancelled, or after an error) and the caller attempted to send more data.","triggerScenarios":"Calling write() on the request stream after close() completed, after cancel() (e.g. call was cancelled or deadline hit), or after a previous write failure closed the sink internally.","commonSituations":"Client code continuing to send messages on a gRPC okhttp channel after onError/onCompleted or after cancelling the call; racing a timeout/cancellation with outgoing writes; forgetting that half-close ends the stream.","solutions":["Stop writing once the call is completed, cancelled, or its deadline expired; check call.isCancelled() before sending","Ensure each request stream is written by a single thread or properly sequenced; AsyncSink is not safe for concurrent writers","Handle onError callbacks by aborting your send loop instead of continuing to write","Wrap writes in try-catch on IOException and treat 'closed' as stream-already-done, not a retryable error"],"exampleFix":"// before\nrequestStream.send(nextMessage); // may throw IOException(\"closed\") after cancel\n// after\nif (!call.isCancelled() && !done) {\n  try {\n    requestStream.send(nextMessage);\n  } catch (IOException e) {\n    if (\"closed\".equals(e.getMessage())) return; // stream already terminated\n    throw e;\n  }\n}","handlingStrategy":"try-catch","validationCode":"if (call.isCancelled() || call.isDeadlineExceeded() || streamClosed) {\n  return; // do not attempt write\n}","typeGuard":"boolean canWrite(CallStreamObserver<?> obs) { return !obs.isCancelled() && obs.isReady(); }","tryCatchPattern":"try {\n  sink.write(buffer, byteCount);\n} catch (IOException e) {\n  if (\"closed\".equals(e.getMessage())) {\n    // stream already terminated; stop sending, no retry\n    return;\n  }\n  throw e;\n}","preventionTips":["Stop sending as soon as onError/onCompleted/cancel fires","Check call.isCancelled() before each send on long-lived streams","Write request streams from a single thread or serialize access","Treat 'closed' IOException as terminal, not retryable"],"tags":["okhttp","grpc","stream-closed","io"],"backgroundTag":"stream-already-closed","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}