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 issue 1118 #5

Merged
merged 4 commits into from
Jul 4, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions src/main/java/spark/FilterImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
*/
public abstract class FilterImpl implements Filter, Wrapper {

static final String DEFAULT_ACCEPT_TYPE = "*/*";
public static final String DEFAULT_ACCEPT_TYPE = "*/*";

private String path;
private String acceptType;
Expand Down Expand Up @@ -63,7 +63,7 @@ static FilterImpl create(final String path, final Filter filter) {
* @param filter the filter
* @return the wrapped route
*/
static FilterImpl create(final String path, String acceptType, final Filter filter) {
public static FilterImpl create(final String path, String acceptType, final Filter filter) {
if (acceptType == null) {
acceptType = DEFAULT_ACCEPT_TYPE;
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/spark/RouteImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* @author Per Wendel
*/
public abstract class RouteImpl implements Route, Wrapper {
static final String DEFAULT_ACCEPT_TYPE = "*/*";
public static final String DEFAULT_ACCEPT_TYPE = "*/*";

private String path;
private String acceptType;
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/spark/route/Routes.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import spark.FilterImpl;
import spark.RouteImpl;
Expand Down Expand Up @@ -49,7 +50,7 @@ public static Routes create() {
* Constructor
*/
protected Routes() {
routes = new ArrayList<>();
routes = new CopyOnWriteArrayList<>();
}

/**
Expand Down
103 changes: 103 additions & 0 deletions src/test/java/spark/route/RoutesConcurrencyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package spark.route;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;

import spark.FilterImpl;
import spark.RouteImpl;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;

public class RoutesConcurrencyTest {
private final AtomicInteger numberOfSuccessfulIterations = new AtomicInteger();
private final AtomicInteger numberOfFailedIterations = new AtomicInteger();
private ExecutorService executorService;

@Rule
public final ErrorCollector collector = new ErrorCollector();

private static final int NUMBER_OF_ITERATIONS = 10_000;
private static final int NUMBER_OF_THREADS = 2;
private static final int NUMBER_OF_TASKS = NUMBER_OF_THREADS;

private static final String ROUTE_PATH_PREFIX = "/route/";
private static final String FILTER_PATH_PREFIX = "/filter/";

@Before
public void setup() {
numberOfSuccessfulIterations.set(0);
numberOfFailedIterations.set(0);
}

@After
public void teardown() {
if (executorService != null && !executorService.isShutdown()) {
try {
executorService.shutdownNow();
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("Executor service did not terminate.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

@Test
public void classShouldBeThreadSafe() throws Exception {
executorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
List<Callable<Void>> tasks = partitionIterationsIntoTasks();
List<Future<Void>> futureResults = executorService.invokeAll(tasks);
executorService.shutdown();
for (Future<Void> futureResult : futureResults) {
futureResult.get();
collector.checkThat(futureResult.isDone(), is(true));
}
collector.checkThat(numberOfSuccessfulIterations.intValue(), equalTo(NUMBER_OF_ITERATIONS));
collector.checkThat(numberOfFailedIterations.intValue(), equalTo(0));
}

private List<Callable<Void>> partitionIterationsIntoTasks() {
final List<Callable<Void>> tasks = new ArrayList<>();
final Routes routes = Routes.create();
final int numberOfIterationsPerTask = NUMBER_OF_ITERATIONS / NUMBER_OF_TASKS;
for (int taskIndex = 0; taskIndex < NUMBER_OF_TASKS; taskIndex++) {
final int fromIteration = numberOfIterationsPerTask * taskIndex;
final int toIteration = numberOfIterationsPerTask * (taskIndex + 1);
tasks.add(() -> {
for (int iterationIndex = fromIteration; iterationIndex < toIteration; iterationIndex++) {
try {
String routePath = ROUTE_PATH_PREFIX + iterationIndex;
String filterPath = FILTER_PATH_PREFIX + iterationIndex;
routes.add(HttpMethod.get, RouteImpl.create(routePath, RouteImpl.DEFAULT_ACCEPT_TYPE, null));
routes.add(HttpMethod.get, FilterImpl.create(filterPath, FilterImpl.DEFAULT_ACCEPT_TYPE, null));
routes.find(HttpMethod.get, routePath, RouteImpl.DEFAULT_ACCEPT_TYPE);
routes.findMultiple(HttpMethod.get, filterPath, FilterImpl.DEFAULT_ACCEPT_TYPE);
routes.findAll();
routes.remove(routePath, HttpMethod.get.toString());
routes.remove(filterPath);
routes.clear();
numberOfSuccessfulIterations.getAndIncrement();
} catch (Exception e) {
numberOfFailedIterations.getAndIncrement();
}
}
return null;
});
}
return tasks;
}
}