Skip to content

Commit

Permalink
Implement default clauses (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw authored Jan 5, 2024
1 parent e5cb3c5 commit 3b25e95
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 3 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,28 @@ class Demo5 {
}
```

Optionally, you can also provide a default clause, which will be selected if none of the other clauses can be completed
immediately:

```java
import com.softwaremill.jox.Channel;

import static com.softwaremill.jox.Select.defaultClause;
import static com.softwaremill.jox.Select.select;

class Demo6 {
public static void main(String[] args) throws InterruptedException {
var ch1 = new Channel<Integer>(3);
var ch2 = new Channel<Integer>(3);

var received = select(ch1.receiveClause(), ch2.receiveClause(), defaultClause(52));

// prints: Received: 52
System.out.println("Received: " + received);
}
}
```

## Performance

The project includes benchmarks implemented using JMH - both for the `Channel`, as well as for some built-in Java
Expand Down
15 changes: 14 additions & 1 deletion core/src/main/java/com/softwaremill/jox/Select.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Supplier;

public class Select {
/*
Expand Down Expand Up @@ -75,7 +76,11 @@ public static <U> Object selectSafe(SelectClause<U>... clauses) throws Interrupt
verifyChannelsUnique(clauses);

var si = new SelectInstance(clauses.length);
for (var clause : clauses) {
for (int i = 0; i < clauses.length; i++) {
SelectClause<U> clause = clauses[i];
if (clause instanceof DefaultClause<U> && i != clauses.length - 1) {
throw new IllegalArgumentException("The default clause can only be the last one.");
}
if (!si.register(clause)) {
break; // channel is closed, or a clause was selected - in both cases, no point in further registrations
}
Expand All @@ -94,6 +99,14 @@ private static void verifyChannelsUnique(SelectClause<?>[] clauses) {
}
}
}

public static <T> SelectClause<T> defaultClause(T value) {
return defaultClause(() -> value);
}

public static <T> SelectClause<T> defaultClause(Supplier<T> callback) {
return new DefaultClause<>(callback);
}
}

class SelectInstance {
Expand Down
33 changes: 33 additions & 0 deletions core/src/main/java/com/softwaremill/jox/SelectClause.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.softwaremill.jox;

import java.util.function.Supplier;

/**
* A clause to use as part of {@link Select#select(SelectClause[])}. Clauses can be created having a channel instance,
* using {@link Channel#receiveClause()} and {@link Channel#sendClause(Object)}}.
Expand All @@ -22,3 +24,34 @@ public abstract class SelectClause<T> {
*/
abstract T transformedRawValue(Object rawValue);
}

class DefaultClause<T> extends SelectClause<T> {
private final Supplier<T> callback;

public DefaultClause(Supplier<T> callback) {
this.callback = callback;
}

@Override
Channel<?> getChannel() {
return null;
}

@Override
Object register(SelectInstance select) {
return DefaultClauseMarker.DEFAULT;
}

@Override
T transformedRawValue(Object rawValue) {
return callback.get();
}
}

/**
* Used as a result of {@link DefaultClause#register(SelectInstance)}, instead of null, to indicate that the select
* clause has been selected during registration.
*/
enum DefaultClauseMarker {
DEFAULT
}
56 changes: 56 additions & 0 deletions core/src/test/java/com/softwaremill/jox/SelectTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.softwaremill.jox;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.ExecutionException;

import static com.softwaremill.jox.Select.*;
import static com.softwaremill.jox.TestUtil.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

public class SelectTest {
@Test
public void testSelectDefault() throws InterruptedException {
// given
Channel<String> ch1 = new Channel<>(1);
Channel<String> ch2 = new Channel<>(1);

// when
String received = select(ch1.receiveClause(), ch2.receiveClause(), defaultClause("x"));

// then
assertEquals("x", received);
}

@Test
public void testDoNotSelectDefault() throws InterruptedException {
// given
Channel<String> ch1 = new Channel<>(1);
Channel<String> ch2 = new Channel<>(1);
ch2.send("a");

// when
String received = select(ch1.receiveClause(), ch2.receiveClause(), defaultClause("x"));

// then
assertEquals("a", received);
}

@Test
public void testDefaultCanOnlyBeLast() throws InterruptedException {
// given
Channel<String> ch1 = new Channel<>(1);
Channel<String> ch2 = new Channel<>(1);

// when
try {
select(ch1.receiveClause(), defaultClause("x"), ch2.receiveClause());
fail("Select should have failed");
} catch (IllegalArgumentException e) {
// then ok
}
}
}
7 changes: 5 additions & 2 deletions core/src/test/java/com/softwaremill/jox/StressTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ void testMultipleOperationsSelect(int capacity) throws Exception {
* correct.
*/
private void testAndVerify(int capacity, boolean direct) throws Exception {
int numberOfRepetitions = 20;
boolean ci = System.getenv("CI") != null;
System.out.println("Running in ci: " + ci);

int numberOfRepetitions = ci ? 20 : 5;
int numberOfThreads = 8;
int numberOfIterations = 2000;
int numberOfIterations = ci ? 2000 : 100;
int numberOfChannels = direct ? 1 : 10;

for (int r = 0; r < numberOfRepetitions; r++) {
Expand Down

1 comment on commit 3b25e95

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 3b25e95 Previous: 00712c2 Ratio
com.softwaremill.jox.SelectBenchmark.two_channels_iterative 305.68983421198413 ns/op 249.15165528650795 ns/op 1.23
com.softwaremill.jox.RendezvousKotlinBenchmark.sendReceiveUsingDefaultDispatcher 202.55439544793646 ns/op 141.0767834504762 ns/op 1.44

This comment was automatically generated by workflow using github-action-benchmark.

CC: @adamw

Please sign in to comment.