diff --git a/java/com.googlecode.java-diff-utils/src/difflib/Patch.java b/java/com.googlecode.java-diff-utils/src/difflib/Patch.java index 3966b18cf51..5941d58ccbe 100755 --- a/java/com.googlecode.java-diff-utils/src/difflib/Patch.java +++ b/java/com.googlecode.java-diff-utils/src/difflib/Patch.java @@ -43,6 +43,10 @@ public class Patch { return result; } + public boolean isEmpty() { + return deltas.isEmpty(); + } + /** * Apply this patch to the given target in-place, updating the target list * @return the patched text diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java index 82df756d1ad..a8a2b51654a 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java @@ -214,12 +214,15 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen TreeMap markPassingsByMarkIndex = new TreeMap(); // now fill with the already existing mark passings for the competitor identified by boatID... NavigableSet 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 markIndexRankAndTimeSinceStartInMilliseconds : markIndicesRanksAndTimesSinceStartInMilliseconds) { diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java index c170a8b4753..848e043fa9b 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java @@ -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 trackedRaces = replayListener.getTrackedRaces(); + assertFalse(Util.isEmpty(trackedRaces)); } @Test diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java index 074de6f79df..267a4931161 100755 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java @@ -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 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 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 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 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 controlPointWithWind : windAtControlPoint.entrySet()) { + Iterator> i = windAtControlPoint.entrySet().iterator(); + while (i.hasNext()) { + Entry 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 newMarkPassings = new ArrayList<>(); - Util.addAll(trackedRace.getMarkPassings(competitor), newMarkPassings); + final NavigableSet 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); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index b544c0452f0..aac340c3f29 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -688,4 +688,16 @@ public class MockedTrackedRace implements DynamicTrackedRace { // TODO Auto-generated method stub return false; } + + @Override + public void lockForRead(Iterable markPassings) { + // TODO Auto-generated method stub + + } + + @Override + public void unlockAfterRead(Iterable markPassings) { + // TODO Auto-generated method stub + + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index 04fdfdcd16b..04c4994e404 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -451,4 +451,16 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { // TODO Auto-generated method stub return false; } + + @Override + public void lockForRead(Iterable markPassings) { + // TODO Auto-generated method stub + + } + + @Override + public void unlockAfterRead(Iterable markPassings) { + // TODO Auto-generated method stub + + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java index 8cf7a53bccd..a0c841fa83d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java @@ -398,8 +398,11 @@ public class CourseImpl extends NamedImpl implements Course { newWaypointList.add(waypoint); } Patch 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); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index 20fc8cd6a68..8bf28a4d531 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -174,9 +174,10 @@ public interface TrackedRace extends Serializable { * that ends with waypoint. 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 getMarkPassingsInOrder(Waypoint waypoint); @@ -266,15 +267,19 @@ public interface TrackedRace extends Serializable { /** * @return the mark passings for competitor 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 getMarkPassings(Competitor competitor); + + void lockForRead(Iterable markPassings); + + void unlockAfterRead(Iterable markPassings); /** * Time stamp that the event received last from the underlying push service carried on it. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index aece3ebd4ea..3b7ba5607df 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -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 { @@ -377,14 +379,17 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements Map oldMarkPassings = new HashMap(); MarkPassing oldStartMarkPassing = null; boolean requiresStartTimeUpdate = true; - NavigableSet markPassingsForCompetitor = getMarkPassings(competitor); - synchronized (markPassingsForCompetitor) { + final NavigableSet 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 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) super.getMarkPassingsInOrder(waypoint); } + @Override + public void lockForRead(Iterable markPassings) { + LockUtil.lockForRead(getMarkPassingsLock(markPassings)); + } + + @Override + public void unlockAfterRead(Iterable markPassings) { + LockUtil.unlockAfterRead(getMarkPassingsLock(markPassings)); + } + private void clearMarkPassings(Competitor competitor) { NavigableSet markPassings = getMarkPassings(competitor); - synchronized (markPassings) { + final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassings); + LockUtil.lockForWrite(markPassingsLock); + try { Iterator mpIter = markPassings.iterator(); while (mpIter.hasNext()) { MarkPassing mp = mpIter.next(); mpIter.remove(); Collection markPassingsInOrder = getMarkPassingsInOrderAsNavigableSet(mp.getWaypoint()); - synchronized (markPassingsInOrder) { + LockUtil.lockForWrite(getMarkPassingsLock(markPassingsInOrder)); + try { markPassingsInOrder.remove(mp); + } finally { + LockUtil.unlockAfterWrite(getMarkPassingsLock(markPassingsInOrder)); } } + } finally { + LockUtil.unlockAfterWrite(markPassingsLock); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceRankComparator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceRankComparator.java index 72733c7d0e8..5732d8e9759 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceRankComparator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/RaceRankComparator.java @@ -60,7 +60,8 @@ public class RaceRankComparator implements Comparator { 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 { o1LastMarkPassingBeforeTimePoint = o1MarkPassingsBeforeTimePoint.last(); } o1Leg = trackedRace.getCurrentLeg(o1, timePoint); + } finally { + trackedRace.unlockAfterRead(o1MarkPassings); } NavigableSet o2MarkPassings = trackedRace.getMarkPassings(o2); NavigableSet 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 diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java index 0173d9ccb9d..9df992646ed 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java @@ -449,11 +449,14 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { Iterable markPassingsInOrder = getTrackedRace().getMarkPassingsInOrder(getLeg().getTo()); if (markPassingsInOrder != null) { MarkPassing firstMarkPassing = null; - synchronized (markPassingsInOrder) { + getTrackedRace().lockForRead(markPassingsInOrder); + try { Iterator markPassingsForLegEnd = markPassingsInOrder.iterator(); if (markPassingsForLegEnd.hasNext()) { firstMarkPassing = markPassingsForLegEnd.next(); } + } finally { + getTrackedRace().unlockAfterRead(markPassingsInOrder); } if (firstMarkPassing != null) { // someone has already finished the leg diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index cba7b3f7ff9..b77af012b6f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -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, 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 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 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 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 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 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 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 markPassings = getMarkPassings(competitor); double markPassingProximityConfidenceReduction = 1.0; - synchronized (markPassings) { + lockForRead(markPassings); + try { NavigableSet prevMarkPassing = markPassings.headSet( dummyMarkPassingForNow, /* inclusive */true); NavigableSet 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 bearing = new BearingWithConfidenceImpl( 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; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 1156c538c20..f69a735405e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -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 markPassingsForLegStart = trackedLeg.getTrackedLeg().getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getFrom()); - final Iterator 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 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 competitorMarkPassings = trackedRace.getMarkPassings(competitor); if (competitorMarkPassings != null) { - for (MarkPassing markPassing : competitorMarkPassings) { - MillisecondsTimePoint time = new MillisecondsTimePoint(markPassing.getTimePoint().asMillis()); - markPassingsData.add(new Triple(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(markPassing.getWaypoint().getName(), + time.asDate(), + getCompetitorRaceDataEntry(detailType, trackedRace, competitor, time))); + } + } finally { + trackedRace.unlockAfterRead(competitorMarkPassings); } } if (startTime != null && endTime != null) { diff --git a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaDataPerLeg.java b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaDataPerLeg.java index 8306b8c7510..30c7471bf96 100755 --- a/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaDataPerLeg.java +++ b/java/com.sap.sailing.xcelsiusadapter/src/com/sap/sailing/xcelsiusadapter/RegattaDataPerLeg.java @@ -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 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(