Skip to content

Commit

Permalink
Add testing that things seem to generally work OK with virtual thread…
Browse files Browse the repository at this point in the history
…s. Not exhaustive.
  • Loading branch information
badgerwithagun committed Dec 23, 2023
1 parent 386a751 commit a8f49f0
Show file tree
Hide file tree
Showing 11 changed files with 435 additions and 95 deletions.
9 changes: 9 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,15 @@
<module>transactionoutbox-spring</module>
</modules>
</profile>
<profile>
<id>java-21-modules</id>
<activation>
<jdk>[21,)</jdk>
</activation>
<modules>
<module>transactionoutbox-virtthreads</module>
</modules>
</profile>
<profile>
<id>release</id>
<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import static org.junit.jupiter.api.Assertions.*;

import com.gruelbox.transactionoutbox.*;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.DriverManager;
Expand All @@ -22,40 +21,23 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;
import lombok.Builder;
import lombok.SneakyThrows;
import lombok.Value;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Slf4j
public abstract class AbstractAcceptanceTest {
public abstract class AbstractAcceptanceTest extends BaseTest {

private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAcceptanceTest.class);
private final ExecutorService unreliablePool =
new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(16));

private static final Random random = new Random();
protected HikariDataSource dataSource;

@BeforeEach
final void baseBeforeEach() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(connectionDetails().url());
config.setUsername(connectionDetails().user());
config.setPassword(connectionDetails().password());
config.addDataSourceProperty("cachePrepStmts", "true");
dataSource = new HikariDataSource(config);
}

@AfterEach
final void baseAfterEach() {
dataSource.close();
}

/**
* Uses a simple direct transaction manager and connection manager and attempts to fire an
Expand Down Expand Up @@ -585,68 +567,6 @@ public void success(TransactionOutboxEntry entry) {
containsInAnyOrder(IntStream.range(0, count * 10).boxed().toArray()));
}

protected ConnectionDetails connectionDetails() {
return ConnectionDetails.builder()
.dialect(Dialect.H2)
.driverClassName("org.h2.Driver")
.url(
"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=60000;LOB_TIMEOUT=2000;MV_STORE=TRUE;DATABASE_TO_UPPER=FALSE")
.user("test")
.password("test")
.build();
}

protected TransactionManager txManager() {
return TransactionManager.fromDataSource(dataSource);
}

protected Persistor persistor() {
return Persistor.forDialect(connectionDetails().dialect());
}

protected void clearOutbox() {
DefaultPersistor persistor = Persistor.forDialect(connectionDetails().dialect());
TransactionManager transactionManager = txManager();
transactionManager.inTransaction(
tx -> {
try {
persistor.clear(tx);
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
}

protected void withRunningFlusher(TransactionOutbox outbox, ThrowingRunnable runnable)
throws Exception {
Thread backgroundThread =
new Thread(
() -> {
while (!Thread.interrupted()) {
try {
// Keep flushing work until there's nothing left to flush
//noinspection StatementWithEmptyBody
while (outbox.flush()) {}
} catch (Exception e) {
log.error("Error flushing transaction outbox. Pausing", e);
}
try {
//noinspection BusyWait
Thread.sleep(250);
} catch (InterruptedException e) {
break;
}
}
});
backgroundThread.start();
try {
runnable.run();
} finally {
backgroundThread.interrupt();
backgroundThread.join();
}
}

private static class FailingInstantiator implements Instantiator {

private final AtomicInteger attempts;
Expand Down Expand Up @@ -699,15 +619,4 @@ public Object getInstance(String name) {
}
}
}

@Value
@Accessors(fluent = true)
@Builder
public static class ConnectionDetails {
String driverClassName;
String url;
String user;
String password;
Dialect dialect;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.gruelbox.transactionoutbox.testing;

import com.gruelbox.transactionoutbox.*;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.SQLException;
import lombok.Builder;
import lombok.Value;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

@Slf4j
public abstract class BaseTest {

protected HikariDataSource dataSource;

@BeforeEach
final void baseBeforeEach() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(connectionDetails().url());
config.setUsername(connectionDetails().user());
config.setPassword(connectionDetails().password());
config.addDataSourceProperty("cachePrepStmts", "true");
dataSource = new HikariDataSource(config);
}

@AfterEach
final void baseAfterEach() {
dataSource.close();
}

protected ConnectionDetails connectionDetails() {
return ConnectionDetails.builder()
.dialect(Dialect.H2)
.driverClassName("org.h2.Driver")
.url(
"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=60000;LOB_TIMEOUT=2000;MV_STORE=TRUE;DATABASE_TO_UPPER=FALSE")
.user("test")
.password("test")
.build();
}

protected TransactionManager txManager() {
return TransactionManager.fromDataSource(dataSource);
}

protected Persistor persistor() {
return Persistor.forDialect(connectionDetails().dialect());
}

protected void clearOutbox() {
DefaultPersistor persistor = Persistor.forDialect(connectionDetails().dialect());
TransactionManager transactionManager = txManager();
transactionManager.inTransaction(
tx -> {
try {
persistor.clear(tx);
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
}

protected void withRunningFlusher(TransactionOutbox outbox, ThrowingRunnable runnable)
throws Exception {
Thread backgroundThread =
new Thread(
() -> {
while (!Thread.interrupted()) {
try {
// Keep flushing work until there's nothing left to flush
//noinspection StatementWithEmptyBody
while (outbox.flush()) {}
} catch (Exception e) {
log.error("Error flushing transaction outbox. Pausing", e);
}
try {
//noinspection BusyWait
Thread.sleep(250);
} catch (InterruptedException e) {
break;
}
}
});
backgroundThread.start();
try {
runnable.run();
} finally {
backgroundThread.interrupt();
backgroundThread.join();
}
}

@Value
@Accessors(fluent = true)
@Builder
public static class ConnectionDetails {
String driverClassName;
String url;
String user;
String password;
Dialect dialect;
}
}
82 changes: 82 additions & 0 deletions transactionoutbox-virtthreads/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>transactionoutbox-parent</artifactId>
<groupId>com.gruelbox</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<name>Transaction Outbox Virtual Threads support</name>
<packaging>jar</packaging>
<artifactId>transactionoutbox-virtthreads</artifactId>
<description>A safe implementation of the transactional outbox pattern for Java (core library)</description>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.gruelbox</groupId>
<artifactId>transactionoutbox-core</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>

<!-- Compile time -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>com.gruelbox</groupId>
<artifactId>transactionoutbox-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.gruelbox</groupId>
<artifactId>transactionoutbox-jooq</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>oracle-xe</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
</project>
Loading

0 comments on commit a8f49f0

Please sign in to comment.