Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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> setIfAbsent(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