Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: build test and publish
run: ./gradlew assemble && ./gradlew check --info && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -x check --info --stacktrace
run: ./gradlew assemble && ./gradlew check --info && && ./gradlew jcstress && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -x check --info --stacktrace
env:
CI: true
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: build and test
run: ./gradlew assemble && ./gradlew check --info --stacktrace
run: ./gradlew assemble && ./gradlew check --info --stacktrace && ./gradlew jcstress
env:
CI: true
11 changes: 9 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import net.ltgt.gradle.errorprone.CheckSeverity
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import net.ltgt.gradle.errorprone.CheckSeverity

import java.text.SimpleDateFormat

plugins {
Expand All @@ -15,6 +16,7 @@ plugins {
id 'com.github.ben-manes.versions' version '0.53.0'
id "me.champeau.jmh" version "0.7.3"
id "net.ltgt.errorprone" version '4.3.0'
id "io.github.reyerizo.gradle.jcstress" version "0.8.15"

// Kotlin just for tests - not production code
id 'org.jetbrains.kotlin.jvm' version '2.2.20'
Expand Down Expand Up @@ -229,7 +231,8 @@ nexusPublishing {
// https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
// GraphQL Java does not publish snapshots, but adding this URL for completeness
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) }
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
}
}
}

Expand Down Expand Up @@ -258,3 +261,7 @@ tasks.named("dependencyUpdates").configure {
isNonStable(it.candidate.version)
}
}

jcstress {
// verbose = true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.dataloader;

import org.openjdk.jcstress.annotations.Actor;
import org.openjdk.jcstress.annotations.Arbiter;
import org.openjdk.jcstress.annotations.JCStressTest;
import org.openjdk.jcstress.annotations.Outcome;
import org.openjdk.jcstress.annotations.State;
import org.openjdk.jcstress.infra.results.II_Result;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;

import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE;

@JCStressTest
@State
@Outcome(id = "2000, 2000", expect = ACCEPTABLE, desc = "accepted")
public class DataLoader_Batching_Caching_JCStress {


AtomicInteger counter = new AtomicInteger();
AtomicInteger batchLoaderCount = new AtomicInteger();
volatile boolean finished1;
volatile boolean finished2;


BatchLoader<String, String> batchLoader = keys -> {
return CompletableFuture.supplyAsync(() -> {
batchLoaderCount.getAndAdd(keys.size());
return keys;
});
};
DataLoader<String, String> dataLoader = DataLoaderFactory.newDataLoader(batchLoader);

public DataLoader_Batching_Caching_JCStress() {

}

@Actor
public void load1() {
for (int i = 0; i < 1000; i++) {
dataLoader.load("load-1-" + i);
}
// we load the same keys again
for (int i = 0; i < 1000; i++) {
dataLoader.load("load-1-" + i);
}
finished1 = true;
}

@Actor
public void load2() {
for (int i = 0; i < 1000; i++) {
dataLoader.load("load-2-" + i);
}
// we load the same keys again
for (int i = 0; i < 1000; i++) {
dataLoader.load("load-2-" + i);
}
finished2 = true;
}


@Actor
public void dispatch1() {
while (!finished1 || !finished2) {
try {
List<String> dispatchedResult = dataLoader.dispatch().get();
counter.getAndAdd(dispatchedResult.size());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
try {
List<String> dispatchedResult = dataLoader.dispatch().get();
counter.getAndAdd(dispatchedResult.size());
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@Actor
public void dispatch2() {
while (!finished1 || !finished2) {
try {
List<String> dispatchedResult = dataLoader.dispatch().get();
counter.getAndAdd(dispatchedResult.size());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
try {
List<String> dispatchedResult = dataLoader.dispatch().get();
counter.getAndAdd(dispatchedResult.size());
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@Arbiter
public void arbiter(II_Result r) {
r.r1 = counter.get();
r.r2 = batchLoaderCount.get();
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.dataloader;

import org.openjdk.jcstress.annotations.Actor;
import org.openjdk.jcstress.annotations.Arbiter;
import org.openjdk.jcstress.annotations.JCStressTest;
import org.openjdk.jcstress.annotations.Outcome;
import org.openjdk.jcstress.annotations.State;
import org.openjdk.jcstress.infra.results.II_Result;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;

import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE;
import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE_INTERESTING;

@JCStressTest
@State
@Outcome(id = "1000, 1000", expect = ACCEPTABLE, desc = "No keys loaded twice")
@Outcome(id = "1.*, 1000", expect = ACCEPTABLE_INTERESTING, desc = "Some keys loaded twice")
public class DataLoader_NoBatching_Caching_JCStress {


AtomicInteger batchLoaderCount = new AtomicInteger();

BatchLoader<String, String> batchLoader = keys -> {
batchLoaderCount.getAndAdd(keys.size());
return CompletableFuture.completedFuture(keys);
};


DataLoader<String, String> dataLoader = DataLoaderFactory.newDataLoader(batchLoader, DataLoaderOptions.newOptions().setBatchingEnabled(false).build());

@Actor
public void load1() {
for (int i = 0; i < 1000; i++) {
dataLoader.load("load-1-" + i);
}
}

@Actor
public void load2() {
for (int i = 0; i < 1000; i++) {
dataLoader.load("load-1-" + i);
}
}


@Arbiter
public void arbiter(II_Result r) {
r.r1 = batchLoaderCount.get();
r.r2 = dataLoader.getCacheMap().size();
}

}
10 changes: 8 additions & 2 deletions src/jmh/java/performance/DataLoaderDispatchPerformance.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

Expand Down Expand Up @@ -280,15 +281,20 @@ public void setup() {

}

DataLoader ownerDL = DataLoaderFactory.newDataLoader(ownerBatchLoader);
DataLoader petDL = DataLoaderFactory.newDataLoader(petBatchLoader);


}


@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Threads(Threads.MAX)
public void loadAndDispatch(MyState myState, Blackhole blackhole) {
DataLoader ownerDL = DataLoaderFactory.newDataLoader(ownerBatchLoader);
DataLoader petDL = DataLoaderFactory.newDataLoader(petBatchLoader);
DataLoader ownerDL = myState.ownerDL;
DataLoader petDL = myState.petDL;

for (Owner owner : owners.values()) {
ownerDL.load(owner.id);
Expand Down
14 changes: 12 additions & 2 deletions src/main/java/org/dataloader/CacheMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ static <K, V> CacheMap<K, V> simpleMap() {
*
* @return the cached value, or {@code null} if not found (depends on cache implementation)
*/
@Nullable CompletableFuture<V> get(K key);
@Nullable CompletableFuture<V> get(K key);

/**
* Gets a collection of CompletableFutures from the cache map.
*
* @return the collection of cached values
*/
Collection<CompletableFuture<V>> getAll();
Expand All @@ -90,7 +91,7 @@ static <K, V> CacheMap<K, V> simpleMap() {
*
* @return the cache map for fluent coding
*/
CacheMap<K, V> set(K key, CompletableFuture<V> value);
CompletableFuture<V> putIfAbsentAtomically(K key, CompletableFuture<V> value);

/**
* Deletes the entry with the specified key from the cache map, if it exists.
Expand All @@ -107,4 +108,13 @@ static <K, V> CacheMap<K, V> simpleMap() {
* @return the cache map for fluent coding
*/
CacheMap<K, V> clear();

/**
* Returns the current size of the cache. This is not used by DataLoader directly
* and intended for testing and debugging.
* If a cache doesn't support it, it can throw an Exception.
*
* @return
*/
int size();
}
Loading