Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions api/src/main/java/org/openmrs/api/impl/OrderServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -656,8 +656,9 @@ public Order getRevisionOrder(Order order) throws APIException {
* @param orderContext
*/
@Override
public String getNewOrderNumber(OrderContext orderContext) throws APIException {
return ORDER_NUMBER_PREFIX + Context.getOrderService().getNextOrderNumberSeedSequenceValue();
@Transactional(propagation = Propagation.REQUIRES_NEW)
public synchronized String getNewOrderNumber(OrderContext orderContext) throws APIException {
return ORDER_NUMBER_PREFIX + dao.getNextOrderNumberSeedSequenceValue();
}

/**
Expand Down
70 changes: 44 additions & 26 deletions api/src/test/java/org/openmrs/api/OrderServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

import org.openmrs.Allergy;
import org.openmrs.AllergyReaction;
Expand Down Expand Up @@ -133,6 +134,7 @@
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.concurrent.*;
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the reference @dkayiwa.
I’ve replaced the wildcard java.util.concurrent.* import with explicit imports for only the required classes, in line with the coding conventions. All tests are passing successfully.

import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
Expand Down Expand Up @@ -301,32 +303,48 @@ public void purgeOrder_shouldDeleteOrderFromTheDatabase() {
* @see OrderNumberGenerator#getNewOrderNumber(OrderContext)
*/
@Test
public void getNewOrderNumber_shouldAlwaysReturnUniqueOrderNumbersWhenCalledMultipleTimesWithoutSavingOrders()
throws InterruptedException {

int N = 50;
final Set<String> uniqueOrderNumbers = Collections.synchronizedSet(new HashSet<String>(50));
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < N; i++) {
threads.add(new Thread(() -> {
try {
Context.openSession();
Context.addProxyPrivilege(PrivilegeConstants.ADD_ORDERS);
uniqueOrderNumbers.add(((OrderNumberGenerator) orderService).getNewOrderNumber(null));
} finally {
Context.removeProxyPrivilege(PrivilegeConstants.ADD_ORDERS);
Context.closeSession();
}
}));
}
for (int i = 0; i < N; ++i) {
threads.get(i).start();
}
for (int i = 0; i < N; ++i) {
threads.get(i).join();
}
//since we used a set we should have the size as N indicating that there were no duplicates
assertEquals(N, uniqueOrderNumbers.size());
@Timeout(15)
public void getNewOrderNumber_shouldAlwaysReturnUniqueOrderNumbersWhenCalledConcurrently() throws Exception {

int N = 20;
Set<String> uniqueOrderNumbers = Collections.synchronizedSet(new HashSet<>());

ExecutorService executor = Executors.newFixedThreadPool(N);

CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch doneLatch = new CountDownLatch(N);

for (int i = 0; i < N; i++) {
executor.submit(() -> {
try {
Context.openSession();
Context.addProxyPrivilege(PrivilegeConstants.ADD_ORDERS);

startLatch.await(); // all threads start together

String orderNumber = ((OrderNumberGenerator) orderService).getNewOrderNumber(null);

uniqueOrderNumbers.add(orderNumber);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
finally {
Context.removeProxyPrivilege(PrivilegeConstants.ADD_ORDERS);
Context.closeSession();
doneLatch.countDown();
}
});
}

startLatch.countDown(); // release all threads

boolean completed = doneLatch.await(10, TimeUnit.SECONDS);
executor.shutdownNow();

assertTrue(completed, "Deadlock detected: threads did not complete");
assertEquals(N, uniqueOrderNumbers.size());
}

/**
Expand Down
Loading