Skip to content

Commit

Permalink
Merge pull request #14 from briancorbinxyz/jdk23
Browse files Browse the repository at this point in the history
Jdk23
  • Loading branch information
briancorbinxyz authored Aug 9, 2024
2 parents 662a80e + fa28496 commit 7150f54
Show file tree
Hide file tree
Showing 15 changed files with 242 additions and 36 deletions.
Binary file modified .DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ build
debug/
target/

# BC: in our gradle build we changed the target directory to be build/cargo
build/cargo

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
Expand Down
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"type": "java",
"name": "GameClient (on ZGC)",
"request": "launch",
"vmArgs": "-XX:+UseZGC -XX:+ZGenerational",
"vmArgs": "-XX:+UseZGC",
"mainClass": "org.example.GameClient",
"projectName": "app",
"env": {
Expand All @@ -135,7 +135,7 @@
"type": "java",
"name": "GameServer (on ZGC)",
"request": "launch",
"vmArgs": "-XX:+UseZGC -XX:+ZGenerational",
"vmArgs": "-XX:+UseZGC",
"mainClass": "org.example.GameServer",
"projectName": "app",
"env": {
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ Pairs with the ongoing blog post: [Road to JDK 25 - Over-Engineering Tic-Tac-Toe

### Features

https://openjdk.org/projects/jdk/23/

- **JEP467**: Markdown Documentation Comments
- **JEP474**: ZGC: Generational Mode by Default
- **JEP471**: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal

https://openjdk.org/projects/jdk/22/

- **JEP454**: Foreign Function & Memory API
Expand Down
Binary file modified app/.DS_Store
Binary file not shown.
26 changes: 20 additions & 6 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ repositories {
// https://doc.rust-lang.org/cargo/getting-started/installation.html
val osName = System.getProperty("os.name").lowercase()

val cargoBuildDir = file("${buildDir}/cargo")
val cargoBuildDir = file("${layout.buildDirectory.get()}/cargo")

val libPath = when {
osName.contains("win") -> "${cargoBuildDir}/debug"
Expand Down Expand Up @@ -88,6 +88,20 @@ dependencies {
runtimeOnly("ch.qos.logback:logback-classic:1.5.6")
runtimeOnly("org.slf4j:slf4j-api:2.0.13")
runtimeOnly("org.slf4j:slf4j-jdk-platform-logging:2.0.13")


// JDK23: JMH (Third-Party) Not required, added for benchmarking
// https://github.com/openjdk/jmh
implementation("org.openjdk.jmh:jmh-core:1.37")
annotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.37")
}

// Run JMH benchmark
// ./gradlew jmh
tasks.register<JavaExec>("jmh") {
mainClass.set("org.openjdk.jmh.Main")
classpath = sourceSets["main"].runtimeClasspath
args = listOf("org.example.interop.benchmark.PlayerIdsBenchmark")
}

testing {
Expand All @@ -103,7 +117,7 @@ testing {
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(22)
languageVersion = JavaLanguageVersion.of(23)
}
}

Expand All @@ -123,7 +137,7 @@ graalvmNative {
javaLauncher = javaToolchains.launcherFor {
// NB: On MacOS ARM ARCH the native-image implementation is not available
// for the versions of GRAAL_VM Community edition - selecting Oracle
languageVersion = JavaLanguageVersion.of(22)
languageVersion = JavaLanguageVersion.of(23)
vendor = JvmVendorSpec.matching("Oracle")
// languageVersion = JavaLanguageVersion.of(17)
// vendor = JvmVendorSpec.GRAAL_VM
Expand All @@ -141,7 +155,7 @@ application {
// WARNING: java.lang.foreign.SymbolLookup::libraryLookup has been called by org.example.GameBoardNativeImpl in an unnamed module
// WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
// WARNING: Restricted methods will be blocked in a future release unless native access is enabled
applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "-XX:+UseZGC")
}

tasks.run.configure {
Expand All @@ -160,7 +174,7 @@ tasks.withType<Test>().all {
// WARNING: java.lang.foreign.SymbolLookup::libraryLookup has been called by org.example.GameBoardNativeImpl in an unnamed module
// WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
// WARNING: Restricted methods will be blocked in a future release unless native access is enabled
jvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
jvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "-XX:+UseZGC")
environment("PATH", libPath) // For Windows
environment("LD_LIBRARY_PATH", libPath) // For Linux
environment("DYLD_LIBRARY_PATH", libPath) // For macOS
Expand All @@ -177,7 +191,7 @@ tasks.named<JavaExec>("run") {
// WARNING: java.lang.foreign.SymbolLookup::libraryLookup has been called by org.example.GameBoardNativeImpl in an unnamed module
// WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
// WARNING: Restricted methods will be blocked in a future release unless native access is enabled
jvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
jvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "-XX:+UseZGC")
environment("PATH", libPath) // For Windows
environment("LD_LIBRARY_PATH", libPath) // For Linux
environment("DYLD_LIBRARY_PATH", libPath) // For macOS
Expand Down
17 changes: 10 additions & 7 deletions app/src/main/java/org/example/GameBoard.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@ public interface GameBoard extends JsonSerializable {
*/
int getDimension();

/**
* Converts the game board to a JSON string representation for serialization. Format corresponds
* to the following JSON schema with content as a 1D array of strings of size dimension x
* dimension: {@snippet : { "dimension": int, "content": [ string, ... ] } }
*
* @return the game board as a JSON string
*/
///
/// Converts the game board to a JSON string representation for serialization. Format
/// corresponds to the following JSON schema with content as a 1D array of strings of size
/// dimension x dimension.
///
/// ```javascript
/// { "dimension": int, "content": [ string, string, ..., string ] } }
/// ```
/// @return the game board as a JSON string
/// @see JsonSerializable
String asJsonString();

/**
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/org/example/JsonSerializable.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package org.example;

/// This interface defines a contract for classes that can be serialized to JSON strings.
/// Any class that implements this interface must provide an implementation of the `asJsonString()`
/// method, which returns the JSON representation of the object.
public interface JsonSerializable {

/// Returns the JSON representation of the object.
/// @return the JSON string representation of the object.
String asJsonString();
}
28 changes: 15 additions & 13 deletions app/src/main/java/org/example/Player.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package org.example;

/**
* Tic-tac-toe player interface for all players
* {@snippet :
* // Create a human player
* Player player = new HumanPlayer("X"); // @highlight region="player" substring="player"
*
* // Choose the next valid move on the game board
* int validBoardLocation = player.nextMove(gameBoard); // @end
* }
*/
/// Tic-tac-toe player interface for all players
/// {@snippet :
/// // Create a human player
/// Player player = new HumanPlayer("X"); // @highlight region="player" substring="player"
///
/// // Choose the next valid move on the game board
/// int validBoardLocation = player.nextMove(gameBoard); // @end
/// }
public sealed interface Player permits HumanPlayer, BotPlayer, RemoteBotPlayer {


/// Returns the marker (e.g. "X" or "O") used by this player.
/// @return the player's marker
String getPlayerMarker();

/// Chooses the next valid move on the game board.
/// @param board the current state of the game board
/// @return the index of the next valid move on the board
int nextMove(GameBoard board);

}
}
1 change: 0 additions & 1 deletion app/src/main/java/org/example/SecureMessageHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ private void registerSecurityProviders() {
* @throws InvalidParameterSpecException if the received Kyber parameters are invalid
* @throws InvalidAlgorithmParameterException if the Kyber parameters are invalid
* @throws InvalidKeyException if the public/private key is invalid
* @throws EncapsulateException if the encapsulation of the shared secret fails
* @throws DecapsulateException if the decapsulation of the shared secret fails
* @throws ClassNotFoundException
*/
Expand Down
31 changes: 31 additions & 0 deletions app/src/main/java/org/example/interop/PlayerIds.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.example.interop;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class PlayerIds {

private volatile int nextId;

private static final VarHandle NEXT_ID_VH;

static {
try {
NEXT_ID_VH = MethodHandles.lookup().findVarHandle(PlayerIds.class, "nextId", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}

public PlayerIds(int initialValue) {
this.nextId = initialValue;
}

public int getNextId() {
return nextId;
}

public int getNextIdAndIncrement() {
return (int) NEXT_ID_VH.getAndAdd(this, 1);
}
}
13 changes: 7 additions & 6 deletions app/src/main/java/org/example/interop/TicTacToeGameBoard.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.lang.ref.Cleaner;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.example.GameBoard;
Expand All @@ -28,12 +27,14 @@ class TicTacToeGameBoard implements GameBoard {

private final SymbolLookup libTicTacToe;

private final AtomicInteger nextId;
private final PlayerIds playerIds;

private final Map<String, Integer> playerMarkerToId;
private final Map<Integer, String> idToPlayerMarker;

private final Cleaner cleaner;

@SuppressWarnings("unused")
private final Cleaner.Cleanable cleanable;

private MethodHandle newGameBoard;
Expand All @@ -48,7 +49,7 @@ public TicTacToeGameBoard(int dimension, SymbolLookup libTicTacToe, Cleaner clea
this.libTicTacToe = libTicTacToe;
this.playerMarkerToId = new HashMap<>();
this.idToPlayerMarker = new HashMap<>();
this.nextId = new AtomicInteger(1);
this.playerIds = new PlayerIds(1);
this.initGameBoardMethods();
this.board = newGameBoard(dimension);
this.cleaner = cleaner;
Expand All @@ -73,7 +74,7 @@ public TicTacToeGameBoard(int dimension, SymbolLookup libTicTacToe, Cleaner clea
this.libTicTacToe = libTicTacToe;
this.playerMarkerToId = new HashMap<>(playerMarkerToId);
this.idToPlayerMarker = new HashMap<>(idToPlayerMarker);
this.nextId = new AtomicInteger(initialValue);
this.playerIds = new PlayerIds(initialValue);
this.initGameBoardMethods();
this.board = board;
this.cleaner = cleaner;
Expand Down Expand Up @@ -109,7 +110,7 @@ public boolean hasMovesAvailable() {
@Override
public GameBoard withMove(String playerMarker, int location) {
if (!playerMarkerToId.containsKey(playerMarker)) {
int id = nextId.getAndIncrement();
int id = playerIds.getNextIdAndIncrement();
playerMarkerToId.put(playerMarker, id);
idToPlayerMarker.put(id, playerMarker);
}
Expand All @@ -121,7 +122,7 @@ public GameBoard withMove(String playerMarker, int location) {
newBoard,
playerMarkerToId,
idToPlayerMarker,
nextId.get(),
playerIds.getNextId(),
libTicTacToe,
cleaner);
} catch (Throwable e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.example.interop.benchmark;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import org.example.interop.PlayerIds;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;

@SuppressWarnings("unused")
public class PlayerIdsBenchmark {

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testPlayerIdsGetId() {
PlayerIds ids = new PlayerIds(1);
for (int i = 0; i < 1000; i++) {
var id = ids.getNextId();
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testPlayerIdsGetAndIncrementId() {
PlayerIds ids = new PlayerIds(1);
for (int i = 0; i < 1000; i++) {
var id = ids.getNextIdAndIncrement();
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testAtomicIntegerGetId() {
AtomicInteger ids = new AtomicInteger(1);
for (int i = 0; i < 1000; i++) {
var id = ids.get();
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testAtomicIntegerGetAndIncrement() {
AtomicInteger ids = new AtomicInteger(1);
for (int i = 0; i < 1000; i++) {
var id = ids.getAndIncrement();
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testLockGetId() {
Control ids = new Control(1);
for (int i = 0; i < 1000; i++) {
var id = ids.getId();
}
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void testLockGetAndIncrement() {
Control ids = new Control(1);
for (int i = 0; i < 1000; i++) {
var id = ids.getAndIncrement();
}
}

/// Naive implementation of an id generator.
private static class Control {
private final ReentrantLock lock = new ReentrantLock();

private int id = 1;

private Control(int initialValue) {
this.id = initialValue;
}

private int getId() {
lock.lock();
try {
return id;
} finally {
lock.unlock();
}
}

private int getAndIncrement() {
lock.lock();
try {
int oldId = id;
id = id + 1;
return oldId;
} finally {
lock.unlock();
}
}
}
}
Loading

0 comments on commit 7150f54

Please sign in to comment.