t8y2/dbx · warning · std::io::Error
Excel import consumer closed
Error message
Excel import consumer closed
What it means
This is not a real io::Error from Excel parsing: when the async consumer of the streaming XLSX import channel has dropped (hung up), blocking_send on the progress channel fails, and the code converts that into a std::io::ErrorKind::BrokenPipe with the message "Excel import consumer closed" before propagating it with `?`. The library uses it as an internal signal that nobody is listening on the import stream anymore, so the reader thread should stop.
Source
Thrown at crates/dbx-core/src/table_import.rs:3093
.ok_or_else(|| format!("Workbook sheet not found: {sheet_name}"))?;
let shared_strings_bytes = match zip.by_name("xl/sharedStrings.xml") {
Ok(file) => file.size(),
Err(zip::result::ZipError::FileNotFound) => 0,
Err(error) => return Err(error.to_string()),
};
let shared_progress_end = if shared_strings_bytes > 0 { total_bytes / 2 } else { 0 };
let progress_sender = sender.clone();
let mut last_shared_progress = Instant::now() - IMPORT_PROGRESS_INTERVAL;
let mut on_shared_progress = |bytes_read: u64| {
let progress = bytes_read
.saturating_mul(shared_progress_end)
.checked_div(shared_strings_bytes.max(1))
.unwrap_or_default()
.min(shared_progress_end);
if last_shared_progress.elapsed() >= IMPORT_PROGRESS_INTERVAL || bytes_read >= shared_strings_bytes {
progress_sender
.blocking_send(Ok(XlsxStreamMessage::Progress(progress)))
.map_err(|_| std::io::Error::new(std::io::ErrorKind::BrokenPipe, "Excel import consumer closed"))?;
last_shared_progress = Instant::now();
}
Ok(())
};
let is_cancelled = || cancelled.load(Ordering::Acquire);
let mut shared_strings = open_xlsx_shared_strings_with_control(
&mut zip,
MAX_IN_MEMORY_XLSX_SHARED_STRINGS_BYTES,
&is_cancelled,
&mut on_shared_progress,
)?;
let row_range = effective_import_row_range(options)?;
let sheet = zip.by_name(&sheet_path).map_err(|error| error.to_string())?;
let uncompressed_sheet_bytes = sheet.size().max(1);
let mut reader = XmlReader::from_reader(BufReader::new(sheet));
reader.config_mut().trim_text(false);
let mut rows = XlsxStreamRowsState::new(sender, row_range, None, expected_columns, batch_size);
let mut buffer = Vec::new();View on GitHub (pinned to c0390bff16)
Solutions
- Treat BrokenPipe with this message as a normal cancellation/early-close signal, not a data error — stop the import and clean up.
- Keep the progress-channel receiver alive until the reader task has finished, or join/await the reader before dropping the consumer.
- If closing early is intentional, filter/handle io::ErrorKind::BrokenPipe from the import call and report 'cancelled' instead of 'failed'.
- Check for a cancel flag (is_cancelled) being honored promptly so the reader exits itself before the consumer disappears.
Example fix
// before
let mut rx = importer.progress_receiver();
if first_error { drop(rx); } // reader then panics/errors with BrokenPipe
// after
let rx = importer.progress_receiver();
let handle = tokio::spawn(importer.run());
// drain rx until handle completes, then:
drop(rx);
let result = handle.await; // handle BrokenPipe as cancellation if it surfaces Defensive patterns
Strategy: try-catch
Validate before calling
// Rust: check the receiver is still connected before starting assert!(!progress_rx.is_closed(), "progress consumer already dropped; aborting import early");
Type guard
fn is_consumer_gone(err: &std::io::Error) -> bool {
err.kind() == std::io::ErrorKind::BrokenPipe
&& err.to_string().contains("Excel import consumer closed")
} Try / catch
match table_import::run_xlsx_import(...).await {
Ok(res) => res,
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
// consumer closed: treat as user cancellation, not failure
ImportOutcome::Cancelled
}
Err(e) => return Err(e.into()),
} Prevention
- Keep the progress-channel receiver alive until the reader task finishes (join before drop).
- Use a cancellation flag checked by the reader instead of dropping the receiver to stop imports.
- Map BrokenPipe from import streams to a 'cancelled' status in your UI/error model.
- On abort paths, send a final message or close the channel explicitly and await the reader.
When it happens
Trigger: Calling the streaming XLSX import API (table_import.rs, open_xlsx_shared_strings_with_control path) and dropping/closing the receiver side of the progress channel before the import finishes — e.g. the UI consumer task is cancelled, aborted, or the future holding the receiver is dropped while the reader still emits periodic Progress messages (every IMPORT_PROGRESS_INTERVAL or when shared-strings bytes are fully read).
Common situations: User cancels a long Excel import mid-stream; a web request/aborted connection drops the consumer future; a timeout cancels the receiving task while the background reader thread continues; the importer is driven with a channel that is closed early by error handling on the consumer side.
Related errors
- TDengine operation was cancelled
- a batch cancellation token is always available
- driver token registered
- a cancellation token is always available
- failed to create agent runtime
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/18b044c2f7e29620.
Report an issue: GitHub.