grpc/grpc-java · error · IOException

Unable to perform write due to unavailable sink.

Error message

Unable to perform write due to unavailable sink.

What it means

AsyncSink throws this IOException from the write thread (WriteRunnable.run) when the underlying okio sink has not been initialized yet, i.e. the transport's start() has not completed or stream creation failed before the sink was set. It signals that gRPC-okhttp cannot write frames because the transport's buffered sink is unavailable.

Source

Thrown at okhttp/src/main/java/io/grpc/okhttp/AsyncSink.java:230

          transportExceptionHandler.onException(e);
        }
        try {
          if (socket != null) {
            socket.close();
          }
        } catch (IOException e) {
          transportExceptionHandler.onException(e);
        }
      }
    });
  }

  private abstract class WriteRunnable implements Runnable {
    @Override
    public final void run() {
      try {
        if (sink == null) {
          throw new IOException("Unable to perform write due to unavailable sink.");
        }
        doRun();
      } catch (Exception e) {
        transportExceptionHandler.onException(e);
      }
    }

    public abstract void doRun() throws IOException;
  }

  private class LimitControlFramesWriter extends ForwardingFrameWriter {
    public LimitControlFramesWriter(FrameWriter delegate) {
      super(delegate);
    }

    @Override
    public void ackSettings(Settings peerSettings) throws IOException {
      controlFramesInWrite++;

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Ensure the transport is started (start(listener)) and its start callback has completed before sending any RPC frames
  2. Check for exceptions reported earlier via transportExceptionHandler — the sink is null because startup failed; fix the root cause
  3. Avoid sharing streams across threads without synchronization; serialize writes through the stream's call executor
  4. Upgrade grpc-okhttp; older versions had races between start and write that could leave sink null

Example fix

// before
stream.sendMessage(...); // called immediately after creating transport, before start completes
// after
transport.start(transportListener);
// wait for listener.transportReady() or use ClientStream listener before writing
stream.sendMessage(...);
Defensive patterns

Strategy: try-catch

Validate before calling

if (channel.isShutdown() || channel.isTerminated()) { throw new IllegalStateException("channel shut down"); }

Type guard

boolean canSend(ManagedChannel ch) { return !ch.isShutdown() && !ch.isTerminated(); }

Try / catch

try { stream.sendMessage(msg); } catch (IOException e) { if (e.getMessage().contains("unavailable sink")) { restartTransport(); } else { throw e; } }

Prevention

When it happens

Trigger: Calling ClientStream.sendFrame / writing to a stream before the transport finished starting, after a failed transport start, or concurrently with shutdown so the sink field is still null when WriteRunnable runs.

Common situations: Servers/clients behind flaky networks where connection setup fails; races between start() and the first write; calling write on a stream whose transport was torn down; bugs in transport lifecycle management.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/0026c2f39c89c5ca. Report an issue: GitHub.