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;
}
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
@@ -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) {
@@ -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
@@ -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;
@@ -139,6 +142,10 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
lastNextMark = new HashMap<>();
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);
@@ -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
}
}
@@ -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,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
* 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.
@@ -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);
}
}
@@ -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
@@ -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
@@ -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;
@@ -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) {
@@ -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(