Skip to content

Commit dfbb1e6

Browse files
sokraalexkirsz
andauthored
improve error handling in update stream (vercel/turborepo#4705)
### Description Handle fatal errors in update stream, by sending an not found update with issues. --------- Co-authored-by: Alex Kirszenberg <[email protected]>
1 parent e56edd9 commit dfbb1e6

File tree

4 files changed

+136
-68
lines changed

4 files changed

+136
-68
lines changed

crates/turbopack-dev-server/src/http.rs

+18-21
Original file line numberDiff line numberDiff line change
@@ -42,30 +42,27 @@ enum GetFromSourceResult {
4242
async fn get_from_source(
4343
source: ContentSourceVc,
4444
request: TransientInstance<SourceRequest>,
45-
issue_repoter: IssueReporterVc,
4645
) -> Result<GetFromSourceResultVc> {
47-
Ok(
48-
match &*resolve_source_request(source, request, issue_repoter).await? {
49-
ResolveSourceRequestResult::Static(static_content_vc, header_overwrites) => {
50-
let static_content = static_content_vc.await?;
51-
if let AssetContent::File(file) = &*static_content.content.content().await? {
52-
GetFromSourceResult::Static {
53-
content: file.await?,
54-
status_code: static_content.status_code,
55-
headers: static_content.headers.await?,
56-
header_overwrites: header_overwrites.await?,
57-
}
58-
} else {
59-
GetFromSourceResult::NotFound
46+
Ok(match &*resolve_source_request(source, request).await? {
47+
ResolveSourceRequestResult::Static(static_content_vc, header_overwrites) => {
48+
let static_content = static_content_vc.await?;
49+
if let AssetContent::File(file) = &*static_content.content.content().await? {
50+
GetFromSourceResult::Static {
51+
content: file.await?,
52+
status_code: static_content.status_code,
53+
headers: static_content.headers.await?,
54+
header_overwrites: header_overwrites.await?,
6055
}
56+
} else {
57+
GetFromSourceResult::NotFound
6158
}
62-
ResolveSourceRequestResult::HttpProxy(proxy) => {
63-
GetFromSourceResult::HttpProxy(proxy.await?)
64-
}
65-
ResolveSourceRequestResult::NotFound => GetFromSourceResult::NotFound,
6659
}
67-
.cell(),
68-
)
60+
ResolveSourceRequestResult::HttpProxy(proxy) => {
61+
GetFromSourceResult::HttpProxy(proxy.await?)
62+
}
63+
ResolveSourceRequestResult::NotFound => GetFromSourceResult::NotFound,
64+
}
65+
.cell())
6966
}
7067

7168
/// Processes an HTTP request within a given content source and returns the
@@ -77,7 +74,7 @@ pub async fn process_request_with_content_source(
7774
) -> Result<Response<hyper::Body>> {
7875
let original_path = request.uri().path().to_string();
7976
let request = http_request_to_source_request(request).await?;
80-
let result = get_from_source(source, TransientInstance::new(request), issue_reporter);
77+
let result = get_from_source(source, TransientInstance::new(request));
8178
handle_issues(result, &original_path, "get_from_source", issue_reporter).await?;
8279
match &*result.strongly_consistent().await? {
8380
GetFromSourceResult::Static {

crates/turbopack-dev-server/src/source/resolve.rs

+1-14
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::{
66
use anyhow::{bail, Result};
77
use hyper::Uri;
88
use turbo_tasks::{TransientInstance, Value};
9-
use turbopack_core::issue::IssueReporterVc;
109

1110
use super::{
1211
headers::{HeaderValue, Headers},
@@ -15,10 +14,7 @@ use super::{
1514
ContentSourceContent, ContentSourceDataVary, ContentSourceResult, ContentSourceVc,
1615
HeaderListVc, ProxyResultVc, StaticContentVc,
1716
};
18-
use crate::{
19-
handle_issues,
20-
source::{ContentSource, ContentSourceData, GetContentSourceContent},
21-
};
17+
use crate::source::{ContentSource, ContentSourceData, GetContentSourceContent};
2218

2319
/// The result of [`resolve_source_request`]. Similar to a
2420
/// `ContentSourceContent`, but without the `Rewrite` variant as this is taken
@@ -36,7 +32,6 @@ pub enum ResolveSourceRequestResult {
3632
pub async fn resolve_source_request(
3733
source: ContentSourceVc,
3834
request: TransientInstance<SourceRequest>,
39-
issue_reporter: IssueReporterVc,
4035
) -> Result<ResolveSourceRequestResultVc> {
4136
let mut data = ContentSourceData::default();
4237
let mut current_source = source;
@@ -47,14 +42,6 @@ pub async fn resolve_source_request(
4742
let mut response_header_overwrites = Vec::new();
4843
loop {
4944
let result = current_source.get(&current_asset_path, Value::new(data));
50-
handle_issues(
51-
result,
52-
&original_path,
53-
"get content from source",
54-
issue_reporter,
55-
)
56-
.await?;
57-
5845
match &*result.strongly_consistent().await? {
5946
ContentSourceResult::NotFound => break Ok(ResolveSourceRequestResult::NotFound.cell()),
6047
ContentSourceResult::NeedData(needed) => {

crates/turbopack-dev-server/src/update/server.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,11 @@ impl<P: SourceProvider + Clone + Send + Sync> UpdateServer<P> {
6767
let source = source_provider.get_source();
6868
resolve_source_request(
6969
source,
70-
TransientInstance::new(request),
71-
self.issue_reporter
70+
TransientInstance::new(request)
7271
)
7372
}
7473
};
75-
match UpdateStream::new(TransientInstance::new(Box::new(get_content))).await {
74+
match UpdateStream::new(resource.to_string(), TransientInstance::new(Box::new(get_content))).await {
7675
Ok(stream) => {
7776
streams.insert(resource, stream);
7877
}
@@ -94,7 +93,14 @@ impl<P: SourceProvider + Clone + Send + Sync> UpdateServer<P> {
9493
}
9594
}
9695
Some((resource, update)) = streams.next() => {
97-
Self::send_update(&mut client, &mut streams, resource, &update).await?;
96+
match update {
97+
Ok(update) => {
98+
Self::send_update(&mut client, &mut streams, resource, &update).await?;
99+
}
100+
Err(err) => {
101+
eprintln!("Failed to get update for {resource}: {}", PrettyPrintError(&err));
102+
}
103+
}
98104
}
99105
else => break
100106
}

crates/turbopack-dev-server/src/update/stream.rs

+107-29
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
use std::pin::Pin;
22

3-
use anyhow::{bail, Result};
3+
use anyhow::Result;
44
use futures::{prelude::*, Stream};
55
use tokio::sync::mpsc::Sender;
66
use tokio_stream::wrappers::ReceiverStream;
7-
use turbo_tasks::{CollectiblesSource, IntoTraitRef, State, TraitRef, TransientInstance};
7+
use turbo_tasks::{
8+
primitives::StringVc, CollectiblesSource, IntoTraitRef, State, TraitRef, TransientInstance,
9+
};
10+
use turbo_tasks_fs::{FileSystem, FileSystemPathVc};
811
use turbopack_core::{
9-
issue::{IssueVc, PlainIssueReadRef},
12+
error::PrettyPrintError,
13+
issue::{
14+
Issue, IssueSeverity, IssueSeverityVc, IssueVc, OptionIssueProcessingPathItemsVc,
15+
PlainIssueReadRef,
16+
},
17+
server_fs::ServerFileSystemVc,
1018
version::{
1119
NotFoundVersionVc, PartialUpdate, TotalUpdate, Update, UpdateReadRef, VersionVc,
1220
VersionedContent,
@@ -38,12 +46,43 @@ fn extend_issues(issues: &mut Vec<PlainIssueReadRef>, new_issues: Vec<PlainIssue
3846

3947
#[turbo_tasks::function]
4048
async fn get_update_stream_item(
49+
resource: &str,
4150
from: VersionStateVc,
4251
get_content: TransientInstance<GetContentFn>,
4352
) -> Result<UpdateStreamItemVc> {
4453
let content = get_content();
54+
let mut plain_issues = peek_issues(content).await?;
55+
56+
let content_value = match content.await {
57+
Ok(content) => content,
58+
Err(e) => {
59+
plain_issues.push(
60+
FatalStreamIssue {
61+
resource: resource.to_string(),
62+
description: StringVc::cell(format!("{}", PrettyPrintError(&e))),
63+
}
64+
.cell()
65+
.as_issue()
66+
.into_plain(OptionIssueProcessingPathItemsVc::none())
67+
.await?,
68+
);
69+
70+
let update = Update::Total(TotalUpdate {
71+
to: NotFoundVersionVc::new()
72+
.as_version()
73+
.into_trait_ref()
74+
.await?,
75+
})
76+
.cell();
77+
return Ok(UpdateStreamItem::Found {
78+
update: update.await?,
79+
issues: plain_issues,
80+
}
81+
.cell());
82+
}
83+
};
4584

46-
match *content.await? {
85+
match *content_value {
4786
ResolveSourceRequestResult::Static(static_content_vc, _) => {
4887
let static_content = static_content_vc.await?;
4988

@@ -56,8 +95,7 @@ async fn get_update_stream_item(
5695
let from = from.get();
5796
let update = resolved_content.update(from);
5897

59-
let mut plain_issues = peek_issues(update).await?;
60-
extend_issues(&mut plain_issues, peek_issues(content).await?);
98+
extend_issues(&mut plain_issues, peek_issues(update).await?);
6199

62100
let update = update.await?;
63101

@@ -74,7 +112,7 @@ async fn get_update_stream_item(
74112
return Ok(UpdateStreamItem::NotFound.cell());
75113
}
76114

77-
let plain_issues = peek_issues(proxy_result).await?;
115+
extend_issues(&mut plain_issues, peek_issues(proxy_result).await?);
78116

79117
let from = from.get();
80118
if let Some(from) = ProxyResultVc::resolve_from(from).await? {
@@ -98,8 +136,6 @@ async fn get_update_stream_item(
98136
.cell())
99137
}
100138
_ => {
101-
let plain_issues = peek_issues(content).await?;
102-
103139
let update = if plain_issues.is_empty() {
104140
// Client requested a non-existing asset
105141
// It might be removed in meantime, reload client
@@ -127,19 +163,17 @@ async fn get_update_stream_item(
127163

128164
#[turbo_tasks::function]
129165
async fn compute_update_stream(
166+
resource: &str,
130167
from: VersionStateVc,
131168
get_content: TransientInstance<GetContentFn>,
132-
sender: TransientInstance<Sender<UpdateStreamItemReadRef>>,
133-
) -> Result<()> {
134-
let item = get_update_stream_item(from, get_content)
169+
sender: TransientInstance<Sender<Result<UpdateStreamItemReadRef>>>,
170+
) {
171+
let item = get_update_stream_item(resource, from, get_content)
135172
.strongly_consistent()
136-
.await?;
137-
138-
if sender.send(item).await.is_err() {
139-
bail!("channel closed");
140-
}
173+
.await;
141174

142-
Ok(())
175+
// Send update. Ignore channel closed error.
176+
let _ = sender.send(item).await;
143177
}
144178

145179
#[turbo_tasks::value]
@@ -172,10 +206,15 @@ impl VersionStateVc {
172206
}
173207
}
174208

175-
pub(super) struct UpdateStream(Pin<Box<dyn Stream<Item = UpdateStreamItemReadRef> + Send + Sync>>);
209+
pub(super) struct UpdateStream(
210+
Pin<Box<dyn Stream<Item = Result<UpdateStreamItemReadRef>> + Send + Sync>>,
211+
);
176212

177213
impl UpdateStream {
178-
pub async fn new(get_content: TransientInstance<GetContentFn>) -> Result<UpdateStream> {
214+
pub async fn new(
215+
resource: String,
216+
get_content: TransientInstance<GetContentFn>,
217+
) -> Result<UpdateStream> {
179218
let (sx, rx) = tokio::sync::mpsc::channel(32);
180219

181220
let content = get_content();
@@ -190,13 +229,18 @@ impl UpdateStream {
190229
};
191230
let version_state = VersionStateVc::new(version.into_trait_ref().await?).await?;
192231

193-
compute_update_stream(version_state, get_content, TransientInstance::new(sx));
232+
compute_update_stream(
233+
&resource,
234+
version_state,
235+
get_content,
236+
TransientInstance::new(sx),
237+
);
194238

195239
let mut last_had_issues = false;
196240

197241
let stream = ReceiverStream::new(rx).filter_map(move |item| {
198242
let (has_issues, issues_changed) =
199-
if let UpdateStreamItem::Found { issues, .. } = &*item {
243+
if let Some(UpdateStreamItem::Found { issues, .. }) = item.as_deref().ok() {
200244
let has_issues = !issues.is_empty();
201245
let issues_changed = has_issues != last_had_issues;
202246
last_had_issues = has_issues;
@@ -206,12 +250,8 @@ impl UpdateStream {
206250
};
207251

208252
async move {
209-
match &*item {
210-
UpdateStreamItem::NotFound => {
211-
// Propagate not found updates so we can drop this update stream.
212-
Some(item)
213-
}
214-
UpdateStreamItem::Found { update, .. } => {
253+
match item.as_deref() {
254+
Ok(UpdateStreamItem::Found { update, .. }) => {
215255
match &**update {
216256
Update::Partial(PartialUpdate { to, .. })
217257
| Update::Total(TotalUpdate { to }) => {
@@ -232,6 +272,10 @@ impl UpdateStream {
232272
}
233273
}
234274
}
275+
_ => {
276+
// Propagate other updates
277+
Some(item)
278+
}
235279
}
236280
}
237281
});
@@ -241,7 +285,7 @@ impl UpdateStream {
241285
}
242286

243287
impl Stream for UpdateStream {
244-
type Item = UpdateStreamItemReadRef;
288+
type Item = Result<UpdateStreamItemReadRef>;
245289

246290
fn poll_next(
247291
self: Pin<&mut Self>,
@@ -260,3 +304,37 @@ pub enum UpdateStreamItem {
260304
issues: Vec<PlainIssueReadRef>,
261305
},
262306
}
307+
308+
#[turbo_tasks::value(serialization = "none")]
309+
struct FatalStreamIssue {
310+
description: StringVc,
311+
resource: String,
312+
}
313+
314+
#[turbo_tasks::value_impl]
315+
impl Issue for FatalStreamIssue {
316+
#[turbo_tasks::function]
317+
fn severity(&self) -> IssueSeverityVc {
318+
IssueSeverity::Fatal.into()
319+
}
320+
321+
#[turbo_tasks::function]
322+
fn context(&self) -> FileSystemPathVc {
323+
ServerFileSystemVc::new().root().join(&self.resource)
324+
}
325+
326+
#[turbo_tasks::function]
327+
fn category(&self) -> StringVc {
328+
StringVc::cell("websocket".to_string())
329+
}
330+
331+
#[turbo_tasks::function]
332+
fn title(&self) -> StringVc {
333+
StringVc::cell("Fatal error while getting content to stream".to_string())
334+
}
335+
336+
#[turbo_tasks::function]
337+
fn description(&self) -> StringVc {
338+
self.description
339+
}
340+
}

0 commit comments

Comments
 (0)