Add and use new Signals struct (#13314)

# Description
This PR introduces a new `Signals` struct to replace our adhoc passing
around of `ctrlc: Option<Arc<AtomicBool>>`. Doing so has a few benefits:
- We can better enforce when/where resetting or triggering an interrupt
is allowed.
- Consolidates `nu_utils::ctrl_c::was_pressed` and other ad-hoc
re-implementations into a single place: `Signals::check`.
- This allows us to add other types of signals later if we want. E.g.,
exiting or suspension.
- Similarly, we can more easily change the underlying implementation if
we need to in the future.
- Places that used to have a `ctrlc` of `None` now use
`Signals::empty()`, so we can double check these usages for correctness
in the future.
This commit is contained in:
Ian Manske
2024-07-07 22:29:01 +00:00
committed by GitHub
parent c6b6b1b7a8
commit 399a7c8836
246 changed files with 1332 additions and 1234 deletions

View File

@ -5,16 +5,12 @@ use base64::{
Engine,
};
use nu_engine::command_prelude::*;
use nu_protocol::ByteStream;
use nu_protocol::{ByteStream, Signals};
use std::{
collections::HashMap,
path::PathBuf,
str::FromStr,
sync::{
atomic::AtomicBool,
mpsc::{self, RecvTimeoutError},
Arc,
},
sync::mpsc::{self, RecvTimeoutError},
time::Duration,
};
use ureq::{Error, ErrorKind, Request, Response};
@ -129,7 +125,7 @@ pub fn response_to_buffer(
let reader = response.into_reader();
PipelineData::ByteStream(
ByteStream::read(reader, span, engine_state.ctrlc.clone(), response_type)
ByteStream::read(reader, span, engine_state.signals().clone(), response_type)
.with_known_size(buffer_size),
None,
)
@ -192,13 +188,14 @@ pub fn send_request(
request: Request,
http_body: HttpBody,
content_type: Option<String>,
ctrl_c: Option<Arc<AtomicBool>>,
span: Span,
signals: &Signals,
) -> Result<Response, ShellErrorOrRequestError> {
let request_url = request.url().to_string();
match http_body {
HttpBody::None => {
send_cancellable_request(&request_url, Box::new(|| request.call()), ctrl_c)
send_cancellable_request(&request_url, Box::new(|| request.call()), span, signals)
}
HttpBody::ByteStream(byte_stream) => {
let req = if let Some(content_type) = content_type {
@ -207,7 +204,7 @@ pub fn send_request(
request
};
send_cancellable_request_bytes(&request_url, req, byte_stream, ctrl_c)
send_cancellable_request_bytes(&request_url, req, byte_stream, span, signals)
}
HttpBody::Value(body) => {
let (body_type, req) = match content_type {
@ -224,20 +221,32 @@ pub fn send_request(
Value::Binary { val, .. } => send_cancellable_request(
&request_url,
Box::new(move || req.send_bytes(&val)),
ctrl_c,
span,
signals,
),
Value::String { .. } if body_type == BodyType::Json => {
let data = value_to_json_value(&body)?;
send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c)
send_cancellable_request(
&request_url,
Box::new(|| req.send_json(data)),
span,
signals,
)
}
Value::String { val, .. } => send_cancellable_request(
&request_url,
Box::new(move || req.send_string(&val)),
ctrl_c,
span,
signals,
),
Value::Record { .. } if body_type == BodyType::Json => {
let data = value_to_json_value(&body)?;
send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c)
send_cancellable_request(
&request_url,
Box::new(|| req.send_json(data)),
span,
signals,
)
}
Value::Record { val, .. } if body_type == BodyType::Form => {
let mut data: Vec<(String, String)> = Vec::with_capacity(val.len());
@ -254,7 +263,7 @@ pub fn send_request(
.collect::<Vec<(&str, &str)>>();
req.send_form(&data)
};
send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c)
send_cancellable_request(&request_url, Box::new(request_fn), span, signals)
}
Value::List { vals, .. } if body_type == BodyType::Form => {
if vals.len() % 2 != 0 {
@ -276,11 +285,16 @@ pub fn send_request(
.collect::<Vec<(&str, &str)>>();
req.send_form(&data)
};
send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c)
send_cancellable_request(&request_url, Box::new(request_fn), span, signals)
}
Value::List { .. } if body_type == BodyType::Json => {
let data = value_to_json_value(&body)?;
send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c)
send_cancellable_request(
&request_url,
Box::new(|| req.send_json(data)),
span,
signals,
)
}
_ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
msg: "unsupported body input".into(),
@ -295,7 +309,8 @@ pub fn send_request(
fn send_cancellable_request(
request_url: &str,
request_fn: Box<dyn FnOnce() -> Result<Response, Error> + Sync + Send>,
ctrl_c: Option<Arc<AtomicBool>>,
span: Span,
signals: &Signals,
) -> Result<Response, ShellErrorOrRequestError> {
let (tx, rx) = mpsc::channel::<Result<Response, Error>>();
@ -310,12 +325,7 @@ fn send_cancellable_request(
// ...and poll the channel for responses
loop {
if nu_utils::ctrl_c::was_pressed(&ctrl_c) {
// Return early and give up on the background thread. The connection will either time out or be disconnected
return Err(ShellErrorOrRequestError::ShellError(
ShellError::InterruptedByUser { span: None },
));
}
signals.check(span)?;
// 100ms wait time chosen arbitrarily
match rx.recv_timeout(Duration::from_millis(100)) {
@ -336,7 +346,8 @@ fn send_cancellable_request_bytes(
request_url: &str,
request: Request,
byte_stream: ByteStream,
ctrl_c: Option<Arc<AtomicBool>>,
span: Span,
signals: &Signals,
) -> Result<Response, ShellErrorOrRequestError> {
let (tx, rx) = mpsc::channel::<Result<Response, ShellErrorOrRequestError>>();
let request_url_string = request_url.to_string();
@ -369,12 +380,7 @@ fn send_cancellable_request_bytes(
// ...and poll the channel for responses
loop {
if nu_utils::ctrl_c::was_pressed(&ctrl_c) {
// Return early and give up on the background thread. The connection will either time out or be disconnected
return Err(ShellErrorOrRequestError::ShellError(
ShellError::InterruptedByUser { span: None },
));
}
signals.check(span)?;
// 100ms wait time chosen arbitrarily
match rx.recv_timeout(Duration::from_millis(100)) {

View File

@ -203,7 +203,6 @@ fn helper(
args: Arguments,
) -> Result<PipelineData, ShellError> {
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
@ -214,7 +213,13 @@ fn helper(
request = request_add_authorization_header(args.user, args.password, request);
request = request_add_custom_headers(args.headers, request)?;
let response = send_request(request.clone(), args.data, args.content_type, ctrl_c);
let response = send_request(
request.clone(),
args.data,
args.content_type,
call.head,
engine_state.signals(),
);
let request_flags = RequestFlags {
raw: args.raw,

View File

@ -171,7 +171,6 @@ fn helper(
args: Arguments,
) -> Result<PipelineData, ShellError> {
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
@ -182,7 +181,13 @@ fn helper(
request = request_add_authorization_header(args.user, args.password, request);
request = request_add_custom_headers(args.headers, request)?;
let response = send_request(request.clone(), HttpBody::None, None, ctrl_c);
let response = send_request(
request.clone(),
HttpBody::None,
None,
call.head,
engine_state.signals(),
);
let request_flags = RequestFlags {
raw: args.raw,

View File

@ -1,13 +1,11 @@
use super::client::HttpBody;
use crate::network::http::client::{
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
request_add_authorization_header, request_add_custom_headers, request_handle_response_headers,
request_set_timeout, send_request,
};
use nu_engine::command_prelude::*;
use std::sync::{atomic::AtomicBool, Arc};
use super::client::HttpBody;
use nu_protocol::Signals;
#[derive(Clone)]
pub struct SubCommand;
@ -133,9 +131,8 @@ fn run_head(
timeout: call.get_flag(engine_state, stack, "max-time")?,
redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
};
let ctrl_c = engine_state.ctrlc.clone();
helper(engine_state, stack, call, args, ctrl_c)
helper(engine_state, stack, call, args, engine_state.signals())
}
// Helper function that actually goes to retrieve the resource from the url given
@ -145,7 +142,7 @@ fn helper(
stack: &mut Stack,
call: &Call,
args: Arguments,
ctrlc: Option<Arc<AtomicBool>>,
signals: &Signals,
) -> Result<PipelineData, ShellError> {
let span = args.url.span();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
@ -158,7 +155,7 @@ fn helper(
request = request_add_authorization_header(args.user, args.password, request);
request = request_add_custom_headers(args.headers, request)?;
let response = send_request(request, HttpBody::None, None, ctrlc);
let response = send_request(request, HttpBody::None, None, call.head, signals);
check_response_redirection(redirect_mode, span, &response)?;
request_handle_response_headers(span, response)
}

View File

@ -151,7 +151,6 @@ fn helper(
args: Arguments,
) -> Result<PipelineData, ShellError> {
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let client = http_client(args.insecure, RedirectMode::Follow, engine_state, stack)?;
@ -161,7 +160,13 @@ fn helper(
request = request_add_authorization_header(args.user, args.password, request);
request = request_add_custom_headers(args.headers, request)?;
let response = send_request(request.clone(), HttpBody::None, None, ctrl_c);
let response = send_request(
request.clone(),
HttpBody::None,
None,
call.head,
engine_state.signals(),
);
// http options' response always showed in header, so we set full to true.
// And `raw` is useless too because options method doesn't return body, here we set to true

View File

@ -205,7 +205,6 @@ fn helper(
args: Arguments,
) -> Result<PipelineData, ShellError> {
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
@ -216,7 +215,13 @@ fn helper(
request = request_add_authorization_header(args.user, args.password, request);
request = request_add_custom_headers(args.headers, request)?;
let response = send_request(request.clone(), args.data, args.content_type, ctrl_c);
let response = send_request(
request.clone(),
args.data,
args.content_type,
call.head,
engine_state.signals(),
);
let request_flags = RequestFlags {
raw: args.raw,

View File

@ -203,7 +203,6 @@ fn helper(
args: Arguments,
) -> Result<PipelineData, ShellError> {
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
@ -214,7 +213,13 @@ fn helper(
request = request_add_authorization_header(args.user, args.password, request);
request = request_add_custom_headers(args.headers, request)?;
let response = send_request(request.clone(), args.data, args.content_type, ctrl_c);
let response = send_request(
request.clone(),
args.data,
args.content_type,
call.head,
engine_state.signals(),
);
let request_flags = RequestFlags {
raw: args.raw,

View File

@ -204,7 +204,6 @@ fn helper(
args: Arguments,
) -> Result<PipelineData, ShellError> {
let span = args.url.span();
let ctrl_c = engine_state.ctrlc.clone();
let (requested_url, _) = http_parse_url(call, span, args.url)?;
let redirect_mode = http_parse_redirect_mode(args.redirect)?;
@ -215,7 +214,13 @@ fn helper(
request = request_add_authorization_header(args.user, args.password, request);
request = request_add_custom_headers(args.headers, request)?;
let response = send_request(request.clone(), args.data, args.content_type, ctrl_c);
let response = send_request(
request.clone(),
args.data,
args.content_type,
call.head,
engine_state.signals(),
);
let request_flags = RequestFlags {
raw: args.raw,

View File

@ -48,7 +48,7 @@ impl Command for SubCommand {
) -> Result<PipelineData, ShellError> {
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths);
operate(action, args, input, call.head, engine_state.ctrlc.clone())
operate(action, args, input, call.head, engine_state.signals())
}
fn examples(&self) -> Vec<Example> {

View File

@ -50,15 +50,9 @@ impl Command for SubCommand {
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths);
if call.has_flag(engine_state, stack, "all")? {
operate(
action_all,
args,
input,
call.head,
engine_state.ctrlc.clone(),
)
operate(action_all, args, input, call.head, engine_state.signals())
} else {
operate(action, args, input, call.head, engine_state.ctrlc.clone())
operate(action, args, input, call.head, engine_state.signals())
}
}