Skip to content

Commit 67757de

Browse files
authored
Merge pull request opentripplanner#7253 from HSLdevcom/clear-patterns-for-stop
Fix memory leak in `TimetableSnapshot` related to `patternsForStop` and `realTimeAddedReplacedByTripOnServiceDateById`
2 parents b75d7b4 + bb99fec commit 67757de

2 files changed

Lines changed: 94 additions & 5 deletions

File tree

application/src/main/java/org/opentripplanner/transit/model/timetable/TimetableSnapshot.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ public TimetableSnapshot() {
175175
);
176176
}
177177

178-
private TimetableSnapshot(
178+
TimetableSnapshot(
179179
Map<FeedScopedId, SortedSet<Timetable>> timetables,
180180
Map<TripIdAndServiceDate, TripPattern> realTimeNewTripPatternsForModifiedTrips,
181181
Map<FeedScopedId, Route> realtimeAddedRoutes,
@@ -428,11 +428,13 @@ public void clear(String feedId) {
428428
feedId
429429
);
430430
boolean addedTripPatternsWereCleared = clearEntriesForRealtimeAddedTrips(feedId);
431+
boolean patternsForStopWereCleared = clearPatternsForStop(feedId);
431432
// If this snapshot was modified, it will be dirty after the clear actions.
432433
if (
433434
timetablesWereCleared ||
434435
newTripPatternsForModifiedTripsWereCleared ||
435-
addedTripPatternsWereCleared
436+
addedTripPatternsWereCleared ||
437+
patternsForStopWereCleared
436438
) {
437439
dirty = true;
438440
}
@@ -568,7 +570,15 @@ public boolean isEmpty() {
568570
return (
569571
dirtyTimetables.isEmpty() &&
570572
timetables.isEmpty() &&
571-
realTimeNewTripPatternsForModifiedTrips.isEmpty()
573+
realTimeNewTripPatternsForModifiedTrips.isEmpty() &&
574+
patternsForStop.isEmpty() &&
575+
realtimeAddedRoutes.isEmpty() &&
576+
realTimeAddedTrips.isEmpty() &&
577+
realTimeAddedPatternForTrip.isEmpty() &&
578+
realTimeAddedPatternsForRoute.isEmpty() &&
579+
realTimeAddedTripOnServiceDateById.isEmpty() &&
580+
realTimeAddedReplacedByTripOnServiceDateById.isEmpty() &&
581+
realTimeAddedTripOnServiceDateForTripAndDay.isEmpty()
572582
);
573583
}
574584

@@ -615,7 +625,9 @@ private boolean clearNewTripPatternsForModifiedTrips(String feedId) {
615625
/**
616626
* Clear all realtime added routes, trip patterns and trips matching the provided feed id.
617627
*
618-
* */
628+
* @param feedId feed id to clear out
629+
* @return true if realTimeAddedTrips changed as a result of the call
630+
*/
619631
private boolean clearEntriesForRealtimeAddedTrips(String feedId) {
620632
// it is sufficient to test for the removal of added trips, since other indexed entities are
621633
// added only if a new trip is added.
@@ -625,15 +637,28 @@ private boolean clearEntriesForRealtimeAddedTrips(String feedId) {
625637
realTimeAddedPatternForTrip.keySet().removeIf(trip -> feedId.equals(trip.getId().getFeedId()));
626638
realTimeAddedTripOnServiceDateForTripAndDay
627639
.keySet()
628-
.removeIf(tripOnServiceDate -> feedId.equals(tripOnServiceDate.tripId().getFeedId()));
640+
.removeIf(tripIdAndServiceDate -> feedId.equals(tripIdAndServiceDate.tripId().getFeedId()));
629641
realTimeAddedTripOnServiceDateById.keySet().removeIf(id -> feedId.equals(id.getFeedId()));
630642
realTimeAddedPatternsForRoute
631643
.keySet()
632644
.removeIf(route -> feedId.equals(route.getId().getFeedId()));
633645
realtimeAddedRoutes.keySet().removeIf(id -> feedId.equals(id.getFeedId()));
646+
realTimeAddedReplacedByTripOnServiceDateById
647+
.keySet()
648+
.removeIf(id -> feedId.equals(id.getFeedId()));
634649
return removedEntry;
635650
}
636651

652+
/**
653+
* Clear all trip patterns from patternsForStop matching the provided feed id.
654+
*
655+
* @param feedId feed id to clear out
656+
* @return true if patternsForStop changed as a result of the call
657+
*/
658+
private boolean clearPatternsForStop(String feedId) {
659+
return patternsForStop.values().removeIf(tripPattern -> feedId.equals(tripPattern.getFeedId()));
660+
}
661+
637662
/**
638663
* Add the patterns to the stop index, only if they come from a real-time pattern.
639664
*/

application/src/test/java/org/opentripplanner/transit/model/timetable/TimetableSnapshotTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
import static org.junit.jupiter.api.Assertions.assertThrows;
88
import static org.junit.jupiter.api.Assertions.assertTrue;
99

10+
import com.google.common.collect.ArrayListMultimap;
11+
import com.google.common.collect.HashMultimap;
12+
import com.google.common.collect.ImmutableSortedSet;
13+
import com.google.common.collect.ListMultimap;
14+
import com.google.common.collect.Multimap;
15+
import com.google.common.collect.SetMultimap;
1016
import java.time.LocalDate;
1117
import java.time.ZoneId;
1218
import java.util.Collection;
@@ -24,8 +30,13 @@
2430
import org.opentripplanner._support.time.ZoneIds;
2531
import org.opentripplanner.core.model.id.FeedScopedId;
2632
import org.opentripplanner.model.StopTime;
33+
import org.opentripplanner.transit.model._data.TimetableRepositoryForTest;
2734
import org.opentripplanner.transit.model.framework.Deduplicator;
35+
import org.opentripplanner.transit.model.network.Route;
2836
import org.opentripplanner.transit.model.network.TripPattern;
37+
import org.opentripplanner.transit.model.site.RegularStop;
38+
import org.opentripplanner.transit.model.site.StopLocation;
39+
import org.opentripplanner.transit.model.site.TestStopLocation;
2940
import org.opentripplanner.transit.service.TimetableRepository;
3041

3142
public class TimetableSnapshotTest {
@@ -169,6 +180,59 @@ void testClear() {
169180
assertNotNull(snapshot.getRealtimeAddedRoute(pattern.getRoute().getId()));
170181
}
171182

183+
@Test
184+
void testClearWithAllCollections() {
185+
TimetableRepositoryForTest TEST_MODEL = TimetableRepositoryForTest.of();
186+
RegularStop STOP_A = TEST_MODEL.stop("A").build();
187+
RegularStop STOP_B = TEST_MODEL.stop("B").build();
188+
189+
FeedScopedId id = new FeedScopedId(feedId, "1.2");
190+
TripIdAndServiceDate tripIdAndServiceDate = new TripIdAndServiceDate(id, SERVICE_DATE);
191+
TripOnServiceDate tripOnServiceDate = TripOnServiceDate.of(
192+
new FeedScopedId(feedId, "triponservicedateid")
193+
).build();
194+
TestStopLocation testStopLocation = new TestStopLocation(
195+
new FeedScopedId(feedId, "stoplocationid")
196+
);
197+
Route route = TimetableRepositoryForTest.route(new FeedScopedId(feedId, "routeId")).build();
198+
Trip trip = TimetableRepositoryForTest.trip(feedId, "tripId").build();
199+
TripPattern tripPattern = TripPattern.of(new FeedScopedId(feedId, "tripPatternId"))
200+
.withRoute(route)
201+
.withStopPattern(TimetableRepositoryForTest.stopPattern(STOP_A, STOP_B))
202+
.withScheduledTimeTableBuilder(builder ->
203+
builder.addTripTimes(
204+
ScheduledTripTimes.of().withTrip(trip).withDepartureTimes(new int[] { 0, 1 }).build()
205+
)
206+
)
207+
.build();
208+
209+
Multimap<Route, TripPattern> realTimeAddedPatternsForRoute = ArrayListMultimap.create();
210+
realTimeAddedPatternsForRoute.put(route, tripPattern);
211+
ListMultimap<FeedScopedId, TripOnServiceDate> realTimeAddedReplacedByTripOnServiceDateById =
212+
ArrayListMultimap.create();
213+
realTimeAddedReplacedByTripOnServiceDateById.put(id, tripOnServiceDate);
214+
SetMultimap<StopLocation, TripPattern> patternsForStop = HashMultimap.create();
215+
patternsForStop.put(testStopLocation, tripPattern);
216+
217+
// The entries do not necessarily make sense, they are only for testing the clear method.
218+
TimetableSnapshot snapshot = new TimetableSnapshot(
219+
new HashMap<>(Map.of(id, ImmutableSortedSet.of())),
220+
new HashMap<>(Map.of(tripIdAndServiceDate, tripPattern)),
221+
new HashMap<>(Map.of(id, route)),
222+
new HashMap<>(Map.of(id, trip)),
223+
new HashMap<>(Map.of(trip, tripPattern)),
224+
realTimeAddedPatternsForRoute,
225+
new HashMap<>(Map.of(id, tripOnServiceDate)),
226+
realTimeAddedReplacedByTripOnServiceDateById,
227+
new HashMap<>(Map.of(tripIdAndServiceDate, tripOnServiceDate)),
228+
patternsForStop,
229+
false
230+
);
231+
assertFalse(snapshot.isEmpty());
232+
snapshot.clear(id.getFeedId());
233+
assertTrue(snapshot.isEmpty());
234+
}
235+
172236
private static RealTimeTripUpdate createRealTimeTripUpdate(TripPattern pattern, Trip trip) {
173237
TripTimes updatedTriptimes = TripTimesFactory.tripTimes(
174238
trip,

0 commit comments

Comments
 (0)