FTP connections preserve passwords and filenames, with CI checking every change #1
+6
-2
@@ -100,8 +100,12 @@ fn with_control<T>(
|
||||
/// idle session is replaced and logged in again. Nothing inside a transfer or
|
||||
/// mutation retries, because its missing reply may hide a completed change.
|
||||
fn ready() -> Result<(), ErrorCode> {
|
||||
if with_control(|control| control.expect("NOOP")).is_ok() {
|
||||
return Ok(());
|
||||
if !CONTROL.with_borrow(Option::is_none) {
|
||||
|
|
||||
match with_control(|control| control.expect("NOOP")) {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(ErrorCode::Unavailable) => {}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
let session = SESSION
|
||||
.with_borrow(Clone::clone)
|
||||
|
||||
@@ -58,3 +58,20 @@ async fn recovery_before_a_mutation_never_replays_a_lost_mutation_reply()
|
||||
assert_eq!(summary.mkdir_commands, 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cancelled_preflight_never_reconnects_or_executes_the_mutation()
|
||||
-> TestResult {
|
||||
let fixture = test_host::install(test_host::Scenario::CancelledPreflight);
|
||||
guest(Ftp::open("ftp".into(), config()))?;
|
||||
assert!(matches!(
|
||||
Ftp::mkdir(Locator::Path("/cancelled".into())),
|
||||
Err(ErrorCode::Cancelled)
|
||||
));
|
||||
Ftp::close();
|
||||
let summary = fixture.summary();
|
||||
assert_eq!(summary.control_connections, 1);
|
||||
assert_eq!(summary.listings, 0);
|
||||
assert_eq!(summary.mkdir_commands, 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ const LISTING: &[u8] = b"type=file;size=3; report.md \r\n";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Scenario {
|
||||
CancelledPreflight,
|
||||
ExpiredListing,
|
||||
LostMutationReply,
|
||||
}
|
||||
@@ -142,6 +143,9 @@ impl Tcp {
|
||||
state.listings += 1;
|
||||
"150 Listing\r\n226 Complete\r\n"
|
||||
}
|
||||
"NOOP" if matches!(state.scenario, Scenario::CancelledPreflight) => {
|
||||
return Err(HostError::Cancelled);
|
||||
}
|
||||
"NOOP"
|
||||
if index == 0
|
||||
&& (matches!(state.scenario, Scenario::LostMutationReply)
|
||||
@@ -157,6 +161,13 @@ impl Tcp {
|
||||
state.mkdir_commands += 1;
|
||||
return Ok(bytes.len() as u32);
|
||||
}
|
||||
"MKD /cancelled"
|
||||
if index == 1
|
||||
&& matches!(state.scenario, Scenario::CancelledPreflight) =>
|
||||
{
|
||||
state.mkdir_commands += 1;
|
||||
return Err(HostError::Unavailable);
|
||||
}
|
||||
_ => return Err(HostError::InvalidInput),
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user
Propagate
Cancelled(and other non-recoverable preflight errors) instead of reconnecting: a cancelledNOOPcurrently falls through tosession.connect()and then executes the requested operation, which can perform a mutation after the host cancelled the invocation. Retry only the disconnect error(s) that are safe to recover from.