Skip to content
This repository was archived by the owner on Dec 19, 2018. It is now read-only.

Change SelfHostDeployer to use dynamic ports by default #1383

Merged
merged 5 commits into from
Apr 17, 2018

Conversation

mikeharder
Copy link
Contributor

@mikeharder mikeharder force-pushed the mikeharder/dynamic-port branch from a4e2642 to 4a2eb61 Compare April 16, 2018 16:47
Copy link
Contributor

@analogrelay analogrelay left a comment

Choose a reason for hiding this comment

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

Should make things significantly better. We can always do more tracking if conflicts keep occurring.

// once and never released. Binding to dynamic port "0" on "localhost" (both IPv4 and IPv6) is not
// supported, so the port is only bound on "127.0.0.1" (IPv4). If a test explicitly requires IPv6,
// it should provide a hint URL with "localhost" (IPv4 and IPv6) or "[::1]" (IPv6-only).
return new UriBuilder("http", "127.0.0.1", 0).Uri;
Copy link
Member

Choose a reason for hiding this comment

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

Why default to v4 rather than v6?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the test doesn't care, I assume IPv4 is safer since some environments don't support IPv6 (parts of Azure, maybe Travis/AppVeyor).

{
if (string.IsNullOrEmpty(hint))
{
return BuildTestUri();
if (statusMessagesEnabled)
Copy link
Member

Choose a reason for hiding this comment

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

How is this going to work with HttpSys? It can't bind to port 0. Does it always have to set statusMessagesEnabled=false or provide a hint?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, I assumed SelfHostDeployer was only used for Kestrel, but I now see it's also used for WebListener:

https://github.com/aspnet/Hosting/blob/dev/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/ApplicationDeployerFactory.cs#L38

I see the following repos use ServerType.WebListener, I will run their tests with this change and see what happens:

  • MusicStore
  • ServerTests
  • StaticFiles
  • WebSockets

The best fix will likely be to pass DeploymentParameters.ServerType to BuildTestUri(), and use the less reliable GetNextPort() if ServerType == ServerType.WebListener.

// If status messages are disabled, there is no status message from which to scrape the assigned port,
// so the less reliable GetNextPort() must be used. The port is bound on "localhost" (both IPv4 and IPv6),
// since this is supported when using a specific (non-zero) port.
return new UriBuilder("http", "localhost", GetNextPort()).Uri;
Copy link
Contributor

Choose a reason for hiding this comment

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

When we fall back to GetNextPort(), is it possible to add retry logic for address in use exceptions. If we can do that, it should eliminate all sources of flakiness.

Copy link
Contributor

Choose a reason for hiding this comment

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

The problem is that logic lives elsewhere and involves more complicated scraping of the server process stdio. I think this is sufficient for a first pass

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding retries is possible but difficult. The AddressInUse exception doesn't come from GetNextPort(), it comes from the application started in an external process. So the retry logic would need to be here:

https://github.com/aspnet/Hosting/blob/dev/src/Microsoft.AspNetCore.Server.IntegrationTesting/Deployers/SelfHostDeployer.cs#L47

I think we'd need to try to start the app, and if it fails to start we'd need to scrape the output for AddressInUseException, and then retry on a different port. We'd need to be careful to not retry if it failed for any other reason. We'd also need to be careful to only retry if BuildTestUri() used GetNextPort(), since there's no point retrying if the test is using a hardcoded port.

Overall, this seems risky and error-prone, and it might cause more problems than it solves. I think we should wait and see how many flaky tests remain after changing the default to use a dynamic port.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea it's just another enhancement we could add. Doesn't have to be in this PR which is why I approved it already.

Copy link
Contributor

@JunTaoLuo JunTaoLuo left a comment

Choose a reason for hiding this comment

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

This should help plenty with our flakiness.

Copy link
Contributor

@ryanbrandenburg ryanbrandenburg left a comment

Choose a reason for hiding this comment

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

One question that I assume you have strong reasons for anyway. :shipit: from me, but you definitely want to also get someone super familiar with the nitty gritty parts of this to approve too.

}

public static Uri BuildTestUri(string hint)
{
return BuildTestUri(hint, statusMessagesEnabled: false);
Copy link
Contributor

Choose a reason for hiding this comment

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

I would have thought we would default to statusMessagesEnabled: true considering that you say below that it's the more reliable method?

Copy link
Contributor Author

@mikeharder mikeharder Apr 16, 2018

Choose a reason for hiding this comment

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

The TestUriHelper class and existing BuildTestUri() methods are public, so they can be called directly by test classes in other assemblies, not just by SelfHostDeployer. If the methods are called directly, we have no way of knowing if status messages are enabled or not, so we can't rely on them.

I made the new overload of BuildTestUri() internal to prevent other classes from calling it directly, since it should only be used by SelfHostDeployer. I will also review all existing usage of BuildTestUri(), BuildTestUri(string hint), and GetNextPort() and see if we can remove them or change them to internal.

if (statusMessagesEnabled)
{
// Most functional tests use this codepath and should directly bind to dynamic port "0" and scrape
// the assigned port from the status message, which should be 100% reliable since the port is bound
Copy link
Member

Choose a reason for hiding this comment

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

Does SelfHostDeployer already scrape the assigned port from the status message? Does this mean that any test that uses SelfHostDeployer w/ Kestrel w/o specifying a port should just work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

SelfHostDeployer already scrapes the assigned port when status messages are enabled (which is the default for most tests). So yes, once this change is merged and repos are updated to use this version, most tests should get more reliable with no other changes.

@mikeharder mikeharder merged commit 0cc416f into dev Apr 17, 2018
@mikeharder mikeharder deleted the mikeharder/dynamic-port branch April 17, 2018 21:01
pranavkm pushed a commit that referenced this pull request Sep 19, 2018
* Should significantly reduce flaky test failures due to AddressInUse exceptions
* Addresses #1296
pranavkm pushed a commit that referenced this pull request Sep 19, 2018
* Should significantly reduce flaky test failures due to AddressInUse exceptions
* Addresses #1296
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.

6 participants