risingwavelabs/risingwave · error · io::Error

failed to write_char

Error message

failed to write_char [{c}]

What it means

In write_str_bytes (pg_message.rs:797), while escaping a string into the outgoing buffer, the fallback branch calls std::fmt::Write::write_char on the BytesMut-backed formatter; if the underlying write fails, the error is wrapped as Error::other("failed to write_char [{c}]"). In practice this signals an internal failure while serializing a string literal into a backend message, not a client-input problem.

Solutions

  1. Reproduce with the logged character [c] and check whether the escaping logic should be handling it in an earlier branch (e.g. quote/backslash handling) rather than the fallback write_char.
  2. Check server logs/monitoring for memory pressure or allocation failures at serialization time; a failed append to the buffer is the usual root cause.
  3. If a custom writer or patched BytesMut wrapper is in use, verify its fmt::Write implementation never returns Err for valid chars.
  4. This is effectively an internal invariant violation — if reproducible with normal data, file a bug with the offending character and message type.

Example fix

// before: fallback may fail silently on exotic chars
std::fmt::Write::write_char(buf, c).map_err(|_| Error::other(anyhow!("failed to write_char [{c}]")))?;
// after: ensure capacity before writing, avoiding transient append failure
buf.reserve(c.len_utf8());
std::fmt::Write::write_char(buf, c).map_err(|_| Error::other(anyhow!("failed to write_char [{c}]")))?;
Defensive patterns

Strategy: try-catch

Try / catch

// serialization path
if let Err(e) = msg.write(&mut buf).await {
    if e.to_string().starts_with("failed to write_char") {
        log::error!("internal serialization failure: {}", e);
        // do not send the partially built buffer; reset and close
        buf.clear();
    }
    return Err(e.into());
}

Prevention

When it happens

Trigger: Calling the message write path (FeMessage::write / serialization of a response containing a string field) when the fmt::Write impl over the byte buffer returns Err — essentially only on buffer allocation/append failure; a normal char always succeeds, so this surfaces as an internal invariant issue during response serialization.

Common situations: Writing error/data row responses containing unusual characters where escaping hits an unexpected branch; running out of memory while growing the response buffer; bugs in a custom writer wrapped around BytesMut.

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 risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/e35f37f8c3595f74. Report an issue: GitHub.

Appendix: source

Thrown at src/utils/pgwire/src/pg_message.rs:797

                        for c in s.as_str().chars() {
                            // As suggested in https://en.wikipedia.org/wiki/Tab-separated_values
                            // we only escape "\t\b\r\\"
                            match c {
                                '\t' => {
                                    buf.put_slice(b"\\t");
                                }
                                '\n' => {
                                    buf.put_slice(b"\\n");
                                }
                                '\r' => {
                                    buf.put_slice(b"\\r");
                                }
                                '\\' => {
                                    buf.put_slice(b"\\\\");
                                }
                                _ => {
                                    std::fmt::Write::write_char(buf, c).map_err(|_| {
                                        Error::other(anyhow!("failed to write_char [{c}]"))
                                    })?;
                                }
                            }
                        }
                        Ok(())
                    }
                    match row.values() {
                        [] => {}
                        [first, rest @ ..] => {
                            write_str_bytes(buf, first)?;

                            for rest in rest {
                                buf.put_u8(b'\t');
                                write_str_bytes(buf, rest)?;
                            }
                        }
                    }
                    buf.put_u8(b'\n');

View on GitHub (pinned to 6469eb736d)