{"record":{"id":"4ced16adc3a09a2f","repo":"grpc/grpc-java","slug":"stream-to-the-s2a-is-closed","errorCode":null,"errorMessage":"Stream to the S2A is closed.","messagePattern":"Stream to the S2A is closed\\.","errorType":"exception","errorClass":"ConnectionClosedException","httpStatus":null,"severity":"error","filePath":"s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java","lineNumber":110,"sourceCode":"  }\n\n  /**\n   * Sends a request and returns the response. Caller must wait until this method executes prior to\n   * calling it again. If this method throws {@code ConnectionClosedException}, then it should not\n   * be called again, and both {@code reader} and {@code writer} are closed.\n   *\n   * @param req the {@code SessionReq} message to be sent to the S2A server.\n   * @return the {@code SessionResp} message received from the S2A server.\n   * @throws ConnectionClosedException if {@code reader} or {@code writer} calls their {@code\n   *     onCompleted} method.\n   * @throws IOException if an unexpected response is received, or if the {@code reader} or {@code\n   *     writer} calls their {@code onError} method.\n   */\n  @SuppressWarnings(\"CheckReturnValue\")\n  public SessionResp send(SessionReq req) throws IOException, InterruptedException {\n    if (doneWriting && doneReading) {\n      logger.log(Level.INFO, \"Stream to the S2A is closed.\");\n      throw new ConnectionClosedException(\"Stream to the S2A is closed.\");\n    }\n    createWriterIfNull();\n    if (!responses.isEmpty()) {\n      IOException exception = null;\n      try {\n        responses.take().getResultOrThrow();\n      } catch (IOException e) {\n        exception = e;\n      }\n      responses.clear();\n      if (exception != null) {\n        throw new IOException(\n            \"Received an unexpected response from a host at the S2A's address. The S2A might be\"\n                + \" unavailable.\", exception);\n      } else {\n        throw new IOException(\"Received an unexpected response from a host at the S2A's address.\");\n      }\n    }","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java#L92-L128","documentation":"S2AStub.send() throws ConnectionClosedException when the bidirectional stream to the S2A process has already been fully closed ( both writer and reader are done ). Attempting to send a SessionReq over a closed stream cannot succeed, so the call fails fast. The message is also logged at INFO before throwing.","triggerScenarios":"Calling S2AStub.send(SessionReq) after the stub's stream to the S2A has finished writing and reading ( doneWriting && doneReading ), e.g. after shutdown() or after a previous stream error terminated both directions.","commonSituations":"Reusing a cached S2AStub whose gRPC stream already terminated; S2A process crash followed by continued validation calls; calling send() from checkPeerTrusted after a prior handshake closed the stream.","solutions":["Recreate the S2AStub ( and its underlying channel/stream ) after it is closed, then retry the request.","Check stub liveness ( or catch ConnectionClosedException ) before reusing a cached stub.","Ensure the S2A process is running and the channel to it has not been shut down."],"exampleFix":"// before\nresp = cachedStub.send(req);\n// after\ntry {\n  resp = cachedStub.send(req);\n} catch (ConnectionClosedException e) {\n  cachedStub = S2AStubFactory.create(channel);\n  resp = cachedStub.send(req);\n}","handlingStrategy":"try-catch","validationCode":"if (stub == null || stub.isShutdown()) {\n  stub = stubFactory.create(channel);\n}","typeGuard":null,"tryCatchPattern":"try {\n  resp = stub.send(req);\n} catch (ConnectionClosedException e) {\n  stub = stubFactory.create(channel); // rebuild and retry once\n  resp = stub.send(req);\n}","preventionTips":["Do not cache S2AStub instances beyond the lifetime of their stream.","Watch for S2A process health and recreate stubs on termination.","Wrap all send() calls with a rebuild-and-retry helper."],"tags":["grpc","stream-closed","s2a","network"],"backgroundTag":"connection-refused","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"}