Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix real-time added patterns persistence with DIFFERENTIAL updates #5726

Merged
merged 21 commits into from
Jun 11, 2024

Conversation

optionsome
Copy link
Member

@optionsome optionsome commented Mar 4, 2024

Summary

There is an issue with "cancelling" a stop closure when using DIFFERENTIAL GTFS realtime trip updates. A realtime added trip pattern for the trip remains in a broken state after a follow up update to the trip.

This also contains minor refactoring.

Issue

Closes #5725

Unit tests

Added tests

Documentation

Not needed

Changelog

From title

@optionsome optionsome added Bug Real-Time Update The issue/PR is related to RealTime updates labels Mar 4, 2024
@optionsome optionsome added this to the 2.5 (next release) milestone Mar 4, 2024
@optionsome optionsome requested a review from a team as a code owner March 4, 2024 21:09
Copy link

codecov bot commented Mar 4, 2024

Codecov Report

Attention: Patch coverage is 77.19298% with 13 lines in your changes missing coverage. Please review.

Project coverage is 68.64%. Comparing base (06a7e8c) to head (bc06c14).
Report is 97 commits behind head on dev-2.x.

Files Patch % Lines
...a/org/opentripplanner/model/TimetableSnapshot.java 66.66% 2 Missing and 6 partials ⚠️
...pplanner/updater/trip/TimetableSnapshotSource.java 84.37% 2 Missing and 3 partials ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             dev-2.x    #5726      +/-   ##
=============================================
+ Coverage      68.54%   68.64%   +0.10%     
- Complexity     16732    16838     +106     
=============================================
  Files           1918     1927       +9     
  Lines          72734    72943     +209     
  Branches        7456     7481      +25     
=============================================
+ Hits           49854    50071     +217     
+ Misses         20310    20292      -18     
- Partials        2570     2580      +10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@leonardehrenfried leonardehrenfried self-requested a review March 5, 2024 10:34
@t2gran
Copy link
Member

t2gran commented Mar 8, 2024

@optionsome What is the effective change for the SiriTimetableSnapshotSource? Is the code just pushed "down" or is there actual changes. It is a bit hard to compare it.

@leonardehrenfried
Copy link
Member

I'm afraid that this logic is too brittle to go without unit tests documenting the use cases. I think we need to refactor to make unit tests possible.

// If this trip_id has been used for previously ADDED/MODIFIED trip message (e.g. when the
// sequence of stops has changed, and is now changing back to the originally scheduled one),
// mark that previously created trip as DELETED.
cancelPreviouslyAddedTrip(tripId, serviceDate, CancelationType.DELETE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you moving these call up because they happen for every scenario or is there another reason for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were two reasons, one of them was that I saw this was used in every scenario and the other was that I was unsure what would happen if I run the new code before this. However, I think moving this creates a minor regression as now the previously added trip is always deleted. However, I think previously if you sent a CANCELLED message on the trip, it was just cancelled, not removed. Do we want to keep that behavior?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So an ADDED trip was then CANCELLED again? If so, I'm quite relaxed about that but I would like to see tests documenting all these weird edge cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's just cancelling ADDDED trips, I think it doesn't matter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@t2gran do you know how cancellation of an added trip works in SIRI? Do we cancel or remove it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should cancel it. I have asked @lassetyr about this now, and in the future we want to be able to "cancel" any previously added update - DELETE in GTFS?

Summary of my thought about this - related to todays dev meeting discussion:

  • Trip updates is applied to the PLANED trips
    • not the previous updated trip(if it exist)
    • if the trip do not exist the update must create a new trip and set the appropriate flags. This mean in the future that we might need to support incomplete trips (trip update do not have a complete set of data).
  • To support scalability beyond the current demand, we might have to support incremental updates(incomplete trip data). If done, it needs to be combined with a "aggregator-service" witch keep track of the complete state and can serve the full-updates to OTP instance starting up, or resetting the RT state (periodically). For reference, look at the event-sourcing design pattern.

@optionsome
Copy link
Member Author

@optionsome What is the effective change for the SiriTimetableSnapshotSource? Is the code just pushed "down" or is there actual changes. It is a bit hard to compare it.

Currently the code is just pushed down. However, I'm still slightly evaluating what to do with realtime added trips so I might have to split some functionality.

@t2gran t2gran modified the milestones: 2.5, 2.6 (next release) Mar 13, 2024
@optionsome
Copy link
Member Author

@optionsome What is the effective change for the SiriTimetableSnapshotSource? Is the code just pushed "down" or is there actual changes. It is a bit hard to compare it.

Currently the code is just pushed down. However, I'm still slightly evaluating what to do with realtime added trips so I might have to split some functionality.

Didn't end up refactoring this more so the code is just pushed to the TimetableSnapshot, nothing really at least should have changed for the SIRI updater.

@leonardehrenfried leonardehrenfried changed the title Fix an issue with realtime added patterns persisting with DIFFERENTIAL updates Fixrealtime added patterns persistence with DIFFERENTIAL updates Mar 14, 2024
@leonardehrenfried leonardehrenfried changed the title Fixrealtime added patterns persistence with DIFFERENTIAL updates Fix real-time added patterns persistence with DIFFERENTIAL updates Mar 14, 2024
@leonardehrenfried leonardehrenfried requested review from abyrd and leonardehrenfried and removed request for t2gran March 14, 2024 14:27
@optionsome optionsome added the Digitransit Test Feature is under testing in Digitransit environment(s) label Mar 14, 2024
Comment on lines 313 to 324
case CANCELED -> handleCanceledTrip(
tripId,
serviceDate,
CancelationType.CANCEL,
canceledPreviouslyAddedTrip
);
case DELETED -> handleCanceledTrip(
tripId,
serviceDate,
CancelationType.DELETE,
canceledPreviouslyAddedTrip
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too happy about passing down a boolean to method which leads to an immediate return and I would like to handle it earlier.

In an ideal world Java would have a syntax like this:

case CANCELED if canceledPreviouslyAddedTrip -> Result.success(UpdateSuccess.noWarnings());

but it doesn't.

How about catching that case before the switch? So something like

if (CANCELLED or DELETED) and canceledPreviouslyAddedTrip return Result.success(UpdateSuccess.noWarnings());

Copy link
Member Author

@optionsome optionsome Mar 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I agree with you. However, the handleCanceledTrip is where you expect the handling of any valid case of a CANCELED/DELETED scheduled relationship to be.

@optionsome
Copy link
Member Author

I'm sorry, don't review this even though I requested review. I realized I've by mistake included a commit which I forgot to remove a week ago which explains a lot of confusion I've had today related to this pr... I'll try to properly fix this pr tomorrow hopefully.

@optionsome optionsome marked this pull request as draft June 3, 2024 18:09
@optionsome optionsome force-pushed the fix-skipped-removal branch from ce0eeaa to bdf7161 Compare June 4, 2024 07:58
@optionsome optionsome marked this pull request as ready for review June 4, 2024 07:58
@optionsome
Copy link
Member Author

I've hopefully now fixed the problems. I unfortunately had to force push due to accidentally leaving in a commit I shouldn't have previously.

@@ -870,16 +891,14 @@ private boolean cancelScheduledTrip(
* exist, and will be reused if a similar added/modified trip message is received with the same
* route and stop sequence.
*
* @param tripId trip id without agency id
* @param serviceDate service date
* @return true if a previously added trip was cancelled
*/
private boolean cancelPreviouslyAddedTrip(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of this method is slightly misleading.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've limited what this method does so the naming is more correct now. I think this method was now otherwise doing duplicate stuff with the new clean-up method I've introduced to the GTFS RT logic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now realize that 1cb32d9 commit title is unreadable but I don't think I'm going to fix it as we usually don't fix typos in commit titles 😅

@optionsome
Copy link
Member Author

@leonardehrenfried @abyrd this pr is now ready for review again.

return applyTripUpdates(List.of(update), false);
}

public UpdateResult applyTripUpdate(GtfsRealtime.TripUpdate update, boolean differential) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you convert the boolean to an enum making it clear what exactly that means? After some discussion in another PR we concluded that we want to change to an enum in the main code as well. That I will do in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid conflicts, maybe it's easier if this gets either updated in this pr or in your pr depending on what gets merged first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, lets do that.

Comment on lines 198 to 219
if (!fullDataset) {
// Check whether trip id has been used for previously ADDED trip message and mark previously
// created trip as DELETED unless schedule relationship is CANCELED, then as CANCEL
var cancelationType = tripScheduleRelationship ==
TripDescriptor.ScheduleRelationship.CANCELED
? CancelationType.CANCEL
: CancelationType.DELETE;
canceledPreviouslyAddedTrip =
cancelPreviouslyAddedTrip(tripId, serviceDate, cancelationType);
// Remove previous realtime updates for this trip. This is necessary to avoid previous
// stop pattern modifications from persisting. If a trip was previously added with the ScheduleRelationship
// ADDED and is now cancelled or deleted, we still want to keep the realtime added trip pattern.
if (
!canceledPreviouslyAddedTrip ||
(
tripScheduleRelationship != TripDescriptor.ScheduleRelationship.CANCELED &&
tripScheduleRelationship != TripDescriptor.ScheduleRelationship.DELETED
)
) {
this.buffer.revertTripToScheduledTripPattern(tripId, serviceDate);
}
}
Copy link
Member

@leonardehrenfried leonardehrenfried Jun 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this into a method, please?

Copy link
Member

@leonardehrenfried leonardehrenfried Jun 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, why don't you do all of this logic in handelCancelledTrip?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just don't want to see half the logic on one level of abstraction and the other half in another one.

@optionsome
Copy link
Member Author

This is now ready for review again. I realized there was a potential issue with determining if trip was added based on the original trip pattern because of the trip pattern cache so I changed the logic to check the realtime state instead. I didn't update #5726 (comment) yet as there can be potential naming issues with the enum and I don't want accidentally end up with duplicate enum.

Comment on lines +276 to +314
private void purgePatternModifications(
TripDescriptor.ScheduleRelationship tripScheduleRelationship,
FeedScopedId tripId,
LocalDate serviceDate
) {
final TripPattern pattern = buffer.getRealtimeAddedTripPattern(tripId, serviceDate);
if (
!isPreviouslyAddedTrip(tripId, pattern, serviceDate) ||
(
tripScheduleRelationship != TripDescriptor.ScheduleRelationship.CANCELED &&
tripScheduleRelationship != TripDescriptor.ScheduleRelationship.DELETED
)
) {
// Remove previous realtime updates for this trip. This is necessary to avoid previous
// stop pattern modifications from persisting. If a trip was previously added with the ScheduleRelationship
// ADDED and is now cancelled or deleted, we still want to keep the realtime added trip pattern.
this.buffer.revertTripToScheduledTripPattern(tripId, serviceDate);
}
}

private boolean isPreviouslyAddedTrip(
FeedScopedId tripId,
TripPattern pattern,
LocalDate serviceDate
) {
if (pattern == null) {
return false;
}
var timetable = buffer.resolve(pattern, serviceDate);
if (timetable == null) {
return false;
}
var tripTimes = timetable.getTripTimes(tripId);
if (tripTimes == null) {
return false;
}
return tripTimes.getRealTimeState() == RealTimeState.ADDED;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much nicer!

@optionsome optionsome merged commit 32bee57 into opentripplanner:dev-2.x Jun 11, 2024
5 checks passed
@optionsome optionsome deleted the fix-skipped-removal branch June 11, 2024 07:07
t2gran pushed a commit that referenced this pull request Jun 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Digitransit Test Feature is under testing in Digitransit environment(s) Real-Time Update The issue/PR is related to RealTime updates
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Realtime added patterns for a trip persist with DIFFERENTIAL updates even after a new update
4 participants