mirror of https://github.com/grpc/grpc-java.git
core: InternalSubchannel uses ChannelExecutor. (#2503)
This commit is contained in:
parent
cb6cf1ae2f
commit
f8f569e078
|
|
@ -51,14 +51,12 @@ import java.net.SocketAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.CheckReturnValue;
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
@ -72,8 +70,6 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||||
final class InternalSubchannel implements WithLogId {
|
final class InternalSubchannel implements WithLogId {
|
||||||
private static final Logger log = Logger.getLogger(InternalSubchannel.class.getName());
|
private static final Logger log = Logger.getLogger(InternalSubchannel.class.getName());
|
||||||
|
|
||||||
|
|
||||||
private final Object lock = new Object();
|
|
||||||
private final LogId logId = LogId.allocate(getClass().getName());
|
private final LogId logId = LogId.allocate(getClass().getName());
|
||||||
private final EquivalentAddressGroup addressGroup;
|
private final EquivalentAddressGroup addressGroup;
|
||||||
private final String authority;
|
private final String authority;
|
||||||
|
|
@ -83,18 +79,18 @@ final class InternalSubchannel implements WithLogId {
|
||||||
private final ClientTransportFactory transportFactory;
|
private final ClientTransportFactory transportFactory;
|
||||||
private final ScheduledExecutorService scheduledExecutor;
|
private final ScheduledExecutorService scheduledExecutor;
|
||||||
|
|
||||||
// A serializing executor shared across the Channel
|
// File-specific convention: methods without GuardedBy("lock") MUST NOT be called under the lock.
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
// File-specific convention:
|
||||||
//
|
//
|
||||||
// TODO(zhangkun83): decide the type of Channel Executor. I considered a SerializingExecutor
|
// 1. In a method without GuardedBy("lock"), executeLater() MUST be followed by a drain() later in
|
||||||
// based on the app executor, but it seems abusive because the app executor is intended for app
|
// the same method.
|
||||||
// logic, not for channel bookkeeping. We don't want channel bookkeeping logic to contend for
|
|
||||||
// threads with app logic, which may increase latency or even cause starvation. Instead, we
|
|
||||||
// should consider a thread-less Executor after the refactor of ManagedChannelImpl is done.
|
|
||||||
//
|
//
|
||||||
// NOTE: there are cases where channelExecutor.execute() is run under "lock". This will add risk
|
// 2. drain() MUST NOT be called under "lock".
|
||||||
// of deadlock if channelExecutor is based on a direct executor. Thread-less executor wouldn't
|
//
|
||||||
// have such problem.
|
// 3. Every synchronized("lock") must be inside a try-finally which calls drain() in "finally".
|
||||||
private final Executor channelExecutor;
|
private final ChannelExecutor channelExecutor;
|
||||||
|
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
private int nextAddressIndex;
|
private int nextAddressIndex;
|
||||||
|
|
@ -158,7 +154,7 @@ final class InternalSubchannel implements WithLogId {
|
||||||
InternalSubchannel(EquivalentAddressGroup addressGroup, String authority, String userAgent,
|
InternalSubchannel(EquivalentAddressGroup addressGroup, String authority, String userAgent,
|
||||||
BackoffPolicy.Provider backoffPolicyProvider,
|
BackoffPolicy.Provider backoffPolicyProvider,
|
||||||
ClientTransportFactory transportFactory, ScheduledExecutorService scheduledExecutor,
|
ClientTransportFactory transportFactory, ScheduledExecutorService scheduledExecutor,
|
||||||
Supplier<Stopwatch> stopwatchSupplier, Executor channelExecutor, Callback callback) {
|
Supplier<Stopwatch> stopwatchSupplier, ChannelExecutor channelExecutor, Callback callback) {
|
||||||
this.addressGroup = Preconditions.checkNotNull(addressGroup, "addressGroup");
|
this.addressGroup = Preconditions.checkNotNull(addressGroup, "addressGroup");
|
||||||
this.authority = authority;
|
this.authority = authority;
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
|
|
@ -173,15 +169,15 @@ final class InternalSubchannel implements WithLogId {
|
||||||
/**
|
/**
|
||||||
* Returns a READY transport that will be used to create new streams.
|
* Returns a READY transport that will be used to create new streams.
|
||||||
*
|
*
|
||||||
* <p>Returns {@code null} if the state is not READY.
|
* <p>Returns {@code null} if the state is not READY. Will try to connect if state is IDLE.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
final ClientTransport obtainActiveTransport() {
|
ClientTransport obtainActiveTransport() {
|
||||||
ClientTransport savedTransport = activeTransport;
|
ClientTransport savedTransport = activeTransport;
|
||||||
if (savedTransport != null) {
|
if (savedTransport != null) {
|
||||||
return savedTransport;
|
return savedTransport;
|
||||||
}
|
}
|
||||||
Runnable runnable = null;
|
try {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
savedTransport = activeTransport;
|
savedTransport = activeTransport;
|
||||||
// Check again, since it could have changed before acquiring the lock
|
// Check again, since it could have changed before acquiring the lock
|
||||||
|
|
@ -190,18 +186,17 @@ final class InternalSubchannel implements WithLogId {
|
||||||
}
|
}
|
||||||
if (state.getState() == IDLE) {
|
if (state.getState() == IDLE) {
|
||||||
gotoNonErrorState(CONNECTING);
|
gotoNonErrorState(CONNECTING);
|
||||||
runnable = startNewTransport();
|
startNewTransport();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (runnable != null) {
|
} finally {
|
||||||
runnable.run();
|
channelExecutor.drain();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckReturnValue
|
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
private Runnable startNewTransport() {
|
private void startNewTransport() {
|
||||||
Preconditions.checkState(reconnectTask == null, "Should have no reconnectTask scheduled");
|
Preconditions.checkState(reconnectTask == null, "Should have no reconnectTask scheduled");
|
||||||
|
|
||||||
if (nextAddressIndex == 0) {
|
if (nextAddressIndex == 0) {
|
||||||
|
|
@ -217,11 +212,14 @@ final class InternalSubchannel implements WithLogId {
|
||||||
transportFactory.newClientTransport(address, authority, userAgent);
|
transportFactory.newClientTransport(address, authority, userAgent);
|
||||||
if (log.isLoggable(Level.FINE)) {
|
if (log.isLoggable(Level.FINE)) {
|
||||||
log.log(Level.FINE, "[{0}] Created {1} for {2}",
|
log.log(Level.FINE, "[{0}] Created {1} for {2}",
|
||||||
new Object[] {getLogId(), transport.getLogId(), address});
|
new Object[] {logId, transport.getLogId(), address});
|
||||||
}
|
}
|
||||||
pendingTransport = transport;
|
pendingTransport = transport;
|
||||||
transports.add(transport);
|
transports.add(transport);
|
||||||
return transport.start(new TransportListener(transport, address));
|
Runnable runnable = transport.start(new TransportListener(transport, address));
|
||||||
|
if (runnable != null) {
|
||||||
|
channelExecutor.executeLater(runnable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -235,7 +233,6 @@ final class InternalSubchannel implements WithLogId {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
Runnable runnable = null;
|
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
reconnectTask = null;
|
reconnectTask = null;
|
||||||
if (state.getState() == SHUTDOWN) {
|
if (state.getState() == SHUTDOWN) {
|
||||||
|
|
@ -244,13 +241,12 @@ final class InternalSubchannel implements WithLogId {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
gotoNonErrorState(CONNECTING);
|
gotoNonErrorState(CONNECTING);
|
||||||
runnable = startNewTransport();
|
startNewTransport();
|
||||||
}
|
|
||||||
if (runnable != null) {
|
|
||||||
runnable.run();
|
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
log.log(Level.WARNING, "Exception handling end of backoff", t);
|
log.log(Level.WARNING, "Exception handling end of backoff", t);
|
||||||
|
} finally {
|
||||||
|
channelExecutor.drain();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -262,8 +258,7 @@ final class InternalSubchannel implements WithLogId {
|
||||||
long delayMillis =
|
long delayMillis =
|
||||||
reconnectPolicy.nextBackoffMillis() - connectingTimer.elapsed(TimeUnit.MILLISECONDS);
|
reconnectPolicy.nextBackoffMillis() - connectingTimer.elapsed(TimeUnit.MILLISECONDS);
|
||||||
if (log.isLoggable(Level.FINE)) {
|
if (log.isLoggable(Level.FINE)) {
|
||||||
log.log(Level.FINE, "[{0}] Scheduling backoff for {1} ms",
|
log.log(Level.FINE, "[{0}] Scheduling backoff for {1} ms", new Object[]{logId, delayMillis});
|
||||||
new Object[]{getLogId(), delayMillis});
|
|
||||||
}
|
}
|
||||||
Preconditions.checkState(reconnectTask == null, "previous reconnectTask is not done");
|
Preconditions.checkState(reconnectTask == null, "previous reconnectTask is not done");
|
||||||
reconnectTask = scheduledExecutor.schedule(
|
reconnectTask = scheduledExecutor.schedule(
|
||||||
|
|
@ -283,7 +278,7 @@ final class InternalSubchannel implements WithLogId {
|
||||||
Preconditions.checkState(state.getState() != SHUTDOWN,
|
Preconditions.checkState(state.getState() != SHUTDOWN,
|
||||||
"Cannot transition out of SHUTDOWN to " + newState);
|
"Cannot transition out of SHUTDOWN to " + newState);
|
||||||
state = newState;
|
state = newState;
|
||||||
channelExecutor.execute(new Runnable() {
|
channelExecutor.executeLater(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
callback.onStateChange(InternalSubchannel.this, newState);
|
callback.onStateChange(InternalSubchannel.this, newState);
|
||||||
|
|
@ -295,6 +290,7 @@ final class InternalSubchannel implements WithLogId {
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
ManagedClientTransport savedActiveTransport;
|
ManagedClientTransport savedActiveTransport;
|
||||||
ConnectionClientTransport savedPendingTransport;
|
ConnectionClientTransport savedPendingTransport;
|
||||||
|
try {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (state.getState() == SHUTDOWN) {
|
if (state.getState() == SHUTDOWN) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -307,11 +303,14 @@ final class InternalSubchannel implements WithLogId {
|
||||||
if (transports.isEmpty()) {
|
if (transports.isEmpty()) {
|
||||||
handleTermination();
|
handleTermination();
|
||||||
if (log.isLoggable(Level.FINE)) {
|
if (log.isLoggable(Level.FINE)) {
|
||||||
log.log(Level.FINE, "[{0}] Terminated in shutdown()", getLogId());
|
log.log(Level.FINE, "[{0}] Terminated in shutdown()", logId);
|
||||||
}
|
}
|
||||||
} // else: the callback will be run once all transports have been terminated
|
} // else: the callback will be run once all transports have been terminated
|
||||||
cancelReconnectTask();
|
cancelReconnectTask();
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
channelExecutor.drain();
|
||||||
|
}
|
||||||
if (savedActiveTransport != null) {
|
if (savedActiveTransport != null) {
|
||||||
savedActiveTransport.shutdown();
|
savedActiveTransport.shutdown();
|
||||||
}
|
}
|
||||||
|
|
@ -320,9 +319,9 @@ final class InternalSubchannel implements WithLogId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// May be called under lock.
|
@GuardedBy("lock")
|
||||||
private void handleTermination() {
|
private void handleTermination() {
|
||||||
channelExecutor.execute(new Runnable() {
|
channelExecutor.executeLater(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
callback.onTerminated(InternalSubchannel.this);
|
callback.onTerminated(InternalSubchannel.this);
|
||||||
|
|
@ -332,25 +331,33 @@ final class InternalSubchannel implements WithLogId {
|
||||||
|
|
||||||
private void handleTransportInUseState(
|
private void handleTransportInUseState(
|
||||||
final ConnectionClientTransport transport, final boolean inUse) {
|
final ConnectionClientTransport transport, final boolean inUse) {
|
||||||
channelExecutor.execute(new Runnable() {
|
channelExecutor.executeLater(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
inUseStateAggregator.updateObjectInUse(transport, inUse);
|
inUseStateAggregator.updateObjectInUse(transport, inUse);
|
||||||
}
|
}
|
||||||
});
|
}).drain();
|
||||||
}
|
}
|
||||||
|
|
||||||
void shutdownNow(Status reason) {
|
void shutdownNow(Status reason) {
|
||||||
shutdown();
|
shutdown();
|
||||||
Collection<ManagedClientTransport> transportsCopy;
|
Collection<ManagedClientTransport> transportsCopy;
|
||||||
|
try {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
transportsCopy = new ArrayList<ManagedClientTransport>(transports);
|
transportsCopy = new ArrayList<ManagedClientTransport>(transports);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
channelExecutor.drain();
|
||||||
|
}
|
||||||
for (ManagedClientTransport transport : transportsCopy) {
|
for (ManagedClientTransport transport : transportsCopy) {
|
||||||
transport.shutdownNow(reason);
|
transport.shutdownNow(reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EquivalentAddressGroup getAddressGroup() {
|
||||||
|
return addressGroup;
|
||||||
|
}
|
||||||
|
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
private void cancelReconnectTask() {
|
private void cancelReconnectTask() {
|
||||||
if (reconnectTask != null) {
|
if (reconnectTask != null) {
|
||||||
|
|
@ -366,9 +373,13 @@ final class InternalSubchannel implements WithLogId {
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
ConnectivityState getState() {
|
ConnectivityState getState() {
|
||||||
|
try {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
return state.getState();
|
return state.getState();
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
channelExecutor.drain();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Listener for real transports. */
|
/** Listener for real transports. */
|
||||||
|
|
@ -385,9 +396,10 @@ final class InternalSubchannel implements WithLogId {
|
||||||
public void transportReady() {
|
public void transportReady() {
|
||||||
if (log.isLoggable(Level.FINE)) {
|
if (log.isLoggable(Level.FINE)) {
|
||||||
log.log(Level.FINE, "[{0}] {1} for {2} is ready",
|
log.log(Level.FINE, "[{0}] {1} for {2} is ready",
|
||||||
new Object[] {getLogId(), transport.getLogId(), address});
|
new Object[] {logId, transport.getLogId(), address});
|
||||||
}
|
}
|
||||||
ConnectivityState savedState;
|
ConnectivityState savedState;
|
||||||
|
try {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
savedState = state.getState();
|
savedState = state.getState();
|
||||||
reconnectPolicy = null;
|
reconnectPolicy = null;
|
||||||
|
|
@ -402,6 +414,9 @@ final class InternalSubchannel implements WithLogId {
|
||||||
pendingTransport = null;
|
pendingTransport = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
channelExecutor.drain();
|
||||||
|
}
|
||||||
if (savedState == SHUTDOWN) {
|
if (savedState == SHUTDOWN) {
|
||||||
transport.shutdown();
|
transport.shutdown();
|
||||||
}
|
}
|
||||||
|
|
@ -416,9 +431,9 @@ final class InternalSubchannel implements WithLogId {
|
||||||
public void transportShutdown(Status s) {
|
public void transportShutdown(Status s) {
|
||||||
if (log.isLoggable(Level.FINE)) {
|
if (log.isLoggable(Level.FINE)) {
|
||||||
log.log(Level.FINE, "[{0}] {1} for {2} is being shutdown with status {3}",
|
log.log(Level.FINE, "[{0}] {1} for {2} is being shutdown with status {3}",
|
||||||
new Object[] {getLogId(), transport.getLogId(), address, s});
|
new Object[] {logId, transport.getLogId(), address, s});
|
||||||
}
|
}
|
||||||
Runnable runnable = null;
|
try {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (state.getState() == SHUTDOWN) {
|
if (state.getState() == SHUTDOWN) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -435,12 +450,12 @@ final class InternalSubchannel implements WithLogId {
|
||||||
// Transition to TRANSIENT_FAILURE
|
// Transition to TRANSIENT_FAILURE
|
||||||
scheduleBackoff(s);
|
scheduleBackoff(s);
|
||||||
} else {
|
} else {
|
||||||
runnable = startNewTransport();
|
startNewTransport();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (runnable != null) {
|
} finally {
|
||||||
runnable.run();
|
channelExecutor.drain();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -448,18 +463,22 @@ final class InternalSubchannel implements WithLogId {
|
||||||
public void transportTerminated() {
|
public void transportTerminated() {
|
||||||
if (log.isLoggable(Level.FINE)) {
|
if (log.isLoggable(Level.FINE)) {
|
||||||
log.log(Level.FINE, "[{0}] {1} for {2} is terminated",
|
log.log(Level.FINE, "[{0}] {1} for {2} is terminated",
|
||||||
new Object[] {getLogId(), transport.getLogId(), address});
|
new Object[] {logId, transport.getLogId(), address});
|
||||||
}
|
}
|
||||||
handleTransportInUseState(transport, false);
|
handleTransportInUseState(transport, false);
|
||||||
|
try {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
transports.remove(transport);
|
transports.remove(transport);
|
||||||
if (state.getState() == SHUTDOWN && transports.isEmpty()) {
|
if (state.getState() == SHUTDOWN && transports.isEmpty()) {
|
||||||
if (log.isLoggable(Level.FINE)) {
|
if (log.isLoggable(Level.FINE)) {
|
||||||
log.log(Level.FINE, "[{0}] Terminated in transportTerminated()", getLogId());
|
log.log(Level.FINE, "[{0}] Terminated in transportTerminated()", logId);
|
||||||
}
|
}
|
||||||
handleTermination();
|
handleTermination();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
channelExecutor.drain();
|
||||||
|
}
|
||||||
Preconditions.checkState(activeTransport != transport,
|
Preconditions.checkState(activeTransport != transport,
|
||||||
"activeTransport still points to this transport. "
|
"activeTransport still points to this transport. "
|
||||||
+ "Seems transportShutdown() was not called.");
|
+ "Seems transportShutdown() was not called.");
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,7 @@ public class InternalSubchannelTest {
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
private final FakeClock fakeClock = new FakeClock();
|
||||||
// For channelExecutor
|
// For channelExecutor
|
||||||
private final FakeClock fakeExecutor = new FakeClock();
|
private final FakeClock fakeExecutor = new FakeClock();
|
||||||
private final SerializingExecutor channelExecutor =
|
private final ChannelExecutor channelExecutor = new ChannelExecutor();
|
||||||
new SerializingExecutor(fakeExecutor.getScheduledExecutorService());
|
|
||||||
|
|
||||||
@Mock private BackoffPolicy mockBackoffPolicy1;
|
@Mock private BackoffPolicy mockBackoffPolicy1;
|
||||||
@Mock private BackoffPolicy mockBackoffPolicy2;
|
@Mock private BackoffPolicy mockBackoffPolicy2;
|
||||||
|
|
@ -678,10 +677,7 @@ public class InternalSubchannelTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertExactCallbackInvokes(String ... expectedInvokes) {
|
private void assertExactCallbackInvokes(String ... expectedInvokes) {
|
||||||
// Make sure all callbacks are to run from channelExecutor only.
|
assertEquals(0, channelExecutor.numPendingTasks());
|
||||||
assertEquals(0, callbackInvokes.size());
|
|
||||||
|
|
||||||
while (fakeExecutor.runDueTasks() > 0) {}
|
|
||||||
assertEquals(Arrays.asList(expectedInvokes), callbackInvokes);
|
assertEquals(Arrays.asList(expectedInvokes), callbackInvokes);
|
||||||
callbackInvokes.clear();
|
callbackInvokes.clear();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue