Hi all
Iām currently working on this ticket (TRUNK-4253). I have certainly done some work and came up with a fix . But I uncertainly got blocked by something small
.
What the issue is about
While saving a Patient in PatientService.savePatient(), two/more concurrently executing threads can ably save a Patient with duplicate patient identifiers. This is not a right behavior and should be fixed.I was able to reproduce this and came up with an ideal solution.
Work arounds on this issue attained
I came up with some fixes in the PatientService.savePatient().@Override
@Transactional(isolation = Isolation.SERIALIZABLE)
public Patient savePatient(Patient patient) throws APIException {
synchronized(this) {
requireAppropriatePatientModificationPrivilege(patient);
if (!patient.getVoided() && patient.getIdentifiers().size() == 1) {
patient.getPatientIdentifier().setPreferred(true);
}
if (!patient.getVoided()) {
checkPatientIdentifiers(patient);
}
setPreferredPatientIdentifier(patient);
setPreferredPatientName(patient);
setPreferredPatientAddress(patient);
return dao.savePatient(patient);
}
}
And the above works fine.
What is blocking me
I came up with some unit tests that reproduce the bug, then made the above changes that fix the bug. I came up with a class variable static AtomicBoolean isSavePatientThreadsafe which is false by default and only made true when org.openmrs.api.IdentifierNotUniqueException is caught. Am sure this exception is thrown when the second Thread tries to save a patient of duplicate Identifier. But I wonder why my Assertion fails!@Test
public void savePatient_shouldFailSaveIfPatientsWithSameIdentifierAreSaved() {
isSavePatientThreadsafe = new AtomicBoolean(false);
UserContext ctx = Context.getUserContext();
Patient patient1 = new Patient();
patient1.setGender("M");
patient1.addName(new PersonName("Test1", "Sam", "Test"));
Patient patient2 = new Patient();
patient2.setGender("M");
patient2.addName(new PersonName("Test2", "Samuel", "Test"));
Runnable r1 = () -> savePatientOnThread(ctx, patient1);
Runnable r2 = () -> savePatientOnThread(ctx, patient2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
assertTrue(isSavePatientThreadsafe.get()); //Why does this Assertion fail!
}
public void savePatientOnThread(UserContext ctx, Patient patient) {
Context.setUserContext(ctx);
Context.openSessionWithCurrentUser();
authenticate();
PatientIdentifier patientIdentifier = new PatientIdentifier("103-9", new PatientIdentifierType(1), new Location(1));
patientIdentifier.setPreferred(true);
patient.addIdentifier(patientIdentifier);
try {
patientService.savePatient(patient);
}
catch(org.openmrs.api.IdentifierNotUniqueException e) {
isSavePatientThreadsafe.set(true);
}
Context.closeSessionWithCurrentUser();
}
You can get details in the PR .
Thanks in advance guys
CC: @dkayiwa , @mksd , @mogoodrich , @wyclif etc