Skip to content

Commit ef4b649

Browse files
committed
dvrp: fix non-deterministic PersonStuckEvent due to rejection
Due to race conditions, DefaultPassengerEngine may get notified about a rejection before, during or after its doSimStep() finishes. This means the rejection may be processed in the current or next time step. To overcome this non-determinism, all rejections from the current time step are processed in the next time step.
1 parent 39362b1 commit ef4b649

File tree

2 files changed

+30
-30
lines changed

2 files changed

+30
-30
lines changed

contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngine.java

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,15 @@ public final class DefaultPassengerEngine implements PassengerEngine, PassengerR
6868

6969
//accessed in doSimStep() and handleDeparture() (no need to sync)
7070
private final Map<Id<Request>, MobsimPassengerAgent> activePassengers = new HashMap<>();
71-
71+
7272
// holds vehicle stop activities for requests that have not arrived at departure point yet
7373
private final Map<Id<Request>, PassengerPickupActivity> waitingForPassenger = new HashMap<>();
7474

7575
//accessed in doSimStep() and handleEvent() (potential data races)
7676
private final Queue<PassengerRequestRejectedEvent> rejectedRequestsEvents = new ConcurrentLinkedQueue<>();
7777

78-
DefaultPassengerEngine(String mode, EventsManager eventsManager, MobsimTimer mobsimTimer,
79-
PassengerRequestCreator requestCreator, VrpOptimizer optimizer, Network network,
80-
PassengerRequestValidator requestValidator, AdvanceRequestProvider advanceRequestProvider) {
78+
DefaultPassengerEngine(String mode, EventsManager eventsManager, MobsimTimer mobsimTimer, PassengerRequestCreator requestCreator,
79+
VrpOptimizer optimizer, Network network, PassengerRequestValidator requestValidator, AdvanceRequestProvider advanceRequestProvider) {
8180
this.mode = mode;
8281
this.mobsimTimer = mobsimTimer;
8382
this.requestCreator = requestCreator;
@@ -106,17 +105,22 @@ public void doSimStep(double time) {
106105
// event) after submission, but before departure, the PassengerEngine does not
107106
// know this agent yet. Hence, we wait with setting the state to abort until the
108107
// agent has arrived here (if ever).
109-
108+
110109
Iterator<PassengerRequestRejectedEvent> iterator = rejectedRequestsEvents.iterator();
111-
110+
112111
while (iterator.hasNext()) {
113112
PassengerRequestRejectedEvent event = iterator.next();
114-
MobsimPassengerAgent passenger = activePassengers.remove(event.getRequestId());
113+
if (event.getTime() == time) {
114+
// There is a potential race condition wrt processing rejection events between doSimStep() and handleEvent().
115+
// To ensure a deterministic behaviour, we only process events from the previous time step.
116+
break;
117+
}
115118

119+
MobsimPassengerAgent passenger = activePassengers.remove(event.getRequestId());
116120
if (passenger != null) {
117121
// not much else can be done for immediate requests
118122
// set the passenger agent to abort - the event will be thrown by the QSim
119-
passenger.setStateToAbort(mobsimTimer.getTimeOfDay());
123+
passenger.setStateToAbort(time);
120124
internalInterface.arrangeNextAgentState(passenger);
121125
iterator.remove();
122126
}
@@ -137,20 +141,20 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id<Link> fromLinkI
137141
internalInterface.registerAdditionalAgentOnLink(passenger);
138142

139143
Id<Link> toLinkId = passenger.getDestinationLinkId();
140-
144+
141145
// try to find a prebooked requests that is associated to this leg
142-
Leg leg = (Leg) ((PlanAgent) passenger).getCurrentPlanElement();
146+
Leg leg = (Leg)((PlanAgent)passenger).getCurrentPlanElement();
143147
PassengerRequest request = advanceRequestProvider.retrieveRequest(agent, leg);
144-
148+
145149
if (request == null) { // immediate request
146150
Route route = ((Leg)((PlanAgent)passenger).getCurrentPlanElement()).getRoute();
147-
request = requestCreator.createRequest(internalPassengerHandling.createRequestId(), passenger.getId(),
148-
route, getLink(fromLinkId), getLink(toLinkId), now, now);
151+
request = requestCreator.createRequest(internalPassengerHandling.createRequestId(), passenger.getId(), route, getLink(fromLinkId),
152+
getLink(toLinkId), now, now);
149153

150154
// must come before validateAndSubmitRequest (to come before rejection event)
151155
eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), request.getPassengerId()));
152156
activePassengers.put(request.getId(), passenger);
153-
157+
154158
validateAndSubmitRequest(passenger, request, now);
155159
} else { // advance request
156160
eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), request.getPassengerId()));
@@ -162,7 +166,7 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id<Link> fromLinkI
162166
pickupActivity.notifyPassengerIsReadyForDeparture(passenger, now);
163167
}
164168
}
165-
169+
166170
return true;
167171
}
168172

@@ -182,21 +186,20 @@ private void validateAndSubmitRequest(MobsimPassengerAgent passenger, PassengerR
182186

183187
private Link getLink(Id<Link> linkId) {
184188
return Preconditions.checkNotNull(network.getLinks().get(linkId),
185-
"Link id=%s does not exist in network for mode %s. Agent departs from a link that does not belong to that network?",
186-
linkId, mode);
189+
"Link id=%s does not exist in network for mode %s. Agent departs from a link that does not belong to that network?", linkId, mode);
187190
}
188191

189192
/**
190193
* There are two ways of interacting with the PassengerEngine:
191-
*
194+
* <p>
192195
* - (1) The stop activity tries to pick up a passenger and receives whether the
193196
* pickup succeeded or not (see tryPickUpPassenger). In the classic
194197
* implementation, the vehicle only calls tryPickUpPassenger at the time when it
195198
* actually wants to pick up the person (at the end of the activity). It may
196199
* happen that the person is not present yet. In that case, the pickup request
197200
* is saved and notifyPassengerReady is called on the stop activity upen
198201
* departure of the agent.
199-
*
202+
* <p>
200203
* - (2) If pickup and dropoff times are handled more flexibly by the stop
201204
* activity, it might want to detect whether an agent is ready to be picked up,
202205
* then start an "interaction time" and only after perform the actual pickup.
@@ -210,15 +213,13 @@ public boolean notifyWaitForPassenger(PassengerPickupActivity pickupActivity, Mo
210213
waitingForPassenger.put(requestId, pickupActivity);
211214
return false;
212215
}
213-
216+
214217
return true;
215218
}
216219

217220
@Override
218-
public boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver,
219-
Id<Request> requestId, double now) {
220-
return internalPassengerHandling.tryPickUpPassenger(driver, activePassengers.get(requestId),
221-
requestId, now);
221+
public boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id<Request> requestId, double now) {
222+
return internalPassengerHandling.tryPickUpPassenger(driver, activePassengers.get(requestId), requestId, now);
222223
}
223224

224225
@Override
@@ -243,10 +244,9 @@ public static Provider<PassengerEngine> createProvider(String mode) {
243244

244245
@Override
245246
public DefaultPassengerEngine get() {
246-
return new DefaultPassengerEngine(getMode(), eventsManager, mobsimTimer,
247-
getModalInstance(PassengerRequestCreator.class), getModalInstance(VrpOptimizer.class),
248-
getModalInstance(Network.class), getModalInstance(PassengerRequestValidator.class),
249-
getModalInstance(AdvanceRequestProvider.class));
247+
return new DefaultPassengerEngine(getMode(), eventsManager, mobsimTimer, getModalInstance(PassengerRequestCreator.class),
248+
getModalInstance(VrpOptimizer.class), getModalInstance(Network.class), getModalInstance(PassengerRequestValidator.class),
249+
getModalInstance(AdvanceRequestProvider.class));
250250
}
251251
};
252252
}

contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngineTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public void test_invalid_rejected() {
124124
new PersonDepartureEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE),
125125
new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID),
126126
new PassengerRequestRejectedEvent(0, MODE, requestId, fixture.PERSON_ID, "invalid"),
127-
new PersonStuckEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE));
127+
new PersonStuckEvent(1, fixture.PERSON_ID, fixture.linkAB.getId(), MODE));
128128
}
129129

130130
@Test
@@ -140,7 +140,7 @@ public void test_valid_rejected() {
140140
new PersonDepartureEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE),
141141
new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID),
142142
new PassengerRequestRejectedEvent(0, MODE, requestId, fixture.PERSON_ID, "rejecting_all_requests"),
143-
new PersonStuckEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE));
143+
new PersonStuckEvent(1, fixture.PERSON_ID, fixture.linkAB.getId(), MODE));
144144
}
145145

146146
private static class RejectingOneTaxiOptimizer implements VrpOptimizer {

0 commit comments

Comments
 (0)