Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/jsc/bindings/webcore/streams/JSReadableStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ class JSReadableStream final : public JSC::JSNonFinalObject {
// reads the raw slot, so the fetch/server push-side is unaffected.
if (m_nativeTextMode)
return {};
// The native source of an errored stream only knows that it ended; the stored error is here.
if (m_state == ReadableStreamState::Errored)
return {};
return m_nativePtr.get(); // may be empty
}
// Transferred to a node:stream Readable, drained as a Body, or detached: the handle is off limits.
Expand Down
3 changes: 3 additions & 0 deletions src/jsc/bindings/webcore/streams/WebStreamsExports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ using namespace Bun::WebStreams;
// A JSReadableStream's tag and native source. Pure: no scope, no traps, no script.
static int32_t tagOfStream(JSReadableStream* stream, void** ptr)
{
// An errored stream reads as a JS stream: its readers get the stored error, which the native source does not have.
if (stream->m_state == ReadableStreamState::Errored)
return 0;
// The RAW handle slot, not nativePtrForJS(): a transferred stream still tags.
JSValue handle = stream->m_nativePtr.get();
if (handle.isEmpty() || !handle.isCell())
Expand Down
14 changes: 14 additions & 0 deletions src/runtime/webcore/ByteStream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,20 @@ impl ByteStream {
streams::Result::Pending(self.pending.as_ptr())
}

/// The JS stream was errored with `reason`. A native reader that waits now fails with it.
pub(crate) fn error_native_consumer(&self, reason: JSValue) {
let waiting = self.sink.get().is_some()
|| self.buffer_action.get().is_some()
|| self.pending.get().state == streams::PendingState::Pending;
self.on_data(streams::Result::Err(if waiting {
let global = self.parent_const().global_this();
streams::StreamError::JSValue(StrongOptional::create(reason, global))
} else {
// Kept for a reader that already holds this source and pulls later. A stored `reason` would be a GC root.
streams::StreamError::AbortReason(jsc::CommonAbortReason::UserAbort)
}));
}

pub(crate) fn on_cancel(&self) {
bun_jsc::mark_binding!();
let view = self.value();
Expand Down
24 changes: 24 additions & 0 deletions src/runtime/webcore/FileReader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,32 @@ impl FileReader {
}
}

/// The JS stream was errored with `reason`. A native reader that waits now fails with it.
pub(crate) fn error_native_consumer(&self, reason: jsc::JSValue) {
// SAFETY: see `parent()`.
let _pin = unsafe { SourcePin::new(self.parent()) };
let global = self.parent_const().global_this();
let err = streams::StreamError::JSValue(jsc::strong::Optional::create(reason, global));
let sink = *self.sink.get();
if sink.is_some() {
self.detach_sink(Some(&err));
sink.end(Some(err));
} else if self.pending.get().state == streams::PendingState::Pending {
self.pending
.with_mut(|p| p.result = streams::Result::Err(err));
self.pending.with_mut(|p| p.run());
}
}
Comment thread
robobun marked this conversation as resolved.
Resolved

pub(crate) fn on_cancel(&self) {
// A sink still wired here must fail, not see an EOF and commit a truncated body.
let sink = *self.sink.get();
self.unpipe_without_deref();
if sink.is_some() {
sink.end(Some(streams::StreamError::AbortReason(
jsc::CommonAbortReason::UserAbort,
)));
}
if self.done.get() {
return;
}
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/webcore/ReadableStream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ impl ReadableStream {
/// Like [`Self::cancel`] but pending reads reject with `reason` instead of resolving `{done: true}`.
pub(crate) fn error(&self, global_this: &JSGlobalObject, reason: JSValue) -> JsResult<()> {
let result = bun_jsc::cpp::ReadableStream__error(self.value, global_this, reason);
if let Some(bytes) = self.ptr.bytes() {
bytes.error_native_consumer(reason);
} else if let Some(file) = self.ptr.file() {
file.error_native_consumer(reason);
}
self.done();
result
}
Expand Down
Loading
Loading