Merge branch 'master' of ssh://sapsailing.com/home/trac/git

This commit is contained in:
Generic Wiki User
2020-07-28 15:30:02 +00:00
10 changed files with 249 additions and 48 deletions
@@ -1301,4 +1301,17 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public TrackingConnectorInfo getTrackingConnectorInfo() {
return null;
}
@Override
public void runWhenDoneLoading(Runnable runnable) {
}
@Override
public void runSynchronizedOnStatus(Runnable runnable) {
}
@Override
public boolean hasFinishedLoading() {
return false;
}
}
@@ -814,4 +814,16 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
return null;
}
@Override
public void runWhenDoneLoading(Runnable runnable) {
}
@Override
public void runSynchronizedOnStatus(Runnable runnable) {
}
@Override
public boolean hasFinishedLoading() {
return false;
}
}
@@ -65,7 +65,6 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
public DummyTrackedRace(final Map<Competitor, Boat> competitors, final Regatta regatta, final TrackedRegatta trackedRegatta,
RaceDefinition race) {
this(competitors, regatta, trackedRegatta, race, EmptyWindStore.INSTANCE);
}
public DummyTrackedRace(final String raceName, final Serializable raceId) {
@@ -750,4 +749,17 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
public TrackingConnectorInfo getTrackingConnectorInfo() {
return null;
}
@Override
public void runWhenDoneLoading(Runnable runnable) {
}
@Override
public void runSynchronizedOnStatus(Runnable runnable) {
}
@Override
public boolean hasFinishedLoading() {
return false;
}
}
@@ -1256,4 +1256,28 @@ public interface TrackedRace
* may be {@code null}, particularly in test set-ups
*/
TrackingConnectorInfo getTrackingConnectorInfo();
/**
* Under synchronization with the {@link #getStatus() race status} checks if the race {@link #hasFinishedLoading()
* has already finished loading}. If so, {@code runnable} is invoked. Otherwise, a {@link RaceChangeListener} is
* {@link #addListener(RaceChangeListener) added} to this race, observing the race status and calling
* {@code runnable} when the race has finished loading.
* <p>
*
* A race is considered to have finished loading when it is not in either of the states
* {@link TrackedRaceStatusEnum#PREPARED}, {@link TrackedRaceStatusEnum#LOADING}, or
* {@link TrackedRaceStatusEnum#ERROR}.
*
* @param runnable
* must not be {@code null}
*/
void runWhenDoneLoading(Runnable runnable);
/**
* Executes the {@code callable} under synchronization with the {@link #getStatus() race status}; in other words,
* while the callable executes, the race status of this race cannot be updated.
*/
void runSynchronizedOnStatus(Runnable runnable);
boolean hasFinishedLoading();
}
@@ -2919,13 +2919,18 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
/**
* Changes to the {@link #status} variable are synchronized on the {@link #statusNotifier} field.
*
* @return
* Changes to the {@link #status} variable are synchronized on the {@link #statusNotifier} field
*/
protected Object getStatusNotifier() {
return statusNotifier;
}
@Override
public void runSynchronizedOnStatus(Runnable runnable) {
synchronized (getStatusNotifier()) {
runnable.run();
}
}
protected void setStatus(TrackedRaceStatus newStatus) {
assert newStatus != null;
@@ -2994,6 +2999,37 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
}
@Override
public boolean hasFinishedLoading() {
synchronized (getStatusNotifier()) {
final TrackedRaceStatusEnum status = getStatus().getStatus();
return hasFinishedLoading(status);
}
}
private boolean hasFinishedLoading(TrackedRaceStatusEnum status) {
return (status != TrackedRaceStatusEnum.PREPARED && status != TrackedRaceStatusEnum.LOADING && status != TrackedRaceStatusEnum.ERROR);
}
@Override
public void runWhenDoneLoading(final Runnable runnable) {
synchronized (getStatusNotifier()) {
if (!hasFinishedLoading()) {
addListener(new AbstractRaceChangeListener() {
@Override
public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
if (hasFinishedLoading(newStatus.getStatus())) {
removeListener(this);
runnable.run();
}
}
});
} else {
runnable.run();
}
}
}
@Override
public void attachRaceLog(RaceLog raceLog) {
synchronized (TrackedRaceImpl.this) {
@@ -19,6 +19,7 @@ import com.sap.sse.replication.ReplicationService;
import com.sap.sse.replication.ReplicationStatus;
public class StatusServlet extends HttpServlet {
private static final String WAIT_UNTIL_RACES_LOADED = "waitUntilRacesLoaded";
private static final long serialVersionUID = -8896724182560416457L;
protected <T> T getService(Class<T> clazz) {
@@ -43,15 +44,26 @@ public class StatusServlet extends HttpServlet {
final ServletContext servletContext = req.getServletContext();
final JSONObject result = new JSONObject();
final RacingEventService service = getService(servletContext);
result.put("numberofracestorestore", service.getNumberOfTrackedRacesToRestore());
result.put("numberofracesrestored", service.getNumberOfTrackedRacesRestored());
final String waitUntilRacesLoadedString = req.getParameter(WAIT_UNTIL_RACES_LOADED);
boolean waitUntilRacesLoaded = Boolean.valueOf(waitUntilRacesLoadedString);
final long numberOfTrackedRacesToRestore = service.getNumberOfTrackedRacesToRestore();
result.put("numberofracestorestore", numberOfTrackedRacesToRestore);
final int numberOfTrackedRacesRestored = service.getNumberOfTrackedRacesRestored();
result.put("numberofracesrestored", numberOfTrackedRacesRestored);
final int numberOfTrackedRacesRestoredDoneLoading = service.getNumberOfTrackedRacesRestoredDoneLoading();
result.put("numberofracesrestoreddoneloading", numberOfTrackedRacesRestoredDoneLoading);
final int numberOfTrackedRacesStillLoading = service.getNumberOfTrackedRacesStillLoading();
result.put("numberofracesstillloading", numberOfTrackedRacesStillLoading);
final ReplicationService replicationService = getReplicationService(servletContext);
final ReplicationStatus replicationStatus = replicationService == null ? null : replicationService.getStatus();
if (replicationStatus != null) {
result.put("replication", replicationStatus.toJSONObject());
}
final boolean available = service.getNumberOfTrackedRacesRestored() >= service.getNumberOfTrackedRacesToRestore() &&
(replicationStatus == null || replicationStatus.isAvailable());
boolean available = numberOfTrackedRacesRestored >= numberOfTrackedRacesToRestore
&& (replicationStatus == null || replicationStatus.isAvailable());
if (waitUntilRacesLoaded) {
available = available && numberOfTrackedRacesRestoredDoneLoading == numberOfTrackedRacesToRestore;
}
result.put("available", available);
resp.setStatus(available ? HttpServletResponse.SC_OK : HttpServletResponse.SC_SERVICE_UNAVAILABLE);
resp.setContentType(MediaType.APPLICATION_JSON + ";charset=UTF-8");
@@ -17,6 +17,8 @@ Availability will be reported as "true" if all races to be restored have
appeared. There may still be some data retrieval going on, so for the server
truly to be available, after having received available:true for the first time,
add some leeway that corresponds to the typical race load time.</p>
<p>You can request to obtain available:true only in case all races that are to be restored
have finished loading. For this, set the <tt>waitUntilRacesLoaded</tt> parameter to <tt>true</tt>.</p>
<p>Furthermore, consider the "replica" attribute in the response. It may start
out as "false" although initial replication has been requested. See also the
<tt>/replication/replication?action=STATUS</tt> API which provide comprehensive
@@ -43,7 +45,9 @@ with the "replica" attribute, and if it is "false" check this endpoint.
</tr>
<tr>
<td>Optional parameters:</td>
<td>None</td>
<td>waitUntilRacesLoaded: if <tt>true</tt>, the overall status will be reported
as available only if all races that are to be restored have also finished
their loading process and moved to state TRACKING, FINISHED, or ERROR.</td>
</tr>
<tr>
<td>Request method:</td>
@@ -59,13 +63,57 @@ with the "replica" attribute, and if it is "false" check this endpoint.
<td>Example Response:</td>
<td>
<pre>
{
"numberofracestorestore": 12847,
"numberofracesrestored": 12847,
"isreplica": false,
"initialloadrunning": false,
"available": true
}
{
"numberofracestorestore": 48,
"numberofracesrestored": 48,
"numberofracesrestoreddoneloading": 48,
"numberofracesstillloading": 0,
"replication": {
"replica": false,
"replicationstarting": false,
"suspended": false,
"stopped": false,
"messagequeuelength": 0,
"operationqueuelengths": [],
"totaloperationqueuelength": 0,
"replicables": [
{
"id": "com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl",
"initialloadrunning": false
},
{
"id": "com.sap.sailing.shared.server.impl.SharedSailingDataImpl",
"initialloadrunning": false
},
{
"id": "com.sap.sse.mail.impl.MailServiceImpl",
"initialloadrunning": false
},
{
"id": "com.sap.sailing.domain.racelogtracking.impl.fixtracker.RegattaLogFixTrackerRegattaListener",
"initialloadrunning": false
},
{
"id": "com.sap.sailing.polars.impl.PolarDataServiceImpl",
"initialloadrunning": false
},
{
"id": "com.sap.sse.security.impl.SecurityServiceImpl",
"initialloadrunning": false
},
{
"id": "com.sap.sailing.windestimation.integration.WindEstimationFactoryServiceImpl",
"initialloadrunning": false
},
{
"id": "com.sap.sailing.server.impl.RacingEventServiceImpl",
"initialloadrunning": false
}
],
"available": true
},
"available": true
}
</pre>
</td>
</tr>
@@ -965,4 +965,15 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
void addOrReplaceExpeditionDeviceConfiguration(UUID deviceConfigurationId, String name, Integer expeditionBoatId);
void removeExpeditionDeviceConfiguration(UUID deviceUuid);
/**
* Returns the number of tracked races that are not {@link TrackedRace#hasFinishedLoading() done with loading}.
*/
int getNumberOfTrackedRacesStillLoading();
/**
* Returns the number of tracked races restored during server start-up that are
* {@link TrackedRace#hasFinishedLoading() done with loading}.
*/
int getNumberOfTrackedRacesRestoredDoneLoading();
}
@@ -438,7 +438,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
/**
* A set based on a concurrent hash map, therefore being thread safe
*/
private Set<DynamicTrackedRegatta> regattasObservedForDefaultLeaderboard = Collections
private Set<DynamicTrackedRegatta> regattasObservedWithRaceAdditionListener = Collections
.newSetFromMap(new ConcurrentHashMap<DynamicTrackedRegatta, Boolean>());
private final MongoObjectFactory mongoObjectFactory;
@@ -555,6 +555,10 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
private final AtomicInteger numberOfTrackedRacesRestored;
private final AtomicInteger numberOfTrackedRacesRestoredDoneLoading;
private final AtomicInteger numberOfTrackedRacesStillLoading;
private final ServiceTracker<ResultUrlRegistry, ResultUrlRegistry> resultUrlRegistryServiceTracker;
private final ServiceTracker<ScoreCorrectionProvider, ScoreCorrectionProvider> scoreCorrectionProviderServiceTracker;
@@ -804,6 +808,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
this.currentlyFillingFromInitialLoad = false;
this.securityServiceTracker = securityServiceTracker;
this.numberOfTrackedRacesRestored = new AtomicInteger();
this.numberOfTrackedRacesRestoredDoneLoading = new AtomicInteger();
this.numberOfTrackedRacesStillLoading = new AtomicInteger();
this.resultUrlRegistryServiceTracker = resultUrlRegistryServiceTracker;
this.scoreCorrectionProviderServiceTracker = scoreCorrectionProviderServiceTracker;
this.scoreCorrectionListenersByLeaderboard = new ConcurrentHashMap<>();
@@ -935,24 +941,26 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
numberOfTrackedRacesToRestore = getDomainObjectFactory().loadConnectivityParametersForRacesToRestore(params -> {
try {
final RaceHandle handle = addRace(/* addToRegatta==null means "default regatta" */ null, params, /* no timeout during mass loading */ -1,
new DefaultRaceTrackingHandler() {
@Override
public DynamicTrackedRace createTrackedRace(TrackedRegatta trackedRegatta,
RaceDefinition raceDefinition, Iterable<Sideline> sidelines, WindStore windStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
long millisecondsOverWhichToAverageSpeed,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate,
boolean useMarkPassingCalculator, RaceLogAndTrackedRaceResolver raceLogResolver,
Optional<ThreadLocalTransporter> threadLocalTransporter,
TrackingConnectorInfo trackingConnectorInfo) {
final DynamicTrackedRace trackedRace = super.createTrackedRace(trackedRegatta, raceDefinition, sidelines, windStore,
delayToLiveInMillis, millisecondsOverWhichToAverageWind,
millisecondsOverWhichToAverageSpeed, raceDefinitionSetToUpdate,
useMarkPassingCalculator, raceLogResolver, threadLocalTransporter, trackingConnectorInfo);
getSecurityService().migrateOwnership(trackedRace);
return trackedRace;
}
});
new DefaultRaceTrackingHandler() {
@Override
public DynamicTrackedRace createTrackedRace(TrackedRegatta trackedRegatta,
RaceDefinition raceDefinition, Iterable<Sideline> sidelines, WindStore windStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
long millisecondsOverWhichToAverageSpeed,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate,
boolean useMarkPassingCalculator, RaceLogAndTrackedRaceResolver raceLogResolver,
Optional<ThreadLocalTransporter> threadLocalTransporter,
TrackingConnectorInfo trackingConnectorInfo) {
final DynamicTrackedRace trackedRace = super.createTrackedRace(trackedRegatta, raceDefinition, sidelines, windStore,
delayToLiveInMillis, millisecondsOverWhichToAverageWind,
millisecondsOverWhichToAverageSpeed, raceDefinitionSetToUpdate,
useMarkPassingCalculator, raceLogResolver, threadLocalTransporter, trackingConnectorInfo);
getSecurityService().migrateOwnership(trackedRace);
trackedRace.runWhenDoneLoading(
()->numberOfTrackedRacesRestoredDoneLoading.incrementAndGet());
return trackedRace;
}
});
final RaceDefinition race = handle.getRace(RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS); // try to not flood servers during restore by waiting for race to appear
if (race == null) {
logger.warning("Race for tracker " + handle.getRaceTracker() + " with ID "
@@ -2066,9 +2074,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
trackingConnectorInfo);
}
private void ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(
DynamicTrackedRegatta trackedRegatta) {
if (regattasObservedForDefaultLeaderboard.add(trackedRegatta)) {
private void ensureRegattaHasRaceAdditionListener(DynamicTrackedRegatta trackedRegatta) {
if (regattasObservedWithRaceAdditionListener.add(trackedRegatta)) {
trackedRegatta.addRaceListener(new RaceAdditionListener(),
/* ThreadLocalTransporter */ Optional.empty(), // registering for synchronous callbacks; no thread locals need to be transported
/* register for synchronous execution in order to ensure that any replication-related effects happen before
@@ -2077,8 +2084,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
}
}
private void stopObservingRegattaForRedaultLeaderboardAndAutoLeaderboardLinking(DynamicTrackedRegatta trackedRegatta) {
regattasObservedForDefaultLeaderboard.remove(trackedRegatta);
private void stopObservingRegattaWithRaceAdditionListener(DynamicTrackedRegatta trackedRegatta) {
regattasObservedWithRaceAdditionListener.remove(trackedRegatta);
}
/**
@@ -2086,11 +2093,19 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
* service, the service adds the tracked race to the default leaderboard and links it to the leaderboard columns
* that were previously connected to it. Additionally, a {@link RaceChangeListener} is added to the
* {@link TrackedRace} which is responsible for triggering the replication of all relevant changes to the tracked
* race. When a tracked race is removed, the {@link TrackedRaceReplicatorAndNotifier} that was added as listener to that
* tracked race is removed again.
* race. When a tracked race is removed, the {@link TrackedRaceReplicatorAndNotifier} that was added as listener to
* that tracked race is removed again.
* <p>
*
* A {@link PolarFixCacheUpdater} is added to every race so that polar fixes are aggregated when new GPS fixes
* arrive.
* <p>
*
* Furthermore, the {@link RacingEventServiceImpl#numberOfTrackedRacesStillLoading} counter is adjusted based
* on the {@link TrackedRace#getStatus() race status}. If a tracked race is removed and it hasn't been fully loaded
* yet, the counter is decremented. If a new race is added, the counter is incremented and a function will be
* {@link TrackedRace#runWhenDoneLoading(Runnable) executed when the race is fully loaded} which will decrement
* it again.
*
* @author Axel Uhl (d043530)
*
@@ -2117,6 +2132,11 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
if (polarFixCacheUpdater != null) {
trackedRace.removeListener(polarFixCacheUpdater);
}
trackedRace.runSynchronizedOnStatus(()->{
if (!trackedRace.hasFinishedLoading()) {
numberOfTrackedRacesStillLoading.decrementAndGet();
}
});
}
@Override
@@ -2140,6 +2160,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
trackedRace.setWindEstimation(
windEstimationFactoryService.createIncrementalWindEstimationTrack(trackedRace));
}
numberOfTrackedRacesStillLoading.incrementAndGet();
trackedRace.runWhenDoneLoading(()->numberOfTrackedRacesStillLoading.decrementAndGet());
}
}
@@ -2955,7 +2977,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
result = new DynamicTrackedRegattaImpl(regatta);
replicate(new TrackRegatta(regatta.getRegattaIdentifier()));
regattaTrackingCache.put(regatta, result);
ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(result);
ensureRegattaHasRaceAdditionListener(result);
trackedRegattaListener.regattaAdded(result);
}
return result;
@@ -2979,7 +3001,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
} finally {
LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
}
stopObservingRegattaForRedaultLeaderboardAndAutoLeaderboardLinking(trackedRegatta);
stopObservingRegattaWithRaceAdditionListener(trackedRegatta);
trackedRegattaListener.regattaRemoved(trackedRegatta);
}
@@ -3294,7 +3316,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
}
logger.info("Serializing regattas observed...");
oos.writeObject(regattasObservedForDefaultLeaderboard);
oos.writeObject(regattasObservedWithRaceAdditionListener);
logger.info("Serializing regatta tracking cache...");
oos.writeObject(regattaTrackingCache);
logger.info("Serializing leaderboard groups...");
@@ -3380,7 +3402,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
// old leaderboards takes place which then don't match the new ones
logger.info("Reading all dynamic tracked regattas...");
for (DynamicTrackedRegatta trackedRegattaToObserve : (Set<DynamicTrackedRegatta>) ois.readObject()) {
ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(trackedRegattaToObserve);
ensureRegattaHasRaceAdditionListener(trackedRegattaToObserve);
}
logger.info("Reading all of the regatta tracking cache...");
@@ -3509,7 +3531,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
} finally {
LockUtil.unlockAfterWrite(regattasByNameLock);
}
regattasObservedForDefaultLeaderboard.clear();
regattasObservedWithRaceAdditionListener.clear();
if (raceTrackersByRegatta != null && !raceTrackersByRegatta.isEmpty()) {
for (DynamicTrackedRegatta regatta : regattaTrackingCache.values()) {
@@ -5125,4 +5147,14 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
}
}
@Override
public int getNumberOfTrackedRacesStillLoading() {
return numberOfTrackedRacesStillLoading.get();
}
@Override
public int getNumberOfTrackedRacesRestoredDoneLoading() {
return numberOfTrackedRacesRestoredDoneLoading.get();
}
}
@@ -18,7 +18,8 @@ java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# Overwrite the log leves for specific loggers here
com.sap.sailing.level = FINE
com.sap.sse.level = FINE
com.sap.sailing.domain.markpassingcalculation.level = FINEST
#com.sap.sailing.domain.markpassingcalculation.level = FINEST
com.sap.sailing.domain.markpassingcalculation.level = INFO
# log at level FINEST for TransientCompetitorStoreIMpl
#com.sap.sailing.domain.base.impl.TransientCompetitorStoreImpl.level = FINEST
#com.sap.sailing.domain.base.impl.SharedDomainFactoryImpl.level = FINEST