private void doSendMessage(I message) {
        final RequestLogAccess log = ctx.log();
        if (log.isComplete()) {
            // Completed already; no need to send anymore.
            return;
        }

        try {
            if (!log.isAvailable(RequestLogProperty.REQUEST_CONTENT)) {
                ctx.logBuilder().requestContent(GrpcLogUtil.rpcRequest(method, simpleMethodName(), message),
                                                null);
            }
            final ByteBuf serialized = marshaller.serializeRequest(message);
            req.write(requestFramer.writePayload(serialized));
            req.whenConsumed().thenRun(() -> {
                if (pendingMessagesUpdater.decrementAndGet(this) == 0) {
                    executor.execute(() -> {
                        try (SafeCloseable ignored = ctx.push()) {
                            assert listener != null;
                            listener.onReady();
                        } catch (Throwable t) {
                            closeWhenListenerThrows(t);
                        }
                    });
                }
            });
        } catch (Throwable t) {
            cancel(null, t);
        }
    }
Encapsulates the state of a single client call, writing messages from the client and reading responses
from the server, passing to business logic via {@link ClientCall.Listener}.
grpc/src/main/java/com/linecorp/armeria/internal/client/grpc/ArmeriaClientCall.java