[ffx] Handle target infos in parallel. When resolving device infos, do so in parallel. In the event that a user-mode emulator was online and we attempted to probe it, other devices would not be discovered. This was tested manually with a TAP and a SLiRP target. I terminated the SLiRP target while preserving the instance directory, then kept the TAP target online, and noticed that only the SLiRP target was displayed by `ffx target list`. This also introduces a regression test. Test: fx test ffx_list_test Fixed: 555903414 Change-Id: I4e38305c8a81d9e67a0b789be5b69ad86a6a636c Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1800139 Commit-Queue: Andrew Davies <awdavies@google.com> Fuchsia-Auto-Submit: Andrew Davies <awdavies@google.com> Reviewed-by: Steven Grady <slgrady@google.com>
diff --git a/src/developer/ffx/lib/target/src/list.rs b/src/developer/ffx/lib/target/src/list.rs index a4803db..acb2292 100644 --- a/src/developer/ffx/lib/target/src/list.rs +++ b/src/developer/ffx/lib/target/src/list.rs
@@ -116,8 +116,11 @@ ) -> Result<Vec<TargetInfo>> { let default = TargetInfoQuery::try_from(crate::get_target_specifier(ctx)?)?; - let info_futures = stream.then(|t| handle_to_info(ctx, t, connect, default.clone())); - let infos: Vec<Result<TargetInfo>> = info_futures.collect().await; + let infos: Vec<Result<TargetInfo>> = stream + .map(|t| handle_to_info(ctx, t, connect, default.clone())) + .buffered(16) + .collect() + .await; let targets = infos.into_iter().collect::<Result<Vec<_>>>()?; let targets = merge_target_addrs(targets); Ok(targets) @@ -236,6 +239,7 @@ use crate::info::{RemoteControlState, TargetState}; use addr::TargetAddr; use std::collections::HashSet; + use std::net::TcpListener; #[fuchsia::test] async fn test_serial_addresses() { @@ -495,4 +499,46 @@ let info = handle_to_info(&env.context, handle, false, query).await.unwrap(); assert_eq!(info.serial_number, Some("fastboot_serial".to_string())); } + + #[fuchsia::test] + async fn test_handles_to_infos_concurrency() { + let env = ffx_config::test_init().unwrap(); + let mut config = ffx_config::Config::from_env(&env.load()).unwrap(); + config + .set( + "target.host_pipe_ssh_timeout", + ffx_config::ConfigLevel::User, + serde_json::json!(50), + ) + .unwrap(); + + // Bind a local listener so the connection is accepted and hangs briefly (no Network Unreachable) + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + + let handle1 = discovery::TargetHandle { + node_name: Some("target1-hang".to_string()), + state: discovery::TargetState::Product { + addrs: vec![addr::TargetAddr::Net(addr)], + serial: None, + }, + manual: false, + }; + + let handle2 = discovery::TargetHandle { + node_name: Some("target2-fast".to_string()), + state: discovery::TargetState::Fastboot(discovery::FastbootTargetState { + serial_number: "2222".to_string(), + connection_state: discovery::FastbootConnectionState::Usb, + }), + manual: false, + }; + + let stream_items = vec![handle1, handle2]; + let stream = futures::stream::iter(stream_items.into_iter()); + + let targets = handles_to_infos(stream, &env.context, true).await.unwrap(); + + assert_eq!(targets.len(), 2, "Expected 2 targets"); + } }