core: factor service config state management out of ManagedChannelImpl

This commit is contained in:
Carl Mastrangelo 2019-05-07 08:36:11 -07:00 committed by GitHub
parent 257bb546fd
commit a7660eeef7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 575 additions and 0 deletions

View File

@ -0,0 +1,125 @@
/*
* Copyright 2019 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.internal;
import static com.google.common.base.Preconditions.checkState;
import io.grpc.NameResolver.ConfigOrError;
import javax.annotation.Nullable;
/**
* {@link ServiceConfigState} holds the state of the current service config. It must be mutated
* and read from {@link ManagedChannelImpl} constructor or the provided syncContext.
*/
final class ServiceConfigState {
@Nullable private final ConfigOrError defaultServiceConfig;
private final boolean lookUpServiceConfig;
// mutable state
@Nullable private ConfigOrError currentServiceConfigOrError;
// Has there been at least one update?
private boolean updated;
/**
* @param defaultServiceConfig The initial service config, or {@code null} if absent.
* @param lookUpServiceConfig {@code true} if service config updates might occur.
* @param syncCtx The synchronization context that this is accessed from.
*/
ServiceConfigState(
@Nullable ManagedChannelServiceConfig defaultServiceConfig,
boolean lookUpServiceConfig) {
if (defaultServiceConfig == null) {
this.defaultServiceConfig = null;
} else {
this.defaultServiceConfig = ConfigOrError.fromConfig(defaultServiceConfig);
}
this.lookUpServiceConfig = lookUpServiceConfig;
if (!lookUpServiceConfig) {
this.currentServiceConfigOrError = this.defaultServiceConfig;
}
}
/**
* Returns {@code true} if it RPCs should wait on a service config resolution. This can return
* {@code false} if:
*
* <ul>
* <li>There is a valid service config from the name resolver
* <li>There is a valid default service config and a service config error from the name
* resolver
* <li>No service config from the name resolver, and no intent to lookup a service config.
* </ul>
*
* <p>In the final case, the default service config may be present or absent, and will be the
* current service config.
*/
boolean shouldWaitOnServiceConfig() {
return !(updated || !expectUpdates());
}
/**
* Gets the current service config or error.
*
* @throws IllegalStateException if the service config has not yet been updated.
*/
@Nullable
ConfigOrError getCurrent() {
checkState(!shouldWaitOnServiceConfig(), "still waiting on service config");
return currentServiceConfigOrError;
}
void update(@Nullable ConfigOrError coe) {
checkState(expectUpdates(), "unexpected service config update");
boolean firstUpdate = !updated;
updated = true;
if (firstUpdate) {
if (coe == null) {
currentServiceConfigOrError = defaultServiceConfig;
} else if (coe.getError() != null) {
if (defaultServiceConfig != null) {
currentServiceConfigOrError = defaultServiceConfig;
} else {
currentServiceConfigOrError = coe;
}
} else {
assert coe.getConfig() != null;
currentServiceConfigOrError = coe;
}
} else {
if (coe == null) {
if (defaultServiceConfig != null) {
currentServiceConfigOrError = defaultServiceConfig;
} else {
currentServiceConfigOrError = null;
}
} else if (coe.getError() != null) {
if (currentServiceConfigOrError != null && currentServiceConfigOrError.getError() != null) {
currentServiceConfigOrError = coe;
} else {
// discard
}
} else {
assert coe.getConfig() != null;
currentServiceConfigOrError = coe;
}
}
}
boolean expectUpdates() {
return lookUpServiceConfig;
}
}

View File

@ -0,0 +1,450 @@
/*
* Copyright 2019 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.internal;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import io.grpc.NameResolver.ConfigOrError;
import io.grpc.Status;
import io.grpc.internal.ManagedChannelServiceConfig.MethodInfo;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link ServiceConfigState}.
*/
@RunWith(JUnit4.class)
public class ServiceConfigStateTest {
private final ManagedChannelServiceConfig serviceConfig1 = new ManagedChannelServiceConfig(
Collections.<String, MethodInfo>emptyMap(),
Collections.<String, MethodInfo>emptyMap(),
null,
null);
private final ManagedChannelServiceConfig serviceConfig2 = new ManagedChannelServiceConfig(
Collections.<String, MethodInfo>emptyMap(),
Collections.<String, MethodInfo>emptyMap(),
null,
null);
private final ConfigOrError config1 = ConfigOrError.fromConfig(serviceConfig1);
private final ConfigOrError config2 = ConfigOrError.fromConfig(serviceConfig2);
private final Status serviceConfigError1 = Status.UNKNOWN.withDescription("bang");
private final Status serviceConfigError2 = Status.INTERNAL.withDescription("boom");
private final ConfigOrError error1 = ConfigOrError.fromError(serviceConfigError1);
private final ConfigOrError error2 = ConfigOrError.fromError(serviceConfigError2);
private final ManagedChannelServiceConfig noServiceConfig = null;
private final ConfigOrError noConfig = null;
@Test
public void noLookup_default() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, false);
assertFalse(scs.expectUpdates());
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
}
@Test
public void noLookup_default_allUpdatesFail() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, false);
try {
scs.update(error1);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("unexpected service config update");
}
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
try {
scs.update(error1);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("unexpected service config update");
}
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
try {
scs.update(noConfig);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("unexpected service config update");
}
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
}
@Test
public void noLookup_noDefault() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, false);
assertFalse(scs.expectUpdates());
assertFalse(scs.shouldWaitOnServiceConfig());
assertNull(scs.getCurrent());
}
@Test
public void noLookup_noDefault_allUpdatesFail() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, false);
try {
scs.update(error1);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("unexpected service config update");
}
assertFalse(scs.shouldWaitOnServiceConfig());
assertNull(scs.getCurrent());
try {
scs.update(config2);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("unexpected service config update");
}
assertFalse(scs.shouldWaitOnServiceConfig());
assertNull(scs.getCurrent());
try {
scs.update(noConfig);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("unexpected service config update");
}
assertFalse(scs.shouldWaitOnServiceConfig());
assertNull(scs.getCurrent());
}
@Test
public void lookup_noDefault() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
assertTrue(scs.expectUpdates());
assertTrue(scs.shouldWaitOnServiceConfig());
try {
scs.getCurrent();
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("still waiting on service config");
}
}
@Test
public void lookup_noDefault_onError_onError() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(error1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(error1, scs.getCurrent());
scs.update(error2);
assertSame(error2, scs.getCurrent());
}
@Test
public void lookup_noDefault_onError_onAbsent() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(error1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(error1, scs.getCurrent());
scs.update(noConfig);
assertNull(scs.getCurrent());
scs.update(error2);
assertNull(scs.getCurrent()); //ignores future errors
}
@Test
public void lookup_noDefault_onError_onPresent() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(error1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(error1, scs.getCurrent());
scs.update(config1);
assertSame(config1, scs.getCurrent());
scs.update(error2);
assertSame(config1, scs.getCurrent()); //ignores future errors
}
@Test
public void lookup_noDefault_onAbsent() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(noConfig);
assertFalse(scs.shouldWaitOnServiceConfig());
assertNull(scs.getCurrent());
}
@Test
public void lookup_noDefault_onAbsent_onError() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(noConfig);
assertFalse(scs.shouldWaitOnServiceConfig());
assertNull(scs.getCurrent());
scs.update(error1);
assertNull(scs.getCurrent());
}
@Test
public void lookup_noDefault_onAbsent_onAbsent() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(noConfig);
assertFalse(scs.shouldWaitOnServiceConfig());
assertNull(scs.getCurrent());
scs.update(noConfig);
assertNull(scs.getCurrent());
}
@Test
public void lookup_noDefault_onAbsent_onPresent() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(noConfig);
assertFalse(scs.shouldWaitOnServiceConfig());
assertNull(scs.getCurrent());
scs.update(config1);
assertSame(config1, scs.getCurrent());
}
@Test
public void lookup_noDefault_onPresent() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(config1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(config1, scs.getCurrent());
}
@Test
public void lookup_noDefault_onPresent_onError() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(config1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(config1, scs.getCurrent());
scs.update(error1);
assertSame(config1, scs.getCurrent());
}
@Test
public void lookup_noDefault_onPresent_onAbsent() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(config1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(config1, scs.getCurrent());
scs.update(noConfig);
assertNull(scs.getCurrent());
}
@Test
public void lookup_noDefault_onPresent_onPresent() {
ServiceConfigState scs = new ServiceConfigState(noServiceConfig, true);
scs.update(config1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(config1, scs.getCurrent());
scs.update(config2);
assertSame(config2, scs.getCurrent());
}
@Test
public void lookup_default() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
assertTrue(scs.expectUpdates());
assertTrue(scs.shouldWaitOnServiceConfig());
try {
scs.getCurrent();
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("still waiting on service config");
}
}
@Test
public void lookup_default_onError() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(error1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
}
@Test
public void lookup_default_onError_onError() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(error1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
scs.update(error2);
assertSameConfig(config1, scs.getCurrent());
}
@Test
public void lookup_default_onError_onAbsent() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(error1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
scs.update(noConfig);
assertSameConfig(config1, scs.getCurrent());
}
@Test
public void lookup_default_onError_onPresent() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(error1);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
scs.update(config2);
assertSame(config2, scs.getCurrent());
}
@Test
public void lookup_default_onAbsent() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(noConfig);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
}
@Test
public void lookup_default_onAbsent_onError() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(noConfig);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
scs.update(error1);
assertSameConfig(config1, scs.getCurrent());
}
@Test
public void lookup_default_onAbsent_onAbsent() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(noConfig);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
scs.update(noConfig);
assertSameConfig(config1, scs.getCurrent());
}
@Test
public void lookup_default_onAbsent_onPresent() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(noConfig);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSameConfig(config1, scs.getCurrent());
scs.update(config2);
assertSame(config2, scs.getCurrent());
}
@Test
public void lookup_default_onPresent() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(config2);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(config2, scs.getCurrent());
}
@Test
public void lookup_default_onPresent_onError() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(config2);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(config2, scs.getCurrent());
scs.update(error1);
assertSame(config2, scs.getCurrent());
}
@Test
public void lookup_default_onPresent_onAbsent() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
scs.update(config2);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(config2, scs.getCurrent());
scs.update(noConfig);
assertSameConfig(config1, scs.getCurrent());
}
@Test
public void lookup_default_onPresent_onPresent() {
ServiceConfigState scs = new ServiceConfigState(serviceConfig1, true);
ManagedChannelServiceConfig serviceConfig3 = new ManagedChannelServiceConfig(
Collections.<String, MethodInfo>emptyMap(),
Collections.<String, MethodInfo>emptyMap(),
null,
null);
ConfigOrError config3 = ConfigOrError.fromConfig(serviceConfig3);
scs.update(config2);
assertFalse(scs.shouldWaitOnServiceConfig());
assertSame(config2, scs.getCurrent());
scs.update(config3);
assertSame(config3, scs.getCurrent());
}
private static void assertSameConfig(ConfigOrError expected, ConfigOrError actual) {
if (actual == null || expected == null) {
assertSame(expected, actual);
}
assertWithMessage(actual.toString()).that(actual.getConfig())
.isSameInstanceAs(expected.getConfig());
}
}