Skip to content

Conversation

@DanTup
Copy link
Contributor

@DanTup DanTup commented Feb 6, 2018

Fixes #14184

I created a copy of the command_tests.dart devicelab test and changed it to talk --machine (including extracting the debug port from app.debugPort since there's no banner like flutter run --hot which is what the other test used). Without the change in daemon.dart this test will fail because of a crash. With the code change the test passes (the second reload is rejected).

I'm somewhat nervous about this change because breaking hot reload would be a big deal, so please review carefully. Is the change in daemon.dart reliable enough that it can't end up with a hungover future that stops future hot reloads working?

Also; should I be adding run_machine_concurrent_hot_reload to the manifest file? The doc says "The manifest.yaml file describes a subset of tests we run in the CI" but I'm not certain what that subset is and whether this test should be part of it.

As always, nitpick away; this is a learning exercise!

cc @devoncarew @yjbanov

@DanTup
Copy link
Contributor Author

DanTup commented Feb 6, 2018

Oh dear, looks like I didn't fix my lints! :( Will sort these first in the morning.

Copy link
Contributor

@devoncarew devoncarew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!

Also; should I be adding run_machine_concurrent_hot_reload to the manifest file?

Yes, I think so; that way this test will run as part of the devicelab tests.

vmServicePort = Uri.parse(json['params']['wsUri']).port;
print('service protocol connection available at port $vmServicePort');
}
else if (json != null && json['event'] == 'app.started') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: else on the previous line

Since this is a new file, you may run it through dartfmt once in order to level set the formatting (you probably don't want to do that to existing repo files however)

Copy link
Contributor Author

@DanTup DanTup Feb 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 55b68ef (note: I'm not sure if linking to changesets in response to comments is useful or spammy, it's just something I did at previous company, lmk if not useful!)

<String>['run', '--machine', '--verbose', '-d', device.deviceId, 'lib/commands.dart'],
);
final StreamController<String> stdout = new StreamController<String>.broadcast();
run.stdout
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're doing this twice in this file (and lots of places in other files); perhaps in-line a small Stream<List<int>> ==> Stream<String function?

Stream<String> transformToLines(Stream<List<int>> byteStream) {
  return byteStream.transform(UTF8.decoder).transform(const LineSplitter());
}

Copy link
Contributor Author

@DanTup DanTup Feb 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 859a279 I made a function at the top, though not sure if you want more globally?

This is one of those places I wish Dart had extension methods... stream.transformToLines() versus transformToLines(stream)!

bool isRestartSupported(bool enableHotReload, Device device) =>
enableHotReload && device.supportsHotMode;

Future<Null> inProgressHotReload;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: newline after

Perhaps make this private? _inProgressReload?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ae25a98. C# is private by default, suspect I will keep making this mistake for a while!

throw "app '$appId' not found";

return app._runInZone(this, () {
if (!fullRestart && inProgressHotReload != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to disallow any concurrent reload/restart? So, if (_inProgressReload != null) ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in b01335d. I wasn't totally sure if we wanted to affect full restarts too, but I guess it could suffer from similar issues so seems safest (as long as we don't ever go leaving this future around when it should've been cleared - I think hot reload has a timeout so hopefully this is all handled nicely).

});
if (!fullRestart)
inProgressHotReload = action.then((_) => inProgressHotReload = null);
return action;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be possible to simplify this all to:

_inProgressReload = app._runInZone(this, () {
  return app.restart(fullRestart: fullRestart, pauseAfterRestart: pauseAfterRestart);
});

return _inProgressReload.whenComplete(() {
  _inProgressReload = nu'l;
});

In which case we'd want to type _inProgressReload as Future<OperationResult>. I think the above will have the nice benefit that errors in the reload operation future will be passed back to the caller (and back over the wire).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, good catch - I think my original code would've failed to set the in-progress future back to null if the runInZone future errorred. 2a58cd8 changes to your code (similar to the earlier change, this removes the checking for only hot reloads, so the future will be set for full restarts too, but I think you intended this).

@DanTup
Copy link
Contributor Author

DanTup commented Feb 7, 2018

@devoncarew

Yes, I think so; that way this test will run as part of the devicelab tests.

I've added this in b406480, I'm not sure if these values are set optimally:

    stage: devicelab
    required_agent_capabilities: ["mac/android"]

I've been building/testing on windows/android though the test I originally copied was set to mac/android. In theory the test should work fine on all platforms, but I don't know if there's a trade-off between running it in all places versus how long the test run will take.

Copy link
Contributor

@devoncarew devoncarew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Let me know if you can merge; if not, I can do for now.

I'd watch the devicelab build page after this lands to make sure everything looks good.

@DanTup
Copy link
Contributor Author

DanTup commented Feb 7, 2018

@devoncarew I can't merge; I don't have any access here (which might not be a bad thing, at least for now! 😁). I'll keep an eye on the devicelab tests once merged 👍

@devoncarew devoncarew merged commit efb88a0 into flutter:master Feb 7, 2018
@DanTup
Copy link
Contributor Author

DanTup commented Feb 7, 2018

I'd watch the devicelab build page after this lands to make sure everything looks good.

Looks like everything (eventually) went green, thanks!

@yjbanov
Copy link
Contributor

yjbanov commented Feb 8, 2018

Just realized that I forgot to click the "Send" button 🤦‍♂️ I was going to say that when we add a brand new devicelab test we begin by marking it as flaky: true until we're sure it's consistently green. Of course, this is @DanTup's code, so the chances of build breakage was extremely low anyway 😉

@DanTup
Copy link
Contributor Author

DanTup commented Feb 8, 2018

Of course, this is @DanTup's code, so the chances of build breakage was extremely low anyway 😉

Hmm, unsure if misplaced faith or sarcasm! 😛

But I can see on the build page that two runs are red and two are yellow. I'm not entirely sure what's with the yellow ones - is is being run multiple times to determine it's flaky?

I can't currently access the logs (I don't have access to my @google account yet, and my personal Google account gives Authentication/authorization error: da***@tupp***.com is not authorized to access the dashboard when clicking the log). Can someone give me details of what was up and advise whether I should mark it flaky?

@DanTup
Copy link
Contributor Author

DanTup commented Feb 8, 2018

@mraleph I think everyone above may be on US time so I wonder if you have any opinions on this - my devicelab test run_machine_concurrent_hot_reload looks flaky. I can't currently access the logs (I don't have access to my @google account yet); I don't know the impact of this. Should I mark it as flaky in the manifest? Will that stop the build being considered broken?

@DanTup
Copy link
Contributor Author

DanTup commented Feb 8, 2018

@devoncarew @yjbanov

I got some of these logs from @mraleph; I guess something is trying to write to stdout as it's terminating (as a result of app.stop); not sure how best to handle though? The code at the end is kinda similar to the command_test one I copied - it sends q then awaits the exit code, whereas I send app.stop then await it.

So, two questions:

  1. Should I mark this flaky in the manifest?
  2. How can I make it unflaky?
NoSuchMethodError: The getter 'stdout' was called on null.
run:stdout: [{"event":"app.stop","params":{"appId":"c7777f51-f0a5-4273-a726-fe57345fa5c6"}}]
run:stdout: [   +2 ms] "flutter run" took 57,615ms.
run:stderr: Unhandled exception:
run:stderr: NoSuchMethodError: The getter 'stdout' was called on null.
run:stderr: Receiver: null
run:stderr: Tried calling: stdout
run:stderr: #0      Object.noSuchMethod (dart:core-patch/dart:core/object_patch.dart:46)
run:stderr: #1      stdout (package:flutter_tools/src/base/io.dart:176)
run:stderr: #2      StdoutLogger.writeToStdOut (package:flutter_tools/src/base/logger.dart:92)
run:stderr: #3      StdoutLogger.printStatus (package:flutter_tools/src/base/logger.dart:87)
run:stderr: #4      VerboseLogger._emit (package:flutter_tools/src/base/logger.dart:244)
run:stderr: #5      VerboseLogger.printTrace (package:flutter_tools/src/base/logger.dart:207)
run:stderr: #6      _AppRunLogger.printTrace (package:flutter_tools/src/commands/daemon.dart:859)
run:stderr: #7      printTrace (package:flutter_tools/src/globals.dart:50)
run:stderr: #8      ResidentRunner._serviceProtocolDone (package:flutter_tools/src/resident_runner.dart:652)
run:stderr: #9      _rootRunUnary (dart:async/zone.dart:1134)
run:stderr: #10     _CustomZone.runUnary (dart:async/zone.dart:1031)
run:stderr: #11     _FutureListener.handleValue (dart:async/future_impl.dart:129)
run:stderr: #12     _Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:633)
run:stderr: #13     _Future._propagateToListeners (dart:async/future_impl.dart:662)
run:stderr: #14     _Future._complete (dart:async/future_impl.dart:467)
run:stderr: #15     _SyncCompleter.complete (dart:async/future_impl.dart:51)
run:stderr: #16     ChannelManager.listen. (package:json_rpc_2/src/channel_manager.dart:58)
run:stderr: #17     _rootRun (dart:async/zone.dart:1122)
run:stderr: #18     _CustomZone.run (dart:async/zone.dart:1023)
run:stderr: #19     _CustomZone.runGuarded (dart:async/zone.dart:925)
run:stderr: #20     _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:383)
run:stderr: #21     _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:393)
run:stderr: #22     _BufferingStreamSubscription._close (dart:async/stream_impl.dart:277)
run:stderr: #23     _ForwardingStream._handleDone (dart:async/stream_pipe.dart:106)
run:stderr: #24     _ForwardingStreamSubscription._handleDone (dart:async/stream_pipe.dart:172)
run:stderr: #25     _rootRun (dart:async/zone.dart:1122)
run:stderr: #26     _CustomZone.run (dart:async/zone.dart:1023)
run:stderr: #27     _CustomZone.runGuarded (dart:async/zone.dart:925)
run:stderr: #28     _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:383)
run:stderr: #29     _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:393)
run:stderr: #30     _BufferingStreamSubscription._close (dart:async/stream_impl.dart:277)
run:stderr: #31     _ForwardingStream._handleDone (dart:async/stream_pipe.dart:106)
run:stderr: #32     _ForwardingStreamSubscription._handleDone (dart:async/stream_pipe.dart:172)
run:stderr: #33     _rootRun (dart:async/zone.dart:1122)
run:stderr: #34     _CustomZone.run (dart:async/zone.dart:1023)
run:stderr: #35     _CustomZone.runGuarded (dart:async/zone.dart:925)
run:stderr: #36     _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:383)
run:stderr: #37     _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:393)
run:stderr: #38     _BufferingStreamSubscription._close (dart:async/stream_impl.dart:277)
run:stderr: #39     _StreamController&&_SyncStreamControllerDispatch._sendDone (dart:async/stream_controller.dart:770)
run:stderr: #40     _StreamController._closeUnchecked (dart:async/stream_controller.dart:627)
run:stderr: #41     _StreamController.close (dart:async/stream_controller.dart:620)
run:stderr: #42     _rootRun (dart:async/zone.dart:1122)
run:stderr: #43     _CustomZone.run (dart:async/zone.dart:1023)
run:stderr: #44     _FutureListener.handleWhenComplete (dart:async/future_impl.dart:151)
run:stderr: #45     _Future._propagateToListeners.handleWhenCompleteCallback (dart:async/future_impl.dart:603)
run:stderr: #46     _Future._propagateToListeners (dart:async/future_impl.dart:659)
run:stderr: #47     _Future._completeWithValue (dart:async/future_impl.dart:477)
run:stderr: #48     _Future._asyncComplete. (dart:async/future_impl.dart:507)
run:stderr: #49     _rootRun (dart:async/zone.dart:1126)
run:stderr: #50     _CustomZone.run (dart:async/zone.dart:1023)
run:stderr: #51     _CustomZone.bindCallback. (dart:async/zone.dart:949)
run:stderr: #52     _microtaskLoop (dart:async/schedule_microtask.dart:41)
run:stderr: #53     _startMicrotaskLoop (dart:async/schedule_microtask.dart:50)
run:stderr: #54     _runPendingImmediateCallback (dart:isolate-patch/dart:isolate/isolate_patch.dart:112)
run:stderr: #55     _RawReceivePortImpl._handleMessage (dart:isolate-patch/dart:isolate/isolate_patch.dart:165)
exitcode: 255
Task failed: Received unexpected exit code 255 from run process.

@devoncarew
Copy link
Contributor

Looks like it's trying to print 'Service protocol connection closed.'. This call may be happening after we've torn down some of the app context / logging forwarding machinery. We may need to make this more robust in terms of writes after the app finishes, or choose to send the writes up to the higher-level handlers (would would probably just print to stdout).

But it looks like you're tickling an existing issue - this isn't new with your PR.

@devoncarew
Copy link
Contributor

@tvolkert, @jcollins-g, is it expected that context[Stdio] can be null, or should that be updated to always return a value?

https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/base/io.dart#L176

@tvolkert
Copy link
Contributor

tvolkert commented Feb 8, 2018

context[Stdio] shouldn't be null:

context.putIfAbsent(Stdio, () => const Stdio());

@DanTup
Copy link
Contributor Author

DanTup commented Feb 12, 2018

I've raised a specific issue to investigate/fix this flakiness (#14636) since this PR is already merged and it could easily get lost.

'-d',
device.deviceId,
'lib/commands.dart'
],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to support preview-dart-2 mode?

@aam
Copy link
Member

aam commented Feb 12, 2018

Also; should I be adding run_machine_concurrent_hot_reload to the manifest file?

Yes, I think so; that way this test will run as part of the devicelab tests.

Not sure if this changes are for preview-dart-2, but if they are the tests should run together with other preview-dart-2 hot reload tests.

@DanTup
Copy link
Contributor Author

DanTup commented Feb 12, 2018

No, this one is just for a crash when concurrent reload requests are sent (it existed well before preview-dart-2).

I've since been told about reload issues with preview-dart-2 via the daemon though, so suspect there will be additional daemon tests on the way.

DaveShuckerow pushed a commit to DaveShuckerow/flutter that referenced this pull request May 14, 2018
flutter#14494)

* Reject requests for hot reload if a hot reload is already in progress.

Fixes flutter#14184

* Implement TODO, verifying further hot reloads complete sucessfully.

* Fix year on new file.

* Add missing type annotations to fix lints

* Add run_machine_concurrent_hot_reload to manifest for CI

* Reformat document

... but undo things that cause lints (like single-line ifs)

* Extract std stream transformations

* Make inProgressHotReload private

* Disallow all types of reload while hot reload in progress

* Simplify code handling in-progress hot reloads
@DanTup DanTup deleted the reject-concurrent-hot-reloads branch August 22, 2018 16:14
@DanTup DanTup restored the reject-concurrent-hot-reloads branch August 22, 2018 16:15
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 10, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash if request for hot reload is sent while a reload is already in progress

6 participants