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

stopTimers capture in ServiceManager #5903

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,18 @@ public void testServiceStartupDurations() {
assertThat(startupTimes.get(b)).isAtLeast(Duration.ofMillis(353));
}

public void testServiceStopDurations() {
Service a = new NoOpDelayedService(150);
Service b = new NoOpDelayedService(353);
ServiceManager serviceManager = new ServiceManager(asList(a, b));
serviceManager.startAsync().awaitHealthy();
serviceManager.stopAsync().awaitStopped();
ImmutableMap<Service, Duration> stopDurations = serviceManager.stopDurations();
assertThat(stopDurations).hasSize(2);
assertThat(stopDurations.get(a)).isAtLeast(Duration.ofMillis(150));
assertThat(stopDurations.get(b)).isAtLeast(Duration.ofMillis(353));
}


public void testServiceStartupTimes_selfStartingServices() {
// This tests to ensure that:
Expand Down
120 changes: 106 additions & 14 deletions guava/src/com/google/common/util/concurrent/ServiceManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.IdentityHashMap;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -334,6 +335,7 @@ public void awaitHealthy(long timeout, TimeUnit unit) throws TimeoutException {
@CanIgnoreReturnValue
public ServiceManager stopAsync() {
for (Service service : services) {
state.tryStartStopTiming(service);
service.stopAsync();
}
return this;
Expand Down Expand Up @@ -414,6 +416,17 @@ public ImmutableMap<Service, Long> startupTimes() {
return state.startupTimes();
}

/**
* Returns the service stop times. This value will only return stop times for services that
* have finished stopping.
*
* @return Map of services and their corresponding stop time in millis, the map entries will be
* ordered by stop time.
*/
public ImmutableMap<Service, Long> stopTimes() {
return state.stopTimes();
}

/**
* Returns the service load times. This value will only return startup times for services that
* have finished starting.
Expand All @@ -428,6 +441,19 @@ public ImmutableMap<Service, Duration> startupDurations() {
Maps.<Service, Long, Duration>transformValues(startupTimes(), Duration::ofMillis));
}

/**
* Returns the service stopping termination times. This method returns values for only those services
* that have finished stopping.
*
* @return Map of services and their corresponding stop time, the map entries will be ordered
* by stop time.
*/
@J2ObjCIncompatible
public ImmutableMap<Service, Duration> stopDurations() {
return ImmutableMap.copyOf(
Maps.<Service, Long, Duration>transformValues(stopTimes(), Duration::ofMillis));
}

@Override
public String toString() {
return MoreObjects.toStringHelper(ServiceManager.class)
Expand All @@ -452,6 +478,9 @@ private static final class ServiceManagerState {
@GuardedBy("monitor")
final Map<Service, Stopwatch> startupTimers = Maps.newIdentityHashMap();

@GuardedBy("monitor")
final Map<Service, Stopwatch> stopTimers = new IdentityHashMap<>();

/**
* These two booleans are used to mark the state as ready to start.
*
Expand Down Expand Up @@ -542,6 +571,22 @@ void tryStartTiming(Service service) {
}
}

/**
* Attempts to start the stop timer immediately prior to the service being stopped via {@link
* Service#stopAsync()}
*/
void tryStartStopTiming(Service service) {
monitor.enter();
try {
Stopwatch stopwatch = stopTimers.get(service);
if (stopwatch == null) {
stopTimers.put(service, Stopwatch.createStarted());
}
} finally {
monitor.leave();
}
}

/**
* Marks the {@link State} as ready to receive transitions. Returns true if no transitions have
* been observed yet.
Expand Down Expand Up @@ -661,6 +706,35 @@ public Long apply(Entry<Service, Long> input) {
return ImmutableMap.copyOf(loadTimes);
}

ImmutableMap<Service, Long> stopTimes() {
List<Entry<Service, Long>> stopTimes;
monitor.enter();
try {
stopTimes = Lists.newArrayListWithCapacity(stopTimers.size());
// N.B. There will only be an entry in the map if the service has stopped
for (Entry<Service, Stopwatch> entry : stopTimers.entrySet()) {
Service service = entry.getKey();
Stopwatch stopwatch = entry.getValue();
if (!stopwatch.isRunning() && !(service instanceof NoOpService)) {
stopTimes.add(Maps.immutableEntry(service, stopwatch.elapsed(MILLISECONDS)));
}
}
} finally {
monitor.leave();
}
Collections.sort(
stopTimes,
Ordering.natural()
.onResultOf(
new Function<Entry<Service, Long>, Long>() {
@Override
public Long apply(Entry<Service, Long> input) {
return input.getValue();
}
}));
return ImmutableMap.copyOf(stopTimes);
}

/**
* Updates the state with the given service transition.
*
Expand Down Expand Up @@ -693,20 +767,8 @@ void transitionService(final Service service, State from, State to) {
"Service %s in the state map unexpectedly at %s",
service,
to);
// Update the timer
Stopwatch stopwatch = startupTimers.get(service);
if (stopwatch == null) {
// This means the service was started by some means other than ServiceManager.startAsync
stopwatch = Stopwatch.createStarted();
startupTimers.put(service, stopwatch);
}
if (to.compareTo(RUNNING) >= 0 && stopwatch.isRunning()) {
// N.B. if we miss the STARTING event then we may never record a startup time.
stopwatch.stop();
if (!(service instanceof NoOpService)) {
logger.log(Level.FINE, "Started {0} in {1}.", new Object[] {service, stopwatch});
}
}

updateStartAndStopTimersIfRequired(service, to);
// Queue our listeners

// Did a service fail?
Expand All @@ -728,6 +790,36 @@ void transitionService(final Service service, State from, State to) {
}
}

void updateStartAndStopTimersIfRequired(Service service, State state) {

// Update the timer
Stopwatch stopwatch = startupTimers.get(service);
if (stopwatch == null) {
// This means the service was started by some means other than ServiceManager.startAsync
stopwatch = Stopwatch.createStarted();
startupTimers.put(service, stopwatch);
}
if (state.compareTo(RUNNING) >= 0 && stopwatch.isRunning()) {
// N.B. if we miss the STARTING event then we may never record a startup time.
stopwatch.stop();
if (!(service instanceof NoOpService)) {
logger.log(Level.FINE, "Started {0} in {1}.", new Object[]{service, stopwatch});
}
}

stopwatch = stopTimers.get(service);
// can be null only if the service has been triggered stop from somewhere else than ServiceManager.stopAsync
if (stopwatch == null) {
stopTimers.put(service, Stopwatch.createStarted());
}
if (state.compareTo(TERMINATED) >= 0 && stopwatch.isRunning()) {
stopwatch.stop();
if (!(service instanceof NoOpService)) {
logger.log(Level.FINE, "Stopped {0} in {1}.", new Object[]{service, stopwatch});
}
}
}

void enqueueStoppedEvent() {
listeners.enqueue(STOPPED_EVENT);
}
Expand Down