Repository navigation
[flutter_tools] iOS fallback discovery protocol - #49735
Conversation
| try { | ||
| // TODO(jonahwilliams): determine if this succeeds even if there is nothing | ||
| // listening. | ||
| final int hostPort = await _portForwarder.forward(assumedDevicePort, hostPort: hostVmservicePort); |
There was a problem hiding this comment.
If this still succeeds when there is no vmservice, I'll update it to make an HTTP get to grab version info or something safe
There was a problem hiding this comment.
Actually this definitely won't throw. Instead I'll need to poll w/ exponential backoff for a while. Need to run some experiments to determine the best numbers
|
Adding @jmagman @christopherfujino for initial thoughts |
|
Waiting on an engine roll to start working on this again |
|
engine roll has landed and this is working locally |
|
I spoke too soon, the flag is not yet in flutter/flutter |
| // Step 2.5: Generate a potential open port using the provided argument, | ||
| // or randomly with the package name as a seed. | ||
| final int assumedObservatoryPort = debuggingOptions?.deviceVmServicePort | ||
| ?? math.Random(packageId.hashCode).nextInt(1000) + 6000; |
There was a problem hiding this comment.
We should probably pick a random port in the standard ephemeral port range 49152 to 65535 (2^15+2^14 to 2^16−1)
| final HttpClientResponse response = await request.close(); | ||
| if (response.statusCode == HttpStatus.ok) { | ||
| final String responseBody = await response.transform(utf8.decoder).join(''); | ||
| if (responseBody.contains('Dart')) { |
There was a problem hiding this comment.
As discussed offline, this is probably a bit fragile, and we should instead connect to the vmservice to check for an isolate with the right name or root library uri.
| // response page containing a specific string. This logic needs to | ||
| // be tightened up to ensure we don't accidentally try to connect | ||
| // to some arbitrary server. | ||
| final HttpClientRequest request = await _httpClient.getUrl(assumedUri); |
There was a problem hiding this comment.
The body of this loop could be pulled out to a helper function.
| attempts += 1; | ||
| } | ||
| } on Exception { | ||
| _logger.printTrace('Failed to connect directly, falling back to mDNS'); |
There was a problem hiding this comment.
It might be helpful to print the exception object here, too.
| } on Exception { | ||
| _logger.printTrace('Failed to connect directly, falling back to mDNS'); | ||
| } | ||
| UsageEvent('ios-mdns', 'precheck-failure').send(); |
There was a problem hiding this comment.
Consider creating a new event type in events.dart. It might also be interesting to record the port number that we tried for here in case there's one that fails a lot.
There was a problem hiding this comment.
I created ios-handshake as an event type. I don't think it would be worth it to record the port, since we're effectively choosing a random number, we'd need a tremendous amount of data to get any insight into the failures.
| return result; | ||
| } | ||
| } on Exception { | ||
| _logger.printTrace('Failed to connect with mDNS, falling back to log scanning'); |
…tter into ios_Fallback_discovery
|
This requires flutter-team-archive/engine@7e1d144 to roll into the framework to land - tested locally to verify that the fallback works as expected when multiple apps are run with the same specified port |
|
That commit is included in #50177 |
| } | ||
| } on Exception { | ||
| // No action, we might have failed to connect. | ||
| // The error message will be irrelevant. |
There was a problem hiding this comment.
You might record one of the exception objects to print with the failure trace message.
| hostVmservicePort: hostVmservicePort, | ||
| ); | ||
| if (result != null) { | ||
| UsageEvent('ios-mdns', 'success').send(); |
There was a problem hiding this comment.
It'll make it a little easier to view this data in analytics if first argument for all of the events is the same. So maybe make this:
UsageEvent('ios-handshake', 'mdns-success').send();
and similarly below.
There was a problem hiding this comment.
Are you worried about changing the existing event names?
There was a problem hiding this comment.
No. We're the only ones looking at those events in GA, and they'll kind of mean something different now anyway since the handshake procedure is different, so may as well make the naming uniform.
There was a problem hiding this comment.
Done. I kept the first set of events as just success and failure, since I wasn't sure what else to prefix them with
| .thenAnswer((Invocation invocation) async => 1); | ||
| }); | ||
|
|
||
| testUsingContext('Selects assumed port if VM service connection is successful', () async { |
There was a problem hiding this comment.
Sorry if I already asked this, but can these be testWithoutContext() ?
There was a problem hiding this comment.
I think you mentioned it and I completely forgot
There was a problem hiding this comment.
Ahh, no it was the Usage/event classes need to be refactored. There doesn't seem to be a way to reference any of the helpers without going through the context
There was a problem hiding this comment.
Ah, okay. I'll take on the Usage refactor when I'm back in the office.
|
Jonah, do you imagine this will require a change in google3? or is it expected to work as is? @jamesderlin FYI. |
|
I expect this to work as is |
Description
A protocol for discovery of a vmservice on an attached iOS device with multiple fallbacks.
On versions of iOS 13 and greater, libimobiledevice can no longer listen to logs directly. The only way to discover an active observatory is through the mDNS protocol. However, there are a number of circumstances where this breaks down, such as when the device is connected to certain wifi networks or with certain hotspot connections enabled.
Another approach to discover a vmservice is to attempt to assign a specific port and then attempt to connect. This may fail if the port is not available. This port value should be either random, or otherwise generated with application specific input. This reduces the chance of accidentally connecting to another running flutter application.
Finally, if neither of the above approaches works, we can still attempt to parse logs.
To improve the overall resilience of the process, this class combines the three discovery strategies. First it assigns a port and attempts to connect. Then if this fails it falls back to mDNS, then finally attempting to scan logs.
Fixes #46724
WIP because I still need to test the port selection logic and wait for the flag/engine to roll. Also I'm not yet sure if the port forwarder will throw, or if I need to ping the observatory