Skip to content

Commit

Permalink
Merge pull request #501 from DependencyTrack/jdbi-rowmapper-threadsafety
Browse files Browse the repository at this point in the history
Address thread safety issues with `CelPolicy*RowMapper`s
  • Loading branch information
nscuro authored Dec 20, 2023
2 parents 0df3887 + e7e9ff4 commit 53b4e2f
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,9 @@

public class CelPolicyComponentRowMapper implements RowMapper<Component> {

private final Component.Builder builder;

@SuppressWarnings("unused") // Used by JDBI
public CelPolicyComponentRowMapper() {
this(Component.newBuilder());
}

CelPolicyComponentRowMapper(final Component.Builder builder) {
this.builder = builder;
}

@Override
public Component map(final ResultSet rs, final StatementContext ctx) throws SQLException {
final Component.Builder builder = Component.newBuilder();
maybeSet(rs, "uuid", ResultSet::getString, builder::setUuid);
maybeSet(rs, "group", ResultSet::getString, builder::setGroup);
maybeSet(rs, "name", ResultSet::getString, builder::setName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,9 @@

public class CelPolicyProjectRowMapper implements RowMapper<Project> {

private final Project.Builder builder;

@SuppressWarnings("unused") // Used by JDBI
public CelPolicyProjectRowMapper() {
this(Project.newBuilder());
}

public CelPolicyProjectRowMapper(final Project.Builder builder) {
this.builder = builder;
}

@Override
public Project map(final ResultSet rs, final StatementContext ctx) throws SQLException {
final Project.Builder builder = Project.newBuilder();
maybeSet(rs, "uuid", ResultSet::getString, builder::setUuid);
maybeSet(rs, "group", ResultSet::getString, builder::setGroup);
maybeSet(rs, "name", ResultSet::getString, builder::setName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,9 @@ public class CelPolicyVulnerabilityRowMapper implements RowMapper<Vulnerability>
private static final TypeReference<List<VulnerabilityAlias>> VULNERABILITY_ALIASES_TYPE_REF = new TypeReference<>() {
};

private final Vulnerability.Builder builder;

@SuppressWarnings("unused") // Used by JDBI
public CelPolicyVulnerabilityRowMapper() {
this(Vulnerability.newBuilder());
}

public CelPolicyVulnerabilityRowMapper(final Vulnerability.Builder builder) {
this.builder = builder;
}

@Override
public Vulnerability map(final ResultSet rs, final StatementContext ctx) throws SQLException {
final Vulnerability.Builder builder = Vulnerability.newBuilder();
maybeSet(rs, "uuid", ResultSet::getString, builder::setUuid);
maybeSet(rs, "id", ResultSet::getString, builder::setId);
maybeSet(rs, "source", ResultSet::getString, builder::setSource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -130,7 +134,7 @@ public void testEvaluateWithMultipleMatchingPolicies() {
}

@Test
public void testEvaluateWithAdditionalRequiredFields() {
public void testEvaluateWithAdditionalRequiredFields() throws Exception {
final var persistentProject = new org.dependencytrack.model.Project();
persistentProject.setName("acme-app");
persistentProject.setVersion("1.0.0");
Expand Down Expand Up @@ -183,8 +187,38 @@ public void testEvaluateWithAdditionalRequiredFields() {
doReturn(List.of(policy))
.when(policyProviderMock).getApplicablePolicies(any(Project.class));

assertThat(policyEvaluator.evaluate(List.of(vuln), component, project))
.containsOnly(Map.entry(persistentVuln.getUuid(), policy));
// We want evaluation as a whole to be thread safe!
final var startLatch = new CountDownLatch(1);
final ExecutorService executor = Executors.newFixedThreadPool(10);
final var exceptionsThrown = new ConcurrentLinkedQueue<Exception>();
try {
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
try {
startLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

try {
assertThat(policyEvaluator.evaluate(List.of(vuln), component, project))
.containsOnly(Map.entry(persistentVuln.getUuid(), policy));
} catch (Exception e) {
// If we throw here, the exception won't bubble up to the test.
// Collect all encountered exceptions here and assert on all of them later.
exceptionsThrown.add(e);
}
});
}

// Open the flood gates :)
startLatch.countDown();
} finally {
executor.shutdown();
assertThat(executor.awaitTermination(90, TimeUnit.SECONDS)).isTrue();
}

assertThat(exceptionsThrown).isEmpty();
}

private static final class TestCacheManager extends AbstractCacheManager {
Expand Down

0 comments on commit 53b4e2f

Please sign in to comment.