2
2
3
3
import java .time .LocalDate ;
4
4
import java .util .Objects ;
5
- import java .util .concurrent .locks .ReentrantLock ;
6
5
import java .util .function .Supplier ;
7
6
import javax .annotation .Nullable ;
8
- import org .opentripplanner .framework .time .CountdownTimer ;
9
7
import org .opentripplanner .model .Timetable ;
10
8
import org .opentripplanner .model .TimetableSnapshot ;
11
9
import org .opentripplanner .routing .algorithm .raptoradapter .transit .mappers .TransitLayerUpdater ;
10
+ import org .opentripplanner .routing .util .ConcurrentPublished ;
12
11
import org .opentripplanner .transit .model .framework .FeedScopedId ;
13
12
import org .opentripplanner .transit .model .framework .Result ;
14
13
import org .opentripplanner .transit .model .network .TripPattern ;
@@ -30,36 +29,18 @@ public final class TimetableSnapshotManager {
30
29
31
30
private static final Logger LOG = LoggerFactory .getLogger (TimetableSnapshotManager .class );
32
31
private final TransitLayerUpdater transitLayerUpdater ;
33
- /**
34
- * Lock to indicate that buffer is in use
35
- */
36
- private final ReentrantLock bufferLock = new ReentrantLock (true );
37
32
38
33
/**
39
- * The working copy of the timetable snapshot. Should not be visible to routing threads. Should
40
- * only be modified by a thread that holds a lock on {@link #bufferLock}. All public methods that
41
- * might modify this buffer will correctly acquire the lock. By design, only one thread should
42
- * ever be writing to this buffer.
43
- * TODO RT_AB: research and document why this lock is needed since only one thread should ever be
44
- * writing to this buffer. One possible reason may be a need to suspend writes while indexing
45
- * and swapping out the buffer. But the original idea was to make a new copy of the buffer
46
- * before re-indexing it. While refactoring or rewriting parts of this system, we could throw
47
- * an exception if a writing section is entered by more than one thread.
34
+ * The working copy of the timetable snapshot. Should not be visible to routing threads.
35
+ * By design, only one thread should ever be writing to this buffer.
48
36
*/
49
37
private final TimetableSnapshot buffer = new TimetableSnapshot ();
50
38
51
39
/**
52
40
* The last committed snapshot that was handed off to a routing thread. This snapshot may be given
53
- * to more than one routing thread if the maximum snapshot frequency is exceeded.
54
- */
55
- private volatile TimetableSnapshot snapshot = null ;
56
-
57
- /**
58
- * If a timetable snapshot is requested less than this number of milliseconds after the previous
59
- * snapshot, just return the same one. Throttles the potentially resource-consuming task of
60
- * duplicating a TripPattern -> Timetable map and indexing the new Timetables.
41
+ * to more than one routing thread.
61
42
*/
62
- private final CountdownTimer snapshotFrequencyThrottle ;
43
+ private final ConcurrentPublished < TimetableSnapshot > snapshot = new ConcurrentPublished <>() ;
63
44
64
45
/**
65
46
* Should expired real-time data be purged from the graph.
@@ -85,7 +66,6 @@ public TimetableSnapshotManager(
85
66
Supplier <LocalDate > localDateNow
86
67
) {
87
68
this .transitLayerUpdater = transitLayerUpdater ;
88
- this .snapshotFrequencyThrottle = new CountdownTimer (parameters .maxSnapshotFrequency ());
89
69
this .purgeExpiredData = parameters .purgeExpiredData ();
90
70
this .localDateNow = Objects .requireNonNull (localDateNow );
91
71
// Force commit so that snapshot initializes
@@ -99,19 +79,17 @@ public TimetableSnapshotManager(
99
79
* to the snapshot to release resources.
100
80
*/
101
81
public TimetableSnapshot getTimetableSnapshot () {
102
- // Try to get a lock on the buffer
103
- if (bufferLock .tryLock ()) {
104
- // Make a new snapshot if necessary
105
- try {
106
- commitTimetableSnapshot (false );
107
- return snapshot ;
108
- } finally {
109
- bufferLock .unlock ();
110
- }
111
- }
112
- // No lock could be obtained because there is either a snapshot commit busy or updates
113
- // are applied at this moment, just return the current snapshot
114
- return snapshot ;
82
+ return snapshot .get ();
83
+ }
84
+
85
+ /**
86
+ * @return the current timetable snapshot buffer that contains pending changes (not yet published
87
+ * in a snapshot).
88
+ * This should be used in the context of an updater to build a TransitEditorService that sees all
89
+ * the changes applied so far by real-time updates.
90
+ */
91
+ public TimetableSnapshot getTimetableSnapshotBuffer () {
92
+ return buffer ;
115
93
}
116
94
117
95
/**
@@ -122,21 +100,12 @@ public TimetableSnapshot getTimetableSnapshot() {
122
100
*
123
101
* @param force Force the committing of a new snapshot even if the above conditions are not met.
124
102
*/
125
- public void commitTimetableSnapshot (final boolean force ) {
126
- if (force || snapshotFrequencyThrottle .timeIsUp ()) {
127
- if (force || buffer .isDirty ()) {
128
- LOG .debug ("Committing {}" , buffer );
129
- snapshot = buffer .commit (transitLayerUpdater , force );
130
-
131
- // We only reset the timer when the snapshot is updated. This will cause the first
132
- // update to be committed after a silent period. This should not have any effect in
133
- // a busy updater. It is however useful when manually testing the updater.
134
- snapshotFrequencyThrottle .restart ();
135
- } else {
136
- LOG .debug ("Buffer was unchanged, keeping old snapshot." );
137
- }
103
+ void commitTimetableSnapshot (final boolean force ) {
104
+ if (force || buffer .isDirty ()) {
105
+ LOG .debug ("Committing {}" , buffer );
106
+ snapshot .publish (buffer .commit (transitLayerUpdater , force ));
138
107
} else {
139
- LOG .debug ("Snapshot frequency exceeded. Reusing snapshot {}" , snapshot );
108
+ LOG .debug ("Buffer was unchanged, keeping old snapshot." );
140
109
}
141
110
}
142
111
@@ -205,22 +174,6 @@ private boolean purgeExpiredData() {
205
174
return buffer .purgeExpiredData (previously );
206
175
}
207
176
208
- /**
209
- * Execute a {@code Runnable} with a locked snapshot buffer and release the lock afterwards. While
210
- * the action of locking and unlocking is not complicated to do for calling code, this method
211
- * exists so that the lock instance is a private field.
212
- */
213
- public void withLock (Runnable action ) {
214
- bufferLock .lock ();
215
-
216
- try {
217
- action .run ();
218
- } finally {
219
- // Always release lock
220
- bufferLock .unlock ();
221
- }
222
- }
223
-
224
177
/**
225
178
* Clear all data of snapshot for the provided feed id
226
179
*/
0 commit comments