@@ -235,7 +235,6 @@ class FlutterDevice {
235235 PrintStructuredErrorLogMethod ? printStructuredErrorLogMethod,
236236 required DebuggingOptions debuggingOptions,
237237 int ? hostVmServicePort,
238- required bool allowExistingDdsInstance,
239238 }) {
240239 final completer = Completer <void >();
241240 late StreamSubscription <void > subscription;
@@ -256,41 +255,99 @@ class FlutterDevice {
256255 }
257256 }
258257
259- // First check if the VM service is actually listening on vmServiceUri as
260- // this may not be the case when scraping logcat for URIs. If this URI is
261- // from an old application instance, we shouldn't try and start DDS.
262- try {
263- service = await connectToVmService (vmServiceUri! , logger: globals.logger);
264- await service.dispose ();
265- } on Exception catch (exception) {
266- globals.printTrace ('Fail to connect to service protocol: $vmServiceUri : $exception ' );
267- if (! completer.isCompleted && ! _isListeningForVmServiceUri! ) {
268- completer.completeError ('failed to connect to $vmServiceUri $exception ' );
258+ const kMaxAttempts = 3 ;
259+ for (var attempts = 1 ; attempts <= kMaxAttempts; ++ attempts) {
260+ void handleVmServiceCheckException (Exception e) {
261+ globals.printTrace ('Fail to connect to service protocol: $vmServiceUri : $e ' );
262+ if (! completer.isCompleted && ! _isListeningForVmServiceUri! ) {
263+ completer.completeError ('failed to connect to $vmServiceUri $e ' );
264+ }
265+ }
266+
267+ // First check if the VM service is actually listening on vmServiceUri as
268+ // this may not be the case when scraping logcat for URIs. If this URI is
269+ // from an old application instance, we shouldn't try and start DDS.
270+ try {
271+ service = await connectToVmService (vmServiceUri! , logger: globals.logger);
272+ await service.dispose ();
273+ } on vm_service.RPCError catch (e, st) {
274+ if (! e.isConnectionDisposedException) {
275+ handleVmServiceCheckException (e);
276+ return ;
277+ }
278+ // It's possible (but unlikely) that two DDS instances can try and start at the same
279+ // time (e.g., a "flutter run" is initiated while an existing "flutter attach" is
280+ // waiting for a target to attach to). This can lead to the initial VM service connection
281+ // failing for one of the processes when the VM service disconnects it after the other
282+ // instance successfully invoked the "_yieldControlToDDS" RPC.
283+ //
284+ // To handle this, we retry connecting to the VM service, which should successfully
285+ // be redirected to the DDS instance.
286+ //
287+ // See https://github.com/flutter/flutter/issues/169265 for details.
288+ if (attempts == kMaxAttempts) {
289+ globals.printTrace (
290+ 'Failed to make initial connection to VM Service (attempt $attempts of $kMaxAttempts ).' ,
291+ );
292+ handleError (e, st);
293+ return ;
294+ }
295+ // Exponential backoff.
296+ final int backoffPeriod = (1 << (attempts - 1 )) * 100 ;
297+ globals.printTrace (
298+ 'Failed to make initial connection to VM Service (attempt $attempts of $kMaxAttempts ). '
299+ 'Retrying in ${backoffPeriod }ms...' ,
300+ );
301+ await Future <void >.delayed (Duration (milliseconds: backoffPeriod));
302+ } on Exception catch (e) {
303+ handleVmServiceCheckException (e);
304+ return ;
269305 }
270- return ;
271306 }
272307
273- // This first try block is meant to catch errors that occur during DDS startup
274- // (e.g., failure to bind to a port, failure to connect to the VM service,
275- // attaching to a VM service with existing clients, etc.).
276- try {
277- await device! .dds.startDartDevelopmentServiceFromDebuggingOptions (
278- vmServiceUri,
279- debuggingOptions: debuggingOptions,
280- );
281- } on DartDevelopmentServiceException catch (e, st) {
282- if (! allowExistingDdsInstance ||
283- (e.errorCode != DartDevelopmentServiceException .existingDdsInstanceError)) {
308+ for (var attempts = 1 ; attempts <= kMaxAttempts; ++ attempts) {
309+ // This try block is meant to catch errors that occur during DDS startup
310+ // (e.g., failure to bind to a port, failure to connect to the VM service,
311+ // attaching to a VM service with existing clients, etc.).
312+ try {
313+ await device! .dds.startDartDevelopmentServiceFromDebuggingOptions (
314+ vmServiceUri! ,
315+ debuggingOptions: debuggingOptions,
316+ );
317+ break ;
318+ } on DartDevelopmentServiceException catch (e, st) {
319+ if (e.errorCode == DartDevelopmentServiceException .existingDdsInstanceError) {
320+ existingDds = true ;
321+ break ;
322+ }
323+ // It's possible (but unlikely) that two DDS instances can try and start at the same
324+ // time (e.g., a "flutter run" is initiated while an existing "flutter attach" is
325+ // waiting for a target to attach to). This leads to DDS failing to initialize for
326+ // one of the processes when the VM service disconnects it after the other instance
327+ // successfully invoked the "_yieldControlToDDS" RPC.
328+ //
329+ // To handle this, we retry to start DDS after a short delay, which should result in
330+ // an existingDdsInstanceError if the failure to start was due to a startup race.
331+ //
332+ // See https://github.com/flutter/flutter/issues/169265 for details.
333+ if (attempts == kMaxAttempts) {
334+ globals.printTrace ('Failed to start DDS (attempt $attempts of $kMaxAttempts ).' );
335+ handleError (e, st);
336+ return ;
337+ }
338+ // Exponential backoff.
339+ final int backoffPeriod = (1 << (attempts - 1 )) * 100 ;
340+ globals.printTrace (
341+ 'Failed to start DDS (attempt $attempts of $kMaxAttempts ). '
342+ 'Retrying in ${backoffPeriod }ms...' ,
343+ );
344+ await Future <void >.delayed (Duration (milliseconds: backoffPeriod));
345+ } on ToolExit {
346+ rethrow ;
347+ } on Exception catch (e, st) {
284348 handleError (e, st);
285349 return ;
286- } else {
287- existingDds = true ;
288350 }
289- } on ToolExit {
290- rethrow ;
291- } on Exception catch (e, st) {
292- handleError (e, st);
293- return ;
294351 }
295352 }
296353 // This second try block handles cases where the VM service connection goes down
@@ -1144,7 +1201,6 @@ abstract class ResidentRunner extends ResidentHandlers {
11441201 Future <int ?> attach ({
11451202 Completer <DebugConnectionInfo >? connectionInfoCompleter,
11461203 Completer <void >? appStartedCompleter,
1147- bool allowExistingDdsInstance = false ,
11481204 bool needsFullRestart = true ,
11491205 });
11501206
@@ -1227,7 +1283,6 @@ abstract class ResidentRunner extends ResidentHandlers {
12271283 await residentDevtoolsHandler! .shutdown ();
12281284 await stopEchoingDeviceLog ();
12291285 await preExit ();
1230- shutdownDartDevelopmentService ();
12311286 appFinished ();
12321287 }
12331288
@@ -1298,7 +1353,6 @@ abstract class ResidentRunner extends ResidentHandlers {
12981353 ReloadSources ? reloadSources,
12991354 Restart ? restart,
13001355 CompileExpression ? compileExpression,
1301- required bool allowExistingDdsInstance,
13021356 }) async {
13031357 if (! debuggingOptions.debuggingEnabled) {
13041358 throw Exception ('The service protocol is not enabled.' );
@@ -1311,7 +1365,6 @@ abstract class ResidentRunner extends ResidentHandlers {
13111365 reloadSources: reloadSources,
13121366 restart: restart,
13131367 compileExpression: compileExpression,
1314- allowExistingDdsInstance: allowExistingDdsInstance,
13151368 hostVmServicePort: debuggingOptions.hostVmServicePort,
13161369 printStructuredErrorLogMethod: printStructuredErrorLog,
13171370 );
0 commit comments