fix exit_code handling when running a scripts with ctrlc (#11466)

# Description
Fixes: #11394

When run `^sleep 3` we have an `exit_code ListStream`, and when we press
ctrl-c, this `ListStream` will return None. But it's not expected,
because `exit_code` sender in `run_external` always send an exit code
out.

This pr is trying to fix the issue by introducing a `first_guard` into
ListStream, it will always generate a value from underlying stream if
`first_guard` is true, so it's guarantee to have at least one value to
return.

And the pr also do a little refactor, which makes use of
`ListStream::from_stream` rather than construct it manually.

# User-Facing Changes
## Before
```
> nu -c "^sleep 3"  # press ctrl-c
> echo $env.LAST_EXIT_CODE
0
```

## After
```
> nu -c "^sleep 3"  # press ctrl-c
> echo $env.LAST_EXIT_CODE
255
```

# Tests + Formatting
None, sorry that I don't think it's easy to test the ctrlc behavior.

# After Submitting
None
This commit is contained in:
WindSoilder 2024-01-30 22:41:14 +08:00 committed by GitHub
parent 4e0a65c822
commit c371d1a535
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 37 additions and 42 deletions

View File

@ -140,30 +140,30 @@ pub fn run_seq(
if !contains_decimals {
// integers only
Ok(PipelineData::ListStream(
nu_protocol::ListStream {
stream: Box::new(IntSeq {
nu_protocol::ListStream::from_stream(
IntSeq {
count: first as i64,
step: step as i64,
last: last as i64,
span,
}),
ctrlc: engine_state.ctrlc.clone(),
},
},
engine_state.ctrlc.clone(),
),
None,
))
} else {
// floats
Ok(PipelineData::ListStream(
nu_protocol::ListStream {
stream: Box::new(FloatSeq {
nu_protocol::ListStream::from_stream(
FloatSeq {
first,
step,
last,
index: 0,
span,
}),
ctrlc: engine_state.ctrlc.clone(),
},
},
engine_state.ctrlc.clone(),
),
None,
))
}

View File

@ -270,25 +270,19 @@ impl PipelineData {
match self {
PipelineData::Value(val, metadata) => match val {
Value::List { vals, .. } => Ok(PipelineIterator(PipelineData::ListStream(
ListStream {
stream: Box::new(vals.into_iter()),
ctrlc: None,
},
ListStream::from_stream(vals.into_iter(), None),
metadata,
))),
Value::Binary { val, .. } => Ok(PipelineIterator(PipelineData::ListStream(
ListStream {
stream: Box::new(val.into_iter().map(move |x| Value::int(x as i64, span))),
ctrlc: None,
},
ListStream::from_stream(
val.into_iter().map(move |x| Value::int(x as i64, span)),
None,
),
metadata,
))),
Value::Range { val, .. } => match val.into_range_iter(None) {
Ok(iter) => Ok(PipelineIterator(PipelineData::ListStream(
ListStream {
stream: Box::new(iter),
ctrlc: None,
},
ListStream::from_stream(iter, None),
metadata,
))),
Err(error) => Err(error),
@ -822,25 +816,19 @@ impl IntoIterator for PipelineData {
let span = v.span();
match v {
Value::List { vals, .. } => PipelineIterator(PipelineData::ListStream(
ListStream {
stream: Box::new(vals.into_iter()),
ctrlc: None,
},
ListStream::from_stream(vals.into_iter(), None),
metadata,
)),
Value::Range { val, .. } => match val.into_range_iter(None) {
Ok(iter) => PipelineIterator(PipelineData::ListStream(
ListStream {
stream: Box::new(iter),
ctrlc: None,
},
ListStream::from_stream(iter, None),
metadata,
)),
Err(error) => PipelineIterator(PipelineData::ListStream(
ListStream {
stream: Box::new(std::iter::once(Value::error(error, span))),
ctrlc: None,
},
ListStream::from_stream(
std::iter::once(Value::error(error, span)),
None,
),
metadata,
)),
},
@ -965,10 +953,7 @@ where
{
fn into_pipeline_data(self, ctrlc: Option<Arc<AtomicBool>>) -> PipelineData {
PipelineData::ListStream(
ListStream {
stream: Box::new(self.into_iter().map(Into::into)),
ctrlc,
},
ListStream::from_stream(self.into_iter().map(Into::into), ctrlc),
None,
)
}
@ -979,10 +964,7 @@ where
ctrlc: Option<Arc<AtomicBool>>,
) -> PipelineData {
PipelineData::ListStream(
ListStream {
stream: Box::new(self.into_iter().map(Into::into)),
ctrlc,
},
ListStream::from_stream(self.into_iter().map(Into::into), ctrlc),
metadata.into(),
)
}

View File

@ -184,6 +184,7 @@ impl Iterator for RawStream {
pub struct ListStream {
pub stream: Box<dyn Iterator<Item = Value> + Send + 'static>,
pub ctrlc: Option<Arc<AtomicBool>>,
first_guard: bool,
}
impl ListStream {
@ -209,6 +210,7 @@ impl ListStream {
ListStream {
stream: Box::new(input),
ctrlc,
first_guard: true,
}
}
}
@ -223,6 +225,17 @@ impl Iterator for ListStream {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
// We need to check `first_guard` to guarantee that it always have something to return in
// underlying stream.
//
// A realworld example is running an external commands, which have an `exit_code`
// ListStream.
// When we press ctrl-c, the external command receives the signal too, if we don't have
// `first_guard`, the `exit_code` ListStream will return Nothing, which is not expected
if self.first_guard {
self.first_guard = false;
return self.stream.next();
}
if nu_utils::ctrl_c::was_pressed(&self.ctrlc) {
None
} else {