introduced reentrant read-write lock instead of synchronized for mark passing structures in TrackedRace

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