nautechsystems/nautilus_trader · error · anyhow::Error
Failed to write COPY trailer: {e}
Error message
Failed to write COPY trailer: {e} What it means
write_copy_trailer sends the 2-byte (-1 i16) binary COPY trailer that terminates every binary COPY stream. If the sink rejects the trailer, the COPY cannot be finalized and the server will discard the stream; this error wraps that send failure. It is shared by all copy_* functions (blocks, tokens, pools, swaps, liquidity updates, collects).
Source
Thrown at crates/adapters/blockchain/src/cache/copy.rs:918
.send(row_data)
.await
.map_err(|e| anyhow::anyhow!("Failed to write pool data: {e}"))?;
Ok(())
}
/// Writes the PostgreSQL COPY binary format trailer.
///
/// The trailer is a 2-byte value of -1 to indicate end of data.
async fn write_copy_trailer(
&self,
copy_in: &mut sqlx::postgres::PgCopyIn<sqlx::pool::PoolConnection<sqlx::Postgres>>,
) -> anyhow::Result<()> {
// Binary trailer: -1 as i16 to indicate end of data
let trailer = (-1i16).to_be_bytes();
copy_in
.send(trailer.to_vec())
.await
.map_err(|e| anyhow::anyhow!("Failed to write COPY trailer: {e}"))?;
Ok(())
}
}
fn write_copy_numeric(row: &mut Vec<u8>, value: impl Display) {
let value = value.to_string();
row.extend_from_slice(&(value.len() as i32).to_be_bytes());
row.extend_from_slice(value.as_bytes());
}
View on GitHub (pinned to 18893faf8b)
Solutions
- Investigate the earlier rows/errors first — the trailer failure is nearly always a secondary symptom
- Check connection liveness and TCP keepalive settings for long COPY operations
- Retry the full copy_* operation; partial COPY data is not committed when the trailer fails
- Verify no intermediate layer (pool, pgbouncer) is terminating the session during COPY
Example fix
// before: treating trailer failure as root cause
match write_copy_trailer(&mut copy_in).await {
Err(e) => log::error!("trailer failed: {e}"), // hides real cause
...
}
// after: surface the full error chain for diagnosis
write_copy_trailer(&mut copy_in).await
.map_err(|e| e.context("COPY trailer failed; check prior row errors and connection health"))?; Defensive patterns
Strategy: try-catch
Validate before calling
// health-check the connection before starting a COPY sequence
sqlx::raw_sql("SELECT 1").fetch_one(&pool).await?; Try / catch
async fn copy_with_trailer_guard<F, Fut>(op: F) -> anyhow::Result<()>
where F: Fn() -> Fut, Fut: Future<Output = anyhow::Result<()>> {
op().await.map_err(|e|
// trailer failures are usually secondary; surface the full chain
anyhow::anyhow!("COPY sequence failed (check row errors/connection): {e:#}")
)
} Prevention
- Treat trailer errors as symptoms: always inspect earlier row errors in the same COPY
- Configure TCP keepalive so idle gaps between rows don't drop the connection
- Avoid PgBouncer transaction pooling modes that can interrupt COPY sessions
- Retry the entire copy_* call — the server discards unterminated COPY data
When it happens
Trigger: copy_in.send(trailer) fails at the end of any copy_* operation — almost always because the connection or COPY stream already broke (rows earlier failed, connection dropped, server aborted), leaving no valid channel for the trailer.
Common situations: A mid-stream row error silently closed the sink so the trailer fails; idle timeout triggered between last row and trailer; database restarted during a large backfill.
Related errors
- Failed to start COPY operation: {e}
- Failed to finish COPY operation: {e}
- Failed to write pool fee collect data: {e}
- Failed to write token data: {e}
- Failed to write pool data: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/c86732f464ae3274.
Report an issue: GitHub.