mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-04 03:13:49 +00:00
introduced reentrant read-write lock instead of synchronized for mark passing structures in TrackedRace
This commit is contained in:
1 parent
19e9948c3c
commit
21c477426a
14 files changed
+233
-63
No files matched your search
@@ -43,6 +43,10 @@ public class Patch<T> {
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return deltas.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply this patch to the given target in-place, updating the <code>target</code> list
|
||||
* @return the patched text
|
||||
|
||||
+4
-1
@@ -214,12 +214,15 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
TreeMap<Integer, MarkPassing> markPassingsByMarkIndex = new TreeMap<Integer, MarkPassing>();
|
||||
// now fill with the already existing mark passings for the competitor identified by boatID...
|
||||
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
||||
synchronized (markPassings) {
|
||||
trackedRace.lockForRead(markPassings);
|
||||
try {
|
||||
for (MarkPassing markPassing : markPassings) {
|
||||
markPassingsByMarkIndex.put(
|
||||
trackedRace.getRace().getCourse().getIndexOfWaypoint(markPassing.getWaypoint()),
|
||||
markPassing);
|
||||
}
|
||||
} finally {
|
||||
trackedRace.unlockAfterRead(markPassings);
|
||||
}
|
||||
// ...and then overwrite those for which we received "new evidence"
|
||||
for (Triple<Integer, Integer, Long> markIndexRankAndTimeSinceStartInMilliseconds : markIndicesRanksAndTimesSinceStartInMilliseconds) {
|
||||
|
||||
+5
-1
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.domain.swisstimingreplayadapter.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
@@ -8,12 +9,14 @@ import java.util.List;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.swisstimingadapter.DomainFactory;
|
||||
import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayRace;
|
||||
import com.sap.sailing.domain.swisstimingreplayadapter.impl.SwissTimingReplayParserImpl;
|
||||
import com.sap.sailing.domain.swisstimingreplayadapter.impl.SwissTimingReplayServiceImpl;
|
||||
import com.sap.sailing.domain.swisstimingreplayadapter.impl.SwissTimingReplayToDomainAdapter;
|
||||
import com.sap.sailing.domain.test.DummyTrackedRegattaRegistry;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
|
||||
public class SwissTimingReplayAdapterServiceTest {
|
||||
|
||||
@@ -84,7 +87,8 @@ public class SwissTimingReplayAdapterServiceTest {
|
||||
SwissTimingReplayToDomainAdapter replayListener = new SwissTimingReplayToDomainAdapter(null, DomainFactory.INSTANCE,
|
||||
new DummyTrackedRegattaRegistry());
|
||||
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAW005906.20120805.replay"), replayListener);
|
||||
|
||||
Iterable<? extends TrackedRace> trackedRaces = replayListener.getTrackedRaces();
|
||||
assertFalse(Util.isEmpty(trackedRaces));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+24
-9
@@ -5,8 +5,11 @@ import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -140,6 +143,10 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
|
||||
this.domainFactory = domainFactory;
|
||||
}
|
||||
|
||||
public Iterable<? extends TrackedRace> getTrackedRaces() {
|
||||
return trackedRacePerRaceID.values();
|
||||
}
|
||||
|
||||
private BoatClass getCurrentBoatClass() {
|
||||
return domainFactory.getOrCreateBoatClassFromRaceID(currentRaceID);
|
||||
}
|
||||
@@ -201,7 +208,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
|
||||
CompetitorStatus competitorStatus, BoatType boatType, short cRank_Bracket, short cnPoints_x10_Bracket,
|
||||
short ctPoints_x10_Winner) {
|
||||
if (boatType == BoatType.Competitor) {
|
||||
Competitor competitor = domainFactory.getOrCreateCompetitor(sailNumberOrTrackerID, threeLetterIOCCode, name,
|
||||
Competitor competitor = domainFactory.getOrCreateCompetitor(sailNumberOrTrackerID, threeLetterIOCCode.trim(), name.trim(),
|
||||
getCurrentBoatClass());
|
||||
Set<Competitor> competitorsOfCurrentRace = competitorsPerRaceID.get(currentRaceID);
|
||||
if (competitorsOfCurrentRace == null) {
|
||||
@@ -212,13 +219,13 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
|
||||
competitorByHashValue.put(hashValue, competitor);
|
||||
} else {
|
||||
// consider it a mark
|
||||
Mark mark = domainFactory.getOrCreateMark(sailNumberOrTrackerID);
|
||||
Mark mark = domainFactory.getOrCreateMark(sailNumberOrTrackerID.trim());
|
||||
Map<String, Mark> marksOfCurrentRace = marksPerRaceIDPerMarkID.get(currentRaceID);
|
||||
if (marksOfCurrentRace == null) {
|
||||
marksOfCurrentRace = new HashMap<>();
|
||||
marksPerRaceIDPerMarkID.put(currentRaceID, marksOfCurrentRace);
|
||||
}
|
||||
marksOfCurrentRace.put(sailNumberOrTrackerID, mark);
|
||||
marksOfCurrentRace.put(sailNumberOrTrackerID.trim(), mark);
|
||||
markByHashValue.put(hashValue, mark);
|
||||
}
|
||||
}
|
||||
@@ -228,9 +235,9 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
|
||||
public void mark(MarkType markType, String name, byte index, String id1, String id2, short windSpeedInKnots,
|
||||
short trueWindDirectionInDegrees) {
|
||||
final List<String> markNames = new ArrayList<>();
|
||||
markNames.add(id1);
|
||||
markNames.add(id1.trim());
|
||||
if (id2 != null) {
|
||||
markNames.add(id2);
|
||||
markNames.add(id2.trim());
|
||||
}
|
||||
final ControlPoint controlPoint = domainFactory.getOrCreateControlPoint(markNames);
|
||||
if (index == 0) {
|
||||
@@ -270,8 +277,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
|
||||
|
||||
private void createRace() {
|
||||
final Regatta myRegatta = regatta != null ? regatta : domainFactory.getOrCreateRegatta(currentRaceID, trackedRegattaRegistry);
|
||||
RaceDefinition race = domainFactory.createRaceDefinition(
|
||||
myRegatta,
|
||||
RaceDefinition race = domainFactory.createRaceDefinition(myRegatta,
|
||||
currentRaceID, competitorsPerRaceID.get(currentRaceID), currentCourseDefinition);
|
||||
racePerRaceID.put(currentRaceID, race);
|
||||
DynamicTrackedRace trackedRace = trackedRegattaRegistry.getOrCreateTrackedRegatta(myRegatta).
|
||||
@@ -299,10 +305,13 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
|
||||
GPSFixMoving fix = new GPSFixMovingImpl(position, raceTimePoint, speed);
|
||||
Mark mark = markByHashValue.get(hashValue);
|
||||
if (mark != null) {
|
||||
for (Map.Entry<ControlPoint, SpeedWithBearing> controlPointWithWind : windAtControlPoint.entrySet()) {
|
||||
Iterator<Entry<ControlPoint, SpeedWithBearing>> i = windAtControlPoint.entrySet().iterator();
|
||||
while (i.hasNext()) {
|
||||
Entry<ControlPoint, SpeedWithBearing> controlPointWithWind = i.next();
|
||||
if (Util.contains(controlPointWithWind.getKey().getMarks(), mark)) {
|
||||
trackedRace.recordWind(new WindImpl(position, raceTimePoint, controlPointWithWind.getValue()),
|
||||
new WindSourceWithAdditionalID(WindSourceType.EXPEDITION, mark.getName()));
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
trackedRace.recordFix(mark, fix);
|
||||
@@ -314,7 +323,13 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
|
||||
if ((!lastNextMark.containsKey(competitor) || lastNextMark.get(competitor) != nextMark) && nextMark > 0) {
|
||||
Waypoint waypointPassed = nextMark == 255 ? course.getLastWaypoint() : Util.get(course.getWaypoints(), nextMark - 1);
|
||||
List<MarkPassing> newMarkPassings = new ArrayList<>();
|
||||
Util.addAll(trackedRace.getMarkPassings(competitor), newMarkPassings);
|
||||
final NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
||||
trackedRace.lockForRead(markPassings);
|
||||
try {
|
||||
Util.addAll(markPassings, newMarkPassings);
|
||||
} finally {
|
||||
trackedRace.unlockAfterRead(markPassings);
|
||||
}
|
||||
newMarkPassings.add(new MarkPassingImpl(raceTimePoint, waypointPassed, competitor));
|
||||
trackedRace.updateMarkPassings(competitor, newMarkPassings);
|
||||
lastNextMark.put(competitor, nextMark);
|
||||
|
||||
+12
@@ -688,4 +688,16 @@ public class MockedTrackedRace implements DynamicTrackedRace {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockForRead(Iterable<MarkPassing> markPassings) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlockAfterRead(Iterable<MarkPassing> markPassings) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
+12
@@ -451,4 +451,16 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockForRead(Iterable<MarkPassing> markPassings) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlockAfterRead(Iterable<MarkPassing> markPassings) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
@@ -398,8 +398,11 @@ public class CourseImpl extends NamedImpl implements Course {
|
||||
newWaypointList.add(waypoint);
|
||||
}
|
||||
Patch<Waypoint> patch = DiffUtils.diff(courseWaypoints, newWaypointList);
|
||||
CourseAsWaypointList courseAsWaypointList = new CourseAsWaypointList(this);
|
||||
patch.applyToInPlace(courseAsWaypointList);
|
||||
if (!patch.isEmpty()) {
|
||||
logger.info("applying course update " + patch + " to course " + this);
|
||||
CourseAsWaypointList courseAsWaypointList = new CourseAsWaypointList(this);
|
||||
patch.applyToInPlace(courseAsWaypointList);
|
||||
}
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(lock);
|
||||
}
|
||||
|
||||
@@ -174,9 +174,10 @@ public interface TrackedRace extends Serializable {
|
||||
* that ends with <code>waypoint</code>. The remaining competitors need to be ordered by the advantage line-related
|
||||
* distance to the waypoint.
|
||||
*
|
||||
* @return the iterable sequence of {@link MarkPassing}s as described above. To iterate on the resulting collection
|
||||
* the caller needs to synchronize on the iterable returned because insertions into the underlying
|
||||
* collection will also synchronize on that collection.
|
||||
* @return the iterable sequence of {@link MarkPassing}s as described above. If the caller wants to iterate on the
|
||||
* resulting collection, the caller needs to invoke {@link #lockForRead(Iterable)} with the collection
|
||||
* returned as parameter because insertions into the competitor's mark passing collection will obtain the
|
||||
* corresponding write lock.
|
||||
*/
|
||||
Iterable<MarkPassing> getMarkPassingsInOrder(Waypoint waypoint);
|
||||
|
||||
@@ -266,16 +267,20 @@ public interface TrackedRace extends Serializable {
|
||||
|
||||
/**
|
||||
* @return the mark passings for <code>competitor</code> in this race received so far; the mark passing objects are
|
||||
* returned such that their {@link MarkPassing#getWaypoint() waypoints} are ordered in the same way they are ordered
|
||||
* in the race's {@link Course}. Note, that this doesn't necessarily guarantee ascending time points, particularly
|
||||
* if premature mark passings have been detected accidentally as can be the case with some tracking providers such
|
||||
* as TracTrac. If the caller wants to iterate on the resulting collection or construct a {@link SortedSet#headSet(Object)}
|
||||
* or {@link SortedSet#tailSet(Object)} and then iterate over that, the caller needs to synchronize on the
|
||||
* collection returned because insertions into the competitor's mark passing collection will also synchronize
|
||||
* on that collection.
|
||||
* returned such that their {@link MarkPassing#getWaypoint() waypoints} are ordered in the same way they are
|
||||
* ordered in the race's {@link Course}. Note, that this doesn't necessarily guarantee ascending time
|
||||
* points, particularly if premature mark passings have been detected accidentally as can be the case with
|
||||
* some tracking providers such as TracTrac. If the caller wants to iterate on the resulting collection or
|
||||
* construct a {@link SortedSet#headSet(Object)} or {@link SortedSet#tailSet(Object)} and then iterate over
|
||||
* that, the caller needs to invoke {@link #lockForRead(Iterable)} with the collection returned as parameter
|
||||
* because insertions into the competitor's mark passing collection will obtain the corresponding write lock.
|
||||
*/
|
||||
NavigableSet<MarkPassing> getMarkPassings(Competitor competitor);
|
||||
|
||||
void lockForRead(Iterable<MarkPassing> markPassings);
|
||||
|
||||
void unlockAfterRead(Iterable<MarkPassing> markPassings);
|
||||
|
||||
/**
|
||||
* Time stamp that the event received last from the underlying push service carried on it.
|
||||
* Note that these times may not increase monotonically.
|
||||
|
||||
+37
-7
@@ -14,9 +14,9 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
@@ -38,6 +38,8 @@ import com.sap.sailing.domain.tracking.TrackedRegatta;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
import com.sap.sailing.domain.tracking.WindStore;
|
||||
import com.sap.sailing.domain.tracking.WindTrack;
|
||||
import com.sap.sailing.util.impl.LockUtil;
|
||||
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
|
||||
|
||||
public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements
|
||||
DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
@@ -377,14 +379,17 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements
|
||||
Map<Waypoint, MarkPassing> oldMarkPassings = new HashMap<Waypoint, MarkPassing>();
|
||||
MarkPassing oldStartMarkPassing = null;
|
||||
boolean requiresStartTimeUpdate = true;
|
||||
NavigableSet<MarkPassing> markPassingsForCompetitor = getMarkPassings(competitor);
|
||||
synchronized (markPassingsForCompetitor) {
|
||||
final NavigableSet<MarkPassing> markPassingsForCompetitor = getMarkPassings(competitor);
|
||||
lockForRead(markPassingsForCompetitor);
|
||||
try {
|
||||
for (MarkPassing oldMarkPassing : markPassingsForCompetitor) {
|
||||
if (oldStartMarkPassing == null) {
|
||||
oldStartMarkPassing = oldMarkPassing;
|
||||
}
|
||||
oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing);
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(markPassingsForCompetitor);
|
||||
}
|
||||
clearMarkPassings(competitor);
|
||||
TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0);
|
||||
@@ -397,7 +402,9 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements
|
||||
requiresStartTimeUpdate = false;
|
||||
}
|
||||
}
|
||||
synchronized (markPassingsForCompetitor) {
|
||||
final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor);
|
||||
LockUtil.lockForWrite(markPassingsLock);
|
||||
try {
|
||||
if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) {
|
||||
StringBuilder courseWaypointsWithID = new StringBuilder();
|
||||
boolean first = true;
|
||||
@@ -418,11 +425,17 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements
|
||||
} else {
|
||||
markPassingsForCompetitor.add(markPassing);
|
||||
}
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(markPassingsLock);
|
||||
}
|
||||
Collection<MarkPassing> markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing
|
||||
.getWaypoint());
|
||||
synchronized (markPassingsInOrderForWaypoint) {
|
||||
final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint);
|
||||
LockUtil.lockForWrite(markPassingsLock2);
|
||||
try {
|
||||
markPassingsInOrderForWaypoint.add(markPassing);
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(markPassingsLock2);
|
||||
}
|
||||
if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) {
|
||||
timePointOfLatestEvent = markPassing.getTimePoint();
|
||||
@@ -446,18 +459,35 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements
|
||||
return (NavigableSet<MarkPassing>) super.getMarkPassingsInOrder(waypoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockForRead(Iterable<MarkPassing> markPassings) {
|
||||
LockUtil.lockForRead(getMarkPassingsLock(markPassings));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlockAfterRead(Iterable<MarkPassing> markPassings) {
|
||||
LockUtil.unlockAfterRead(getMarkPassingsLock(markPassings));
|
||||
}
|
||||
|
||||
private void clearMarkPassings(Competitor competitor) {
|
||||
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
|
||||
synchronized (markPassings) {
|
||||
final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassings);
|
||||
LockUtil.lockForWrite(markPassingsLock);
|
||||
try {
|
||||
Iterator<MarkPassing> mpIter = markPassings.iterator();
|
||||
while (mpIter.hasNext()) {
|
||||
MarkPassing mp = mpIter.next();
|
||||
mpIter.remove();
|
||||
Collection<MarkPassing> markPassingsInOrder = getMarkPassingsInOrderAsNavigableSet(mp.getWaypoint());
|
||||
synchronized (markPassingsInOrder) {
|
||||
LockUtil.lockForWrite(getMarkPassingsLock(markPassingsInOrder));
|
||||
try {
|
||||
markPassingsInOrder.remove(mp);
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(getMarkPassingsLock(markPassingsInOrder));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(markPassingsLock);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -60,7 +60,8 @@ public class RaceRankComparator implements Comparator<Competitor> {
|
||||
MarkPassing o1LastMarkPassingBeforeTimePoint = null;
|
||||
int o1MarkPassingsBeforeTimePointSize;
|
||||
TrackedLegOfCompetitor o1Leg;
|
||||
synchronized (o1MarkPassings) {
|
||||
trackedRace.lockForRead(o1MarkPassings);
|
||||
try {
|
||||
o1MarkPassingsBeforeTimePoint = o1MarkPassings.headSet(
|
||||
markPassingWithTimePoint, /* inclusive */true);
|
||||
o1MarkPassingsBeforeTimePointSize = o1MarkPassingsBeforeTimePoint.size();
|
||||
@@ -68,19 +69,24 @@ public class RaceRankComparator implements Comparator<Competitor> {
|
||||
o1LastMarkPassingBeforeTimePoint = o1MarkPassingsBeforeTimePoint.last();
|
||||
}
|
||||
o1Leg = trackedRace.getCurrentLeg(o1, timePoint);
|
||||
} finally {
|
||||
trackedRace.unlockAfterRead(o1MarkPassings);
|
||||
}
|
||||
NavigableSet<MarkPassing> o2MarkPassings = trackedRace.getMarkPassings(o2);
|
||||
NavigableSet<MarkPassing> o2MarkPassingsBeforeTimePoint;
|
||||
MarkPassing o2LastMarkPassingBeforeTimePoint = null;
|
||||
int o2MarkPassingsBeforeTimePointSize;
|
||||
TrackedLegOfCompetitor o2Leg;
|
||||
synchronized (o2MarkPassings) {
|
||||
trackedRace.lockForRead(o2MarkPassings);
|
||||
try {
|
||||
o2MarkPassingsBeforeTimePoint = o2MarkPassings.headSet(markPassingWithTimePoint, /* inclusive */true);
|
||||
o2MarkPassingsBeforeTimePointSize = o2MarkPassingsBeforeTimePoint.size();
|
||||
if (o2MarkPassingsBeforeTimePointSize > 0) {
|
||||
o2LastMarkPassingBeforeTimePoint = o2MarkPassingsBeforeTimePoint.last();
|
||||
}
|
||||
o2Leg = trackedRace.getCurrentLeg(o2, timePoint);
|
||||
} finally {
|
||||
trackedRace.unlockAfterRead(o2MarkPassings);
|
||||
}
|
||||
result = o2MarkPassingsBeforeTimePointSize - o1MarkPassingsBeforeTimePointSize; // inverted: more legs means
|
||||
// smaller rank
|
||||
|
||||
+4
-1
@@ -449,11 +449,14 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
Iterable<MarkPassing> markPassingsInOrder = getTrackedRace().getMarkPassingsInOrder(getLeg().getTo());
|
||||
if (markPassingsInOrder != null) {
|
||||
MarkPassing firstMarkPassing = null;
|
||||
synchronized (markPassingsInOrder) {
|
||||
getTrackedRace().lockForRead(markPassingsInOrder);
|
||||
try {
|
||||
Iterator<MarkPassing> markPassingsForLegEnd = markPassingsInOrder.iterator();
|
||||
if (markPassingsForLegEnd.hasNext()) {
|
||||
firstMarkPassing = markPassingsForLegEnd.next();
|
||||
}
|
||||
} finally {
|
||||
getTrackedRace().unlockAfterRead(markPassingsInOrder);
|
||||
}
|
||||
if (firstMarkPassing != null) {
|
||||
// someone has already finished the leg
|
||||
|
||||
+67
-11
@@ -9,6 +9,7 @@ import java.util.ConcurrentModificationException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
@@ -237,10 +238,13 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
*/
|
||||
private final NamedReentrantReadWriteLock serializationLock;
|
||||
|
||||
private final Map<Iterable<MarkPassing>, NamedReentrantReadWriteLock> locksForMarkPassings;
|
||||
|
||||
public TrackedRaceImpl(final TrackedRegatta trackedRegatta, RaceDefinition race, final WindStore windStore,
|
||||
long delayToLiveInMillis, final long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
|
||||
long delayForWindEstimationCacheInvalidation) {
|
||||
super();
|
||||
locksForMarkPassings = new IdentityHashMap<>();
|
||||
this.serializationLock = new NamedReentrantReadWriteLock("Serialization lock for tracked race "+race.getName(), /* fair */ true);
|
||||
this.cacheInvalidationTimerLock = new Object();
|
||||
this.updateCount = 0;
|
||||
@@ -497,10 +501,13 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
if (lastWaypoint != null) {
|
||||
Iterable<MarkPassing> markPassingsInOrder = getMarkPassingsInOrder(lastWaypoint);
|
||||
if (markPassingsInOrder != null) {
|
||||
synchronized (markPassingsInOrder) {
|
||||
lockForRead(markPassingsInOrder);
|
||||
try {
|
||||
for (MarkPassing passingFinishLine : markPassingsInOrder) {
|
||||
passingTime = passingFinishLine.getTimePoint();
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(markPassingsInOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -511,10 +518,13 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
NavigableSet<MarkPassing> markPassingsInOrder = getMarkPassingsInOrderAsNavigableSet(waypoint);
|
||||
MarkPassing firstMarkPassing = null;
|
||||
if (markPassingsInOrder != null) {
|
||||
synchronized (markPassingsInOrder) {
|
||||
lockForRead(markPassingsInOrder);
|
||||
try {
|
||||
if (!markPassingsInOrder.isEmpty()) {
|
||||
firstMarkPassing = markPassingsInOrder.first();
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(markPassingsInOrder);
|
||||
}
|
||||
}
|
||||
TimePoint timeOfFirstMarkPassing = null;
|
||||
@@ -529,7 +539,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
TimePoint startOfRace = null;
|
||||
// Find the first mark passing within the largest cluster crossing the line within one minute.
|
||||
final long ONE_MINUTE_IN_MILLIS = 60 * 1000;
|
||||
synchronized (markPassings) {
|
||||
lockForRead(markPassings);
|
||||
try {
|
||||
if (markPassings != null) {
|
||||
int largestStartGroupWithinOneMinuteSize = 0;
|
||||
MarkPassing startOfLargestGroupSoFar = null;
|
||||
@@ -577,6 +588,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
startOfRace = startOfLargestGroupSoFar == null ? null : startOfLargestGroupSoFar.getTimePoint();
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(markPassings);
|
||||
}
|
||||
return startOfRace;
|
||||
}
|
||||
@@ -629,7 +642,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
// read-out;
|
||||
// the results of getMarkPassingsInOrder(to) has by definition an ascending time-point
|
||||
// ordering
|
||||
synchronized (markPassings) {
|
||||
lockForRead(markPassings);
|
||||
try {
|
||||
for (MarkPassing currentMarkPassing : markPassings) {
|
||||
Date currentPassingDate = currentMarkPassing.getTimePoint().asDate();
|
||||
if (previousLegPassingTime == null
|
||||
@@ -639,6 +653,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(markPassings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -716,12 +732,16 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
TrackedLegOfCompetitor result = null;
|
||||
if (roundings != null) {
|
||||
TrackedLeg trackedLeg;
|
||||
synchronized (roundings) {
|
||||
// obtain last waypoint before obtaining mark passings monitor because obtaining the last waypoint
|
||||
// obtains the read lock for the course
|
||||
final Waypoint lastWaypoint = getRace().getCourse().getLastWaypoint();
|
||||
lockForRead(roundings);
|
||||
try {
|
||||
MarkPassing lastBeforeOrAt = roundings.floor(new DummyMarkPassingWithTimePointOnly(at));
|
||||
// already finished the race?
|
||||
if (lastBeforeOrAt != null) {
|
||||
// and not at or after last mark passing
|
||||
if (getRace().getCourse().getLastWaypoint() != lastBeforeOrAt.getWaypoint()) {
|
||||
if (lastWaypoint != lastBeforeOrAt.getWaypoint()) {
|
||||
trackedLeg = getTrackedLegStartingAt(lastBeforeOrAt.getWaypoint());
|
||||
} else {
|
||||
// exactly *at* last mark passing?
|
||||
@@ -737,6 +757,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
// before beginning of race
|
||||
trackedLeg = null;
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(roundings);
|
||||
}
|
||||
if (trackedLeg != null) {
|
||||
result = trackedLeg.getTrackedLeg(competitor);
|
||||
@@ -868,10 +890,13 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
public Distance getAverageCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException {
|
||||
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
|
||||
TimePoint from = null;
|
||||
synchronized (markPassings) {
|
||||
lockForRead(markPassings);
|
||||
try {
|
||||
if (markPassings != null && !markPassings.isEmpty()) {
|
||||
from = markPassings.iterator().next().getTimePoint();
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(markPassings);
|
||||
}
|
||||
Distance result;
|
||||
if (from != null) {
|
||||
@@ -942,12 +967,15 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
public MarkPassing getMarkPassing(Competitor competitor, Waypoint waypoint) {
|
||||
final NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
|
||||
if (markPassings != null) {
|
||||
synchronized (markPassings) {
|
||||
lockForRead(markPassings);
|
||||
try {
|
||||
for (MarkPassing markPassing : markPassings) {
|
||||
if (markPassing.getWaypoint() == waypoint) {
|
||||
return markPassing;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(markPassings);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -1181,6 +1209,12 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
}
|
||||
if (newFuture != null) {
|
||||
// FIXME locking problem: this method is sometimes called by code holding the course's read lock.
|
||||
// If a writer is waiting for the write lock, further read locks aren't granted due to the "fair" locking policy.
|
||||
// To finish the future, a read lock on the course needs to be obtained in the getLegs() call. This isn't granted
|
||||
// until the writer times out and tries again. Moving the future execution into a thread doesn't help because the
|
||||
// future.get() below will again block until the future has finished which can't happen because the writer cannot
|
||||
// obtain the write lock, and the new read lock isn't granted.
|
||||
newFuture.run();
|
||||
future = newFuture;
|
||||
}
|
||||
@@ -1412,7 +1446,12 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
for (NavigableSet<MarkPassing> markPassingsForOneCompetitor : markPassingsForCompetitor.values()) {
|
||||
if (!markPassingsForOneCompetitor.isEmpty()) {
|
||||
final Competitor competitor = markPassingsForOneCompetitor.iterator().next().getCompetitor();
|
||||
markPassingsForOneCompetitor.removeAll(markPassingsRemoved);
|
||||
LockUtil.lockForWrite(getMarkPassingsLock(markPassingsForOneCompetitor));
|
||||
try {
|
||||
markPassingsForOneCompetitor.removeAll(markPassingsRemoved);
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(getMarkPassingsLock(markPassingsForOneCompetitor));
|
||||
}
|
||||
triggerManeuverCacheRecalculation(competitor);
|
||||
}
|
||||
}
|
||||
@@ -1421,6 +1460,17 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
}
|
||||
|
||||
protected NamedReentrantReadWriteLock getMarkPassingsLock(Iterable<MarkPassing> markPassings) {
|
||||
synchronized (locksForMarkPassings) {
|
||||
NamedReentrantReadWriteLock lock = locksForMarkPassings.get(markPassings);
|
||||
if (lock == null) {
|
||||
lock = new NamedReentrantReadWriteLock("mark passings lock for "+markPassings, /* fair */ false);
|
||||
locksForMarkPassings.put(markPassings, lock);
|
||||
}
|
||||
return lock;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointRemoved(int zeroBasedIndex,
|
||||
Waypoint waypointThatGotRemoved) {
|
||||
if (zeroBasedIndex < 2) {
|
||||
@@ -1552,7 +1602,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
// passings:
|
||||
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
|
||||
double markPassingProximityConfidenceReduction = 1.0;
|
||||
synchronized (markPassings) {
|
||||
lockForRead(markPassings);
|
||||
try {
|
||||
NavigableSet<MarkPassing> prevMarkPassing = markPassings.headSet(
|
||||
dummyMarkPassingForNow, /* inclusive */true);
|
||||
NavigableSet<MarkPassing> nextMarkPassing = markPassings.tailSet(
|
||||
@@ -1567,6 +1618,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
1.0 - weigherForMarkPassingProximity.getConfidence(nextMarkPassing
|
||||
.first().getTimePoint(), timePoint));
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(markPassings);
|
||||
}
|
||||
BearingWithConfidence<TimePoint> bearing = new BearingWithConfidenceImpl<TimePoint>(
|
||||
estimatedSpeedWithConfidence.getObject() == null ? null
|
||||
@@ -1693,12 +1746,15 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
MarkPassing crossedFinishLine = null;
|
||||
// getLastWaypoint() will wait for a read lock on the course; do this outside the synchronized block to avoid deadlocks
|
||||
final Waypoint lastWaypoint = getRace().getCourse().getLastWaypoint();
|
||||
synchronized (markPassings) {
|
||||
lockForRead(markPassings);
|
||||
try {
|
||||
markPassingsNotEmpty = markPassings != null && !markPassings.isEmpty();
|
||||
if (markPassingsNotEmpty) {
|
||||
extendedFrom = markPassings.iterator().next().getTimePoint();
|
||||
crossedFinishLine = getMarkPassing(competitor, lastWaypoint);
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead(markPassings);
|
||||
}
|
||||
if (markPassingsNotEmpty) {
|
||||
TimePoint extendedTo;
|
||||
|
||||
+25
-14
@@ -929,19 +929,24 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
if (indexOfStartWaypoint == 0) {
|
||||
// trackedLeg was the first leg; gap is determined by gap of start line passing time points
|
||||
Iterable<MarkPassing> markPassingsForLegStart = trackedLeg.getTrackedLeg().getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getFrom());
|
||||
final Iterator<MarkPassing> markPassingsIter = markPassingsForLegStart.iterator();
|
||||
if (markPassingsIter.hasNext()) {
|
||||
TimePoint firstStart = markPassingsIter.next().getTimePoint();
|
||||
final MarkPassing markPassingForFrom = trackedLeg.getTrackedLeg().getTrackedRace().
|
||||
getMarkPassing(trackedLeg.getCompetitor(), trackedLeg.getLeg().getFrom());
|
||||
if (markPassingForFrom != null) {
|
||||
TimePoint competitorStart = markPassingForFrom.getTimePoint();
|
||||
result = (double) (competitorStart.asMillis() - firstStart.asMillis()) / 1000.;
|
||||
trackedLeg.getTrackedLeg().getTrackedRace().lockForRead(markPassingsForLegStart);
|
||||
try {
|
||||
final Iterator<MarkPassing> markPassingsIter = markPassingsForLegStart.iterator();
|
||||
if (markPassingsIter.hasNext()) {
|
||||
TimePoint firstStart = markPassingsIter.next().getTimePoint();
|
||||
final MarkPassing markPassingForFrom = trackedLeg.getTrackedLeg().getTrackedRace()
|
||||
.getMarkPassing(trackedLeg.getCompetitor(), trackedLeg.getLeg().getFrom());
|
||||
if (markPassingForFrom != null) {
|
||||
TimePoint competitorStart = markPassingForFrom.getTimePoint();
|
||||
result = (double) (competitorStart.asMillis() - firstStart.asMillis()) / 1000.;
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
} else {
|
||||
result = null;
|
||||
} finally {
|
||||
trackedLeg.getTrackedLeg().getTrackedRace().unlockAfterRead(markPassingsForLegStart);
|
||||
}
|
||||
} else {
|
||||
TrackedLeg previousTrackedLeg = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLeg(course.getLegs().get(indexOfStartWaypoint-1));
|
||||
@@ -2438,10 +2443,16 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
// Filling the mark passings
|
||||
Set<MarkPassing> competitorMarkPassings = trackedRace.getMarkPassings(competitor);
|
||||
if (competitorMarkPassings != null) {
|
||||
for (MarkPassing markPassing : competitorMarkPassings) {
|
||||
MillisecondsTimePoint time = new MillisecondsTimePoint(markPassing.getTimePoint().asMillis());
|
||||
markPassingsData.add(new Triple<String, Date, Double>(markPassing.getWaypoint().getName(), time
|
||||
.asDate(), getCompetitorRaceDataEntry(detailType, trackedRace, competitor, time)));
|
||||
trackedRace.lockForRead(competitorMarkPassings);
|
||||
try {
|
||||
for (MarkPassing markPassing : competitorMarkPassings) {
|
||||
MillisecondsTimePoint time = new MillisecondsTimePoint(markPassing.getTimePoint().asMillis());
|
||||
markPassingsData.add(new Triple<String, Date, Double>(markPassing.getWaypoint().getName(),
|
||||
time.asDate(),
|
||||
getCompetitorRaceDataEntry(detailType, trackedRace, competitor, time)));
|
||||
}
|
||||
} finally {
|
||||
trackedRace.unlockAfterRead(competitorMarkPassings);
|
||||
}
|
||||
}
|
||||
if (startTime != null && endTime != null) {
|
||||
|
||||
+11
-5
@@ -5,6 +5,7 @@ import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -177,13 +178,18 @@ public class RegattaDataPerLeg extends Action {
|
||||
TimePoint compareLegEnd = new MillisecondsTimePoint(0);
|
||||
|
||||
// time elapsed / when did the competitor pass the end mark of the leg
|
||||
for (MarkPassing mp : trackedRace.getMarkPassings(competitor)) {
|
||||
if (mp.getWaypoint() == leg.getTo()) {
|
||||
compareLegEnd = mp.getTimePoint();
|
||||
break;
|
||||
final NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
||||
trackedRace.lockForRead(markPassings);
|
||||
try {
|
||||
for (MarkPassing mp : markPassings) {
|
||||
if (mp.getWaypoint() == leg.getTo()) {
|
||||
compareLegEnd = mp.getTimePoint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
trackedRace.unlockAfterRead(markPassings);
|
||||
}
|
||||
|
||||
// leg time / based on elapsed time
|
||||
if (legTimesAlternate.containsKey(competitor.getName())) {
|
||||
legTimesAlternate.put(
|
||||
|
||||
Reference in new issue
Block a user