Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: make NameResolver not thread-safe #5364

Merged
merged 6 commits into from
Feb 20, 2019

Conversation

zhangkun83
Copy link
Contributor

Resolves #2649

As a prerequisite, added getSynchronizationContext() to NameResolver.Helper.

DnsNameResolver has gone through a small refactor around the Resolve runnable, which makes it a little simpler.

private final Stopwatch stopwatch;
private final SynchronizationContext syncContext;

// Following fields must be accessed from synContext
Copy link
Member

Choose a reason for hiding this comment

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

s/synContext/syncContext/

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.

private boolean resolving;
@GuardedBy("this")
private Listener listener;
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this can be accessed outside of the syncContext; it's only changed in start().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The field will only be accessed from syncContext, while it can be called from any thread. I have made it more clear in the comments.

Copy link
Member

Choose a reason for hiding this comment

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

I'm saying that listener can actually be accessed from any thread. It doesn't change.

@@ -138,17 +138,17 @@
private final String host;
private final int port;
private final Resource<Executor> executorResource;
@GuardedBy("this")
private final long cacheTtlNanos;
private final Stopwatch stopwatch;
Copy link
Member

Choose a reason for hiding this comment

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

This should only be accessed from syncContext.

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.

@@ -214,13 +218,27 @@ void onAddresses(
/**
* The port number used in case the target or the underlying naming system doesn't provide a
* port number.
*
* @since 1.19.0
Copy link
Contributor

Choose a reason for hiding this comment

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

Independently of this PR, you should probably backport this to v1.19.x

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure: #5367

@@ -37,10 +37,14 @@
* {@link Listener} is responsible for eventually (after an appropriate backoff period) invoking
* {@link #refresh()}.
*
* <p>Implementations <string>don't need to be thread-safe</strong>. All methods are guaranteed to
Copy link
Contributor

Choose a reason for hiding this comment

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

s/string/strong/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Blame muscle memory :P

private final SynchronizationContext syncContext;

// Following fields must be accessed from synContext
private ResolutionResults cachedResolutionResults = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

remove the null. Its the default and surprising to see it.

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.

this.resolveRunnable = new Resolve(this, stopwatch, getNetworkAddressCacheTtlNanos(isAndroid));
this.cacheTtlNanos = getNetworkAddressCacheTtlNanos(isAndroid);
this.stopwatch = Preconditions.checkNotNull(stopwatch, "stopwatch");
this.syncContext =
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a little uncomfortable to me. It would be better if the ctor was dumber, and the DNRP constructed the dependencies for this class, rather than DNR constructing its own. Same with proxyDetector.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure what you are suggesting. Are you suggesting instead of passing Helper to the ctor, passing the defaultPort, proxyDetector and syncContext? I don't see much benefit of it rather than a longer argument list. There will be more stuff on Helper that DNR needs to look at. Flatting them out doesn't seem favorable.

Copy link
Member

Choose a reason for hiding this comment

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

This new form was much clearer for me, because they are the same for every resolver. Having them in the resolver is harder to reason about because I then have to check if they change. I'd actually prefer if Listener was removed from Resolver as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay then, I think helper needs a checkNotNull too, then.

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.

Preconditions.checkState(this.listener == null, "already started");
executor = SharedResourceHolder.get(executorResource);
this.listener = Preconditions.checkNotNull(listener, "listener");
resolve();
}

@Override
public final synchronized void refresh() {
public final void refresh() {
Copy link
Contributor

Choose a reason for hiding this comment

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

This does not need to be final, since the class is final.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed final on all methods like this.

public void run() {
cachedResolutionResults = results;
}
});
if (cacheTtlNanos > 0) {
stopwatch.reset().start();
Copy link
Contributor

Choose a reason for hiding this comment

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

Can stopwatch be accessed by multiple threads?

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. Moved into syncContext.

Copy link
Contributor Author

@zhangkun83 zhangkun83 left a comment

Choose a reason for hiding this comment

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

Thanks for the review. PTAL

@@ -37,10 +37,14 @@
* {@link Listener} is responsible for eventually (after an appropriate backoff period) invoking
* {@link #refresh()}.
*
* <p>Implementations <string>don't need to be thread-safe</strong>. All methods are guaranteed to
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Blame muscle memory :P

@@ -138,17 +138,17 @@
private final String host;
private final int port;
private final Resource<Executor> executorResource;
@GuardedBy("this")
private final long cacheTtlNanos;
private final Stopwatch stopwatch;
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.

private final Stopwatch stopwatch;
private final SynchronizationContext syncContext;

// Following fields must be accessed from synContext
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.

private final SynchronizationContext syncContext;

// Following fields must be accessed from synContext
private ResolutionResults cachedResolutionResults = 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.

Done.

private boolean resolving;
@GuardedBy("this")
private Listener listener;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The field will only be accessed from syncContext, while it can be called from any thread. I have made it more clear in the comments.

this.resolveRunnable = new Resolve(this, stopwatch, getNetworkAddressCacheTtlNanos(isAndroid));
this.cacheTtlNanos = getNetworkAddressCacheTtlNanos(isAndroid);
this.stopwatch = Preconditions.checkNotNull(stopwatch, "stopwatch");
this.syncContext =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure what you are suggesting. Are you suggesting instead of passing Helper to the ctor, passing the defaultPort, proxyDetector and syncContext? I don't see much benefit of it rather than a longer argument list. There will be more stuff on Helper that DNR needs to look at. Flatting them out doesn't seem favorable.

Preconditions.checkState(this.listener == null, "already started");
executor = SharedResourceHolder.get(executorResource);
this.listener = Preconditions.checkNotNull(listener, "listener");
resolve();
}

@Override
public final synchronized void refresh() {
public final void refresh() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed final on all methods like this.

public void run() {
cachedResolutionResults = results;
}
});
if (cacheTtlNanos > 0) {
stopwatch.reset().start();
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. Moved into syncContext.

@@ -214,13 +218,27 @@ void onAddresses(
/**
* The port number used in case the target or the underlying naming system doesn't provide a
* port number.
*
* @since 1.19.0
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure: #5367

private final SynchronizationContext syncContext;

// Must only be called from syncContext
private final Stopwatch stopwatch;
Copy link
Member

Choose a reason for hiding this comment

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

It seems better to include this in the "must be accessed from the syncContext" section.

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.

Copy link
Contributor Author

@zhangkun83 zhangkun83 left a comment

Choose a reason for hiding this comment

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

@carl-mastrangelo, PTAL
I'd like to include this change in my import this week.

Copy link
Contributor

@carl-mastrangelo carl-mastrangelo left a comment

Choose a reason for hiding this comment

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

One comment but LGTM

this.resolveRunnable = new Resolve(this, stopwatch, getNetworkAddressCacheTtlNanos(isAndroid));
this.cacheTtlNanos = getNetworkAddressCacheTtlNanos(isAndroid);
this.stopwatch = Preconditions.checkNotNull(stopwatch, "stopwatch");
this.syncContext =
Copy link
Contributor

Choose a reason for hiding this comment

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

Okay then, I think helper needs a checkNotNull too, then.

@zhangkun83 zhangkun83 merged commit b867f8e into grpc:master Feb 20, 2019
@zhangkun83 zhangkun83 deleted the nr_not_threadsafe branch February 20, 2019 19:45
@lock lock bot locked as resolved and limited conversation to collaborators May 21, 2019
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.

NameResolver can be NotThreadSafe
3 participants