T yield() throws SuspendExecution {
87 | com.zarbosoft.coroutinescore.Coroutine.yield();
88 | final Coroutine self = getActiveCoroutine();
89 | if (self.inException != null) {
90 | final RuntimeException e = self.inException;
91 | self.inException = null;
92 | throw e;
93 | }
94 | return (T) self.inValue;
95 | }
96 |
97 | /**
98 | * Suspend the coroutine and run the method after the suspension is complete. This is useful when scheduling
99 | * the coroutine to be run on another thread which would otherwise cause a race condition (suspension completion vs
100 | * resumption start).
101 | *
102 | * Any exceptions that escape the runnable will propagate to the coroutine runner rather than raise an error
103 | * in the coroutine. This is typically not the desired behavior, so if an exception might occur you should
104 | * catch it and send it back to the coroutine (perhaps submitting to an executor to prevent stack growth).
105 | *
106 | * @param runAfter
107 | * @param
108 | * @return Value supplied to Coroutine.process when resumed.
109 | * @throws SuspendExecution
110 | */
111 | public static T yieldThen(final Runnable runAfter) throws SuspendExecution {
112 | final Coroutine self = getActiveCoroutine();
113 | self.runAfter = runAfter;
114 | com.zarbosoft.coroutinescore.Coroutine.yield();
115 | self.runAfter = null;
116 | if (self.inException != null) {
117 | final RuntimeException e = self.inException;
118 | self.inException = null;
119 | throw e;
120 | }
121 | return (T) self.inValue;
122 | }
123 |
124 | /**
125 | * May only be called while executing a coroutine, from the same thread.
126 | *
127 | * @return The current coroutine.
128 | */
129 | public static Coroutine getActiveCoroutine() {
130 | return ((InnerCoroutine) com.zarbosoft.coroutinescore.Coroutine.getActiveCoroutine()).outer;
131 | }
132 |
133 | /**
134 | * @return true if coroutine is finished
135 | */
136 | public boolean isFinished() {
137 | return inner.getState() == com.zarbosoft.coroutinescore.Coroutine.State.FINISHED;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/main/java/com/zarbosoft/coroutines/CriticalSection.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 |
5 | import java.util.ArrayDeque;
6 | import java.util.concurrent.ExecutorService;
7 | import java.util.concurrent.locks.ReentrantLock;
8 |
9 | /**
10 | * Create a critical section around a method that will stop coroutines rather than blocking them.
11 | */
12 | public class CriticalSection {
13 | private final ReentrantLock lock = new ReentrantLock();
14 | private boolean locked = false;
15 | private final ArrayDeque queue = new ArrayDeque<>();
16 |
17 | /**
18 | * Run the method if no other coroutine is currently executing it. Otherwise suspend, and resume when the other
19 | * coroutine has finished.
20 | *
21 | * @param executor Worker to resume coroutine on if this suspends.
22 | * @return Wrapped method's return.
23 | * @throws SuspendExecution
24 | */
25 | public R call(final ExecutorService executor, SuspendableSupplier method) throws SuspendExecution {
26 | lock.lock();
27 | if (locked) {
28 | Coroutine coroutine = Coroutine.getActiveCoroutine();
29 | return Coroutine.yieldThen(() -> {
30 | queue.add(new Waiting(executor, coroutine, method));
31 | lock.unlock();
32 | });
33 | }
34 | locked = true;
35 | lock.unlock();
36 |
37 | try {
38 | return method.get();
39 | } finally {
40 | iterate();
41 | }
42 | }
43 |
44 | private void iterate() throws SuspendExecution {
45 | lock.lock();
46 | Waiting next = queue.poll();
47 | if (next == null) {
48 | locked = false;
49 | }
50 | lock.unlock();
51 | if (next != null) {
52 | Cohelp.submit(next.executor, () -> {
53 | try {
54 | final Object out = next.method.get();
55 | iterate();
56 | next.coroutine.process(out);
57 | } catch (final RuntimeException e) {
58 | iterate();
59 | next.coroutine.processThrow(e);
60 | }
61 | });
62 | }
63 | }
64 |
65 | static class Waiting {
66 | public final ExecutorService executor;
67 | public final Coroutine coroutine;
68 | public final SuspendableSupplier method;
69 |
70 | public Waiting(final ExecutorService executor, final Coroutine coroutine, final SuspendableSupplier method) {
71 | this.executor = executor;
72 | this.coroutine = coroutine;
73 | this.method = method;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/zarbosoft/coroutines/Generator.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 | import com.zarbosoft.coroutinescore.SuspendableRunnable;
5 |
6 | import java.util.Iterator;
7 | import java.util.Spliterator;
8 | import java.util.Spliterators;
9 | import java.util.stream.Stream;
10 | import java.util.stream.StreamSupport;
11 |
12 | import static com.zarbosoft.coroutines.Coroutine.yield;
13 |
14 | public class Generator {
15 | private final static Object END = new Object();
16 |
17 | private Generator() {
18 | }
19 |
20 | private T value;
21 |
22 | public void yieldValue(T value) throws SuspendExecution {
23 | this.value = value;
24 | yield();
25 | }
26 |
27 | public static Stream stream(SuspendableConsumer> runnable) {
28 | Generator g = new Generator();
29 | Coroutine c = new Coroutine(new SuspendableRunnable() {
30 | @Override
31 | public void run() throws SuspendExecution {
32 | runnable.apply(g);
33 | }
34 | });
35 | return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator() {
36 | @Override
37 | public boolean hasNext() {
38 | return !c.isFinished();
39 | }
40 |
41 | @Override
42 | public Object next() {
43 | c.process();
44 | if (c.isFinished())
45 | return END;
46 | return g.value;
47 | }
48 | }, Spliterator.ORDERED), false).filter(v -> v != END);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/zarbosoft/coroutines/ManualExecutor.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 | import com.zarbosoft.rendaw.common.Assertion;
5 |
6 | import java.time.Duration;
7 | import java.time.LocalDateTime;
8 | import java.time.ZoneOffset;
9 | import java.time.temporal.ChronoUnit;
10 | import java.util.ArrayList;
11 | import java.util.Collection;
12 | import java.util.Comparator;
13 | import java.util.List;
14 | import java.util.concurrent.*;
15 |
16 | public class ManualExecutor implements ScheduledExecutorService {
17 | private static class Scheduled {
18 | final LocalDateTime at;
19 | final Runnable runnable;
20 | final Duration repeat;
21 |
22 | private Scheduled(final LocalDateTime at, final Runnable runnable, final Duration repeat) {
23 | this.at = at;
24 | this.runnable = runnable;
25 | this.repeat = repeat;
26 | }
27 | }
28 |
29 | public List scheduled = new ArrayList<>();
30 | public LocalDateTime now = LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC);
31 |
32 | private void sortScheduled() {
33 | this.scheduled.sort(new Comparator() {
34 | @Override
35 | public int compare(final Scheduled o1, final Scheduled o2) {
36 | return o1.at.compareTo(o2.at);
37 | }
38 | });
39 | }
40 |
41 | private ScheduledFuture push(final Scheduled event) {
42 | this.scheduled.add(event);
43 | sortScheduled();
44 | return new ScheduledFuture() {
45 | @Override
46 | public long getDelay(final TimeUnit unit) {
47 | throw new AssertionError();
48 | }
49 |
50 | @Override
51 | public int compareTo(final Delayed o) {
52 | throw new AssertionError();
53 | }
54 |
55 | @Override
56 | public boolean cancel(final boolean mayInterruptIfRunning) {
57 | return scheduled.remove(event);
58 | }
59 |
60 | @Override
61 | public boolean isCancelled() {
62 | return scheduled.contains(event);
63 | }
64 |
65 | @Override
66 | public boolean isDone() {
67 | throw new AssertionError();
68 | }
69 |
70 | @Override
71 | public V get() throws InterruptedException, ExecutionException {
72 | throw new AssertionError();
73 | }
74 |
75 | @Override
76 | public V get(
77 | final long timeout, final TimeUnit unit
78 | ) throws InterruptedException, ExecutionException, TimeoutException {
79 | throw new AssertionError();
80 | }
81 | };
82 | }
83 |
84 | public void advance(final Duration amount) throws SuspendExecution {
85 | final Coroutine coroutine = Coroutine.getActiveCoroutine();
86 | Coroutine.yieldThen(() -> {
87 | now = now.plus(amount);
88 | final List reschedule = new ArrayList<>();
89 | for (final Scheduled event : scheduled) {
90 | if (event.at.compareTo(now) <= 0) {
91 | event.runnable.run();
92 | if (event.repeat != null)
93 | reschedule.add(event);
94 | } else {
95 | reschedule.add(event);
96 | }
97 | }
98 | scheduled = reschedule;
99 | sortScheduled();
100 | submit(() -> {
101 | try {
102 | coroutine.process(null);
103 | } catch (final Throwable e) {
104 | this.shutdown();
105 | }
106 | });
107 | });
108 | }
109 |
110 | private ChronoUnit toChronoUnit(final TimeUnit unit) {
111 | switch (unit) {
112 | case NANOSECONDS:
113 | case MICROSECONDS:
114 | return ChronoUnit.MICROS;
115 | case MILLISECONDS:
116 | return ChronoUnit.MILLIS;
117 | case SECONDS:
118 | return ChronoUnit.SECONDS;
119 | case MINUTES:
120 | return ChronoUnit.MINUTES;
121 | case HOURS:
122 | return ChronoUnit.HOURS;
123 | case DAYS:
124 | return ChronoUnit.DAYS;
125 | default:
126 | throw new AssertionError();
127 | }
128 | }
129 |
130 | @Override
131 | public ScheduledFuture> schedule(final Runnable command, final long delay, final TimeUnit unit) {
132 | return push(new Scheduled(now.plus(delay, toChronoUnit(unit)), command, null));
133 | }
134 |
135 | @Override
136 | public ScheduledFuture schedule(final Callable callable, final long delay, final TimeUnit unit) {
137 | return push(new Scheduled(now.plus(delay, toChronoUnit(unit)), () -> {
138 | try {
139 | callable.call();
140 | } catch (final Exception e) {
141 | e.printStackTrace();
142 | }
143 | }, null));
144 | }
145 |
146 | @Override
147 | public ScheduledFuture> scheduleAtFixedRate(
148 | final Runnable command, final long initialDelay, final long period, final TimeUnit unit
149 | ) {
150 | return push(new Scheduled(now.plus(initialDelay, toChronoUnit(unit)),
151 | command,
152 | Duration.of(period, toChronoUnit(unit))
153 | ));
154 | }
155 |
156 | @Override
157 | public ScheduledFuture> scheduleWithFixedDelay(
158 | final Runnable command, final long initialDelay, final long delay, final TimeUnit unit
159 | ) {
160 | return push(new Scheduled(now.plus(initialDelay, toChronoUnit(unit)),
161 | command,
162 | Duration.of(delay, toChronoUnit(unit))
163 | ));
164 | }
165 |
166 | @Override
167 | public void shutdown() {
168 | throw new AssertionError("Shutdown");
169 | }
170 |
171 | @Override
172 | public List shutdownNow() {
173 | throw new Assertion();
174 | }
175 |
176 | @Override
177 | public boolean isShutdown() {
178 | throw new Assertion();
179 | }
180 |
181 | @Override
182 | public boolean isTerminated() {
183 | throw new Assertion();
184 | }
185 |
186 | @Override
187 | public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException {
188 | throw new Assertion();
189 | }
190 |
191 | @Override
192 | public Future submit(final Callable task) {
193 | throw new Assertion();
194 | }
195 |
196 | @Override
197 | public Future submit(final Runnable task, final T result) {
198 | throw new Assertion();
199 | }
200 |
201 | @Override
202 | public Future> submit(final Runnable task) {
203 | task.run();
204 | return null;
205 | }
206 |
207 | @Override
208 | public List> invokeAll(final Collection extends Callable> tasks) throws InterruptedException {
209 | throw new Assertion();
210 | }
211 |
212 | @Override
213 | public List> invokeAll(
214 | final Collection extends Callable> tasks, final long timeout, final TimeUnit unit
215 | ) throws InterruptedException {
216 | throw new Assertion();
217 | }
218 |
219 | @Override
220 | public T invokeAny(final Collection extends Callable> tasks) throws InterruptedException, ExecutionException {
221 | throw new Assertion();
222 | }
223 |
224 | @Override
225 | public T invokeAny(
226 | final Collection extends Callable> tasks, final long timeout, final TimeUnit unit
227 | ) throws InterruptedException, ExecutionException, TimeoutException {
228 | throw new Assertion();
229 | }
230 |
231 | @Override
232 | public void execute(final Runnable command) {
233 | command.run();
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/src/main/java/com/zarbosoft/coroutines/NullaryBlocking.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | /**
4 | * Like runnable but can raise a checked exception.
5 | */
6 | @FunctionalInterface
7 | public interface NullaryBlocking {
8 | void run() throws Exception;
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/zarbosoft/coroutines/RWCriticalSection.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 | import com.zarbosoft.coroutinescore.SuspendableRunnable;
5 |
6 | import java.util.ArrayDeque;
7 | import java.util.concurrent.ExecutorService;
8 | import java.util.concurrent.locks.ReentrantLock;
9 |
10 | /**
11 | * Prioritizes reads
12 | */
13 | public class RWCriticalSection {
14 | final ReentrantLock lock = new ReentrantLock();
15 | final static int STATE_WRITING = -1;
16 | final static int STATE_UNLOCKED = 0;
17 | int state = STATE_UNLOCKED;
18 | ArrayDeque readQueue = new ArrayDeque<>();
19 | final ArrayDeque writeQueue = new ArrayDeque<>();
20 |
21 | public R read(final ExecutorService executor, SuspendableSupplier method) throws SuspendExecution {
22 | lock.lock();
23 | if (state == STATE_WRITING) {
24 | Coroutine coroutine = Coroutine.getActiveCoroutine();
25 | return Coroutine.yieldThen(() -> {
26 | readQueue.add(new CriticalSection.Waiting(executor, coroutine, method));
27 | lock.unlock();
28 | });
29 | }
30 | state += 1;
31 | lock.unlock();
32 |
33 | try {
34 | return method.get();
35 | } finally {
36 | lock.lock();
37 | state -= 1;
38 | if (state == STATE_UNLOCKED && !writeQueue.isEmpty()) {
39 | state = STATE_WRITING;
40 | }
41 | lock.unlock();
42 | if (state == STATE_WRITING)
43 | iterate();
44 | }
45 | }
46 |
47 | public R write(final ExecutorService executor, SuspendableSupplier method) throws SuspendExecution {
48 | lock.lock();
49 | if (state != STATE_UNLOCKED) {
50 | Coroutine coroutine = Coroutine.getActiveCoroutine();
51 | return Coroutine.yieldThen(() -> {
52 | writeQueue.add(new CriticalSection.Waiting(executor, coroutine, method));
53 | lock.unlock();
54 | });
55 | }
56 | state = STATE_WRITING;
57 | lock.unlock();
58 |
59 | try {
60 | return method.get();
61 | } finally {
62 | iterate();
63 | }
64 | }
65 |
66 | public boolean tryUniqueWrite(ExecutorService executor, SuspendableRunnable method) throws SuspendExecution {
67 | lock.lock();
68 | if (state != STATE_UNLOCKED) {
69 | if (state != STATE_WRITING && writeQueue.isEmpty()) {
70 | Coroutine coroutine = Coroutine.getActiveCoroutine();
71 | Coroutine.yieldThen(() -> {
72 | writeQueue.add(new CriticalSection.Waiting(executor, coroutine, () -> {
73 | method.run();
74 | return null;
75 | }));
76 | lock.unlock();
77 | });
78 | return true;
79 | } else {
80 | lock.unlock();
81 | return false;
82 | }
83 | }
84 | state = STATE_WRITING;
85 | lock.unlock();
86 |
87 | try {
88 | method.run();
89 | return true;
90 | } finally {
91 | iterate();
92 | }
93 | }
94 |
95 | void submit(CriticalSection.Waiting next) {
96 | Cohelp.submit(next.executor, () -> {
97 | try {
98 | final Object out = next.method.get();
99 | iterate();
100 | next.coroutine.process(out);
101 | } catch (final RuntimeException e) {
102 | iterate();
103 | next.coroutine.processThrow(e);
104 | }
105 | });
106 | }
107 |
108 | /**
109 | * Release pending readers, or else the next writer.
110 | *
111 | * @throws SuspendExecution
112 | */
113 | void iterate() throws SuspendExecution {
114 | ArrayDeque readers;
115 | ArrayDeque temp = new ArrayDeque<>();
116 |
117 | lock.lock();
118 | // Tally new readers, reduce by completed reader
119 | state = (state == STATE_WRITING ? 0 : state - 1) + readQueue.size();
120 |
121 | // Drain the reader queue for dispatch later here
122 | readers = readQueue;
123 | readQueue = temp;
124 |
125 | // If no readers, prep the next writer
126 | CriticalSection.Waiting writer;
127 | if (state == 0) {
128 | writer = writeQueue.poll();
129 | if (writer != null) {
130 | state = STATE_WRITING;
131 | }
132 | } else
133 | writer = null;
134 | lock.unlock();
135 |
136 | if (state == STATE_WRITING)
137 | submit(writer);
138 | else
139 | for (CriticalSection.Waiting reader : readers)
140 | submit(reader);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/main/java/com/zarbosoft/coroutines/SuspendableConsumer.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 |
5 | /**
6 | * Like Consumer but can suspend.
7 | *
8 | * @param
9 | */
10 | @FunctionalInterface
11 | public interface SuspendableConsumer {
12 | void apply(T t) throws SuspendExecution;
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/zarbosoft/coroutines/SuspendableFunction.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 |
5 | /**
6 | * Like Function but can suspend.
7 | *
8 | * @param
9 | * @param
10 | */
11 | @FunctionalInterface
12 | public interface SuspendableFunction {
13 | R apply(T t) throws SuspendExecution;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/zarbosoft/coroutines/SuspendableSupplier.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 |
5 | /**
6 | * Like Supplier but can suspend.
7 | *
8 | * @param
9 | */
10 | @FunctionalInterface
11 | public interface SuspendableSupplier {
12 | T get() throws SuspendExecution;
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/zarbosoft/coroutines/WRCriticalSection.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 |
5 | import java.util.concurrent.ExecutorService;
6 |
7 | /**
8 | * Prioritizes a single write, releasing batches of queued reads in between
9 | */
10 | public class WRCriticalSection extends RWCriticalSection {
11 | public R read(final ExecutorService executor, SuspendableSupplier method) throws SuspendExecution {
12 | lock.lock();
13 | if (!writeQueue.isEmpty()) {
14 | Coroutine coroutine = Coroutine.getActiveCoroutine();
15 | return Coroutine.yieldThen(() -> {
16 | readQueue.add(new CriticalSection.Waiting(executor, coroutine, method));
17 | lock.unlock();
18 | });
19 | }
20 | state += 1;
21 | lock.unlock();
22 |
23 | try {
24 | return method.get();
25 | } finally {
26 | lock.lock();
27 | state -= 1;
28 | CriticalSection.Waiting next;
29 | if (state == STATE_UNLOCKED) {
30 | next = writeQueue.poll();
31 | if (next != null) {
32 | state = STATE_WRITING;
33 | }
34 | } else next = null;
35 | lock.unlock();
36 | if (state == STATE_WRITING) {
37 | submit(next);
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/java/com/zarbosoft/coroutines/TestCriticalSection.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 | import com.zarbosoft.rendaw.common.Common;
5 | import org.junit.Test;
6 |
7 | import java.util.ArrayList;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 | import java.util.concurrent.ExecutorService;
12 |
13 | import static org.hamcrest.CoreMatchers.equalTo;
14 | import static org.hamcrest.number.OrderingComparison.greaterThan;
15 | import static org.junit.Assert.assertThat;
16 | import static org.junit.Assert.assertTrue;
17 |
18 | public class TestCriticalSection {
19 | private final TestCriticalSection.Gate gate;
20 | private final ManualExecutor executor;
21 | private final CriticalSection critical;
22 |
23 | public TestCriticalSection() {
24 | this.gate = new TestCriticalSection.Gate();
25 | this.executor = new ManualExecutor();
26 | this.critical = new CriticalSection();
27 | }
28 |
29 | public int invoke(int arg) throws SuspendExecution {
30 | return (Integer) critical.call(executor, () -> {
31 | assertThat(arg, greaterThan(4));
32 | gate.stop(arg);
33 | return arg * 2;
34 | });
35 | }
36 |
37 | @Test
38 | public void testCriticalSectionSingle() {
39 | final Coroutine coroutine1 = new Coroutine(() -> {
40 | assertThat(invoke(13), equalTo(26));
41 | });
42 | coroutine1.process();
43 | gate.start(13);
44 | assertTrue(coroutine1.isFinished());
45 | }
46 |
47 | @Test
48 | public void testCriticalSectionSingleNoSuspend() {
49 | final Coroutine coroutine1 = new Coroutine(() -> {
50 | assertThat(critical.call(executor, () -> 26), equalTo(26));
51 | });
52 | coroutine1.process();
53 | assertTrue(coroutine1.isFinished());
54 | }
55 |
56 | @Test
57 | public void testCriticalSectionSequential() {
58 | final Coroutine coroutine1 = new Coroutine(() -> {
59 | assertThat(invoke(13), equalTo(26));
60 | assertThat(invoke(14), equalTo(28));
61 | });
62 | coroutine1.process();
63 | gate.start(13);
64 | gate.start(14);
65 | assertTrue(coroutine1.isFinished());
66 | }
67 |
68 | @Test
69 | public void testCriticalSectionNoContention() {
70 | final Coroutine coroutine1 = new Coroutine(() -> {
71 | assertThat(invoke(7), equalTo(14));
72 | });
73 | final Coroutine coroutine2 = new Coroutine(() -> {
74 | assertThat(invoke(8), equalTo(16));
75 | });
76 | coroutine1.process();
77 | gate.start(7);
78 | assertTrue(coroutine1.isFinished());
79 | coroutine2.process();
80 | gate.start(8);
81 | assertTrue(coroutine2.isFinished());
82 | }
83 |
84 | @Test
85 | public void testCriticalSectionContention() {
86 | final Coroutine coroutine1 = new Coroutine(() -> {
87 | assertThat(invoke(7), equalTo(14));
88 | });
89 | final Coroutine coroutine2 = new Coroutine(() -> {
90 | assertThat(invoke(8), equalTo(16));
91 | });
92 | coroutine1.process();
93 | coroutine2.process();
94 | gate.start(7);
95 | assertTrue(coroutine1.isFinished());
96 | assertTrue(!coroutine2.isFinished());
97 | gate.start(8);
98 | assertTrue(coroutine2.isFinished());
99 | }
100 |
101 | static class Gate {
102 | Map gates = new HashMap<>();
103 |
104 | public void stop(int id) throws SuspendExecution {
105 | System.out.format("Arrived at gate %s\n", id);
106 | if (gates.containsKey(id))
107 | throw new AssertionError();
108 | gates.put(id, Coroutine.getActiveCoroutine());
109 | Coroutine.yield();
110 | }
111 |
112 | public void start(int id) {
113 | System.out.format("Leaving gate %s\n", id);
114 | Coroutine coroutine = gates.remove(id);
115 | if (coroutine == null)
116 | throw new NotAtGateFailure();
117 | coroutine.process();
118 | }
119 | }
120 |
121 | static class NotAtGateFailure extends RuntimeException {
122 |
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/test/java/com/zarbosoft/coroutines/TestGeneral.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import org.junit.Test;
4 |
5 | import static junit.framework.TestCase.assertTrue;
6 | import static org.hamcrest.CoreMatchers.equalTo;
7 | import static org.junit.Assert.assertThat;
8 |
9 | public class TestGeneral {
10 |
11 | @Test
12 | public void testBaseline() {
13 | final Coroutine coroutine = new Coroutine(() -> {
14 | Coroutine.yield();
15 | });
16 | coroutine.process();
17 | coroutine.process();
18 | assertTrue(coroutine.isFinished());
19 | }
20 |
21 | @Test(expected = com.zarbosoft.coroutinescore.Coroutine.Error.class)
22 | public void testCantRestartFinished() {
23 | final Coroutine coroutine = new Coroutine(() -> {
24 | });
25 | coroutine.process();
26 | coroutine.process();
27 | }
28 |
29 | @Test
30 | public void testInject() {
31 | final Coroutine coroutine = new Coroutine(() -> {
32 | assertThat(Coroutine.yield(), equalTo(4));
33 | });
34 | coroutine.process();
35 | coroutine.process(4);
36 | assertTrue(coroutine.isFinished());
37 | }
38 |
39 | @Test
40 | public void testInjectException() {
41 | class TestError extends RuntimeException {
42 |
43 | }
44 | final Coroutine coroutine = new Coroutine(() -> {
45 | try {
46 | Coroutine.yield();
47 | } catch (final TestError e) {
48 | return;
49 | }
50 | throw new AssertionError();
51 | });
52 | coroutine.process();
53 | coroutine.processThrow(new TestError());
54 | assertTrue(coroutine.isFinished());
55 | }
56 |
57 | @Test
58 | public void testYieldThen() {
59 | final Coroutine coroutine = new Coroutine(() -> {
60 | final Coroutine coroutine1 = Coroutine.getActiveCoroutine();
61 | Coroutine.yieldThen(() -> {
62 | coroutine1.process();
63 | });
64 | });
65 | coroutine.process();
66 | assertTrue(coroutine.isFinished());
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/com/zarbosoft/coroutines/TestRWCriticalSection.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 | import com.zarbosoft.rendaw.common.Common;
5 | import org.junit.Test;
6 |
7 | import java.util.*;
8 |
9 | import static org.hamcrest.CoreMatchers.equalTo;
10 | import static org.hamcrest.CoreMatchers.is;
11 | import static org.hamcrest.Matchers.greaterThan;
12 | import static org.junit.Assert.assertThat;
13 | import static org.junit.Assert.assertTrue;
14 |
15 | public class TestRWCriticalSection {
16 | private final TestCriticalSection.Gate gate;
17 | private final ManualExecutor executor;
18 | private final RWCriticalSection critical;
19 |
20 | public TestRWCriticalSection() {
21 | this.gate = new TestCriticalSection.Gate();
22 | this.executor = new ManualExecutor();
23 | this.critical = new RWCriticalSection();
24 | }
25 |
26 | public int invokeRead(int arg) throws SuspendExecution {
27 | return (Integer) critical.read(executor, () -> {
28 | assertThat(arg, greaterThan(4));
29 | gate.stop(arg);
30 | return arg * 2;
31 | });
32 | }
33 |
34 | public int invokeWrite(int arg) throws SuspendExecution {
35 | return (Integer) critical.write(executor, () -> {
36 | assertThat(arg, greaterThan(4));
37 | gate.stop(arg);
38 | return arg * 2;
39 | });
40 | }
41 |
42 | public boolean invokeTryUniqueWrite(int arg) throws SuspendExecution {
43 | return critical.tryUniqueWrite(executor, () -> {
44 | assertThat(arg, greaterThan(4));
45 | gate.stop(arg);
46 | });
47 | }
48 |
49 | @Test
50 | public void testOneReader() {
51 | final Coroutine coroutine1 = new Coroutine(() -> {
52 | assertThat(invokeRead(13), equalTo(26));
53 | });
54 | coroutine1.process();
55 | gate.start(13);
56 | assertTrue(coroutine1.isFinished());
57 | }
58 |
59 | @Test
60 | public void testOneReaderNoSuspend() {
61 | final Coroutine coroutine1 = new Coroutine(() -> {
62 | assertThat(critical.read(executor, () -> 26), equalTo(26));
63 | });
64 | coroutine1.process();
65 | assertTrue(coroutine1.isFinished());
66 | }
67 |
68 | @Test
69 | public void testSequentialReaders() {
70 | Common.Mutable value = new Common.Mutable<>(0);
71 | final Coroutine coroutine1 = new Coroutine(() -> {
72 | assertThat(invokeRead(13), equalTo(26));
73 | assertThat(invokeRead(14), equalTo(28));
74 | value.value = 3;
75 | });
76 | coroutine1.process();
77 | gate.start(13);
78 | gate.start(14);
79 | assertThat(value.value, equalTo(3));
80 | assertTrue(coroutine1.isFinished());
81 | }
82 |
83 | @Test
84 | public void testParallelReaders() {
85 | List value = new ArrayList<>();
86 | final Coroutine coroutine1 = new Coroutine(() -> {
87 | assertThat(invokeRead(13), equalTo(26));
88 | value.add(3);
89 | });
90 | final Coroutine coroutine2 = new Coroutine(() -> {
91 | assertThat(invokeRead(14), equalTo(28));
92 | value.add(4);
93 | });
94 | coroutine1.process();
95 | coroutine2.process();
96 | gate.start(14);
97 | gate.start(13);
98 | assertThat(value, equalTo(Arrays.asList(4, 3)));
99 | assertTrue(coroutine1.isFinished());
100 | assertTrue(coroutine2.isFinished());
101 | }
102 |
103 | @Test
104 | public void testOneWriter() {
105 | Common.Mutable value = new Common.Mutable<>(0);
106 | final Coroutine coroutine1 = new Coroutine(() -> {
107 | assertThat(invokeWrite(13), equalTo(26));
108 | value.value = 3;
109 | });
110 | coroutine1.process();
111 | gate.start(13);
112 | assertThat(value.value, equalTo(3));
113 | assertTrue(coroutine1.isFinished());
114 | }
115 |
116 | @Test
117 | public void testOneWriterNoSuspend() {
118 | final Coroutine coroutine1 = new Coroutine(() -> {
119 | assertThat(critical.write(executor, () -> 26), equalTo(26));
120 | });
121 | coroutine1.process();
122 | assertTrue(coroutine1.isFinished());
123 | }
124 |
125 | @Test
126 | public void testSequentialWriters() {
127 | List value = new ArrayList<>();
128 | final Coroutine coroutine1 = new Coroutine(() -> {
129 | assertThat(invokeWrite(13), equalTo(26));
130 | value.add(3);
131 | });
132 | final Coroutine coroutine2 = new Coroutine(() -> {
133 | assertThat(invokeWrite(14), equalTo(28));
134 | value.add(4);
135 | });
136 | coroutine1.process();
137 | gate.start(13);
138 | coroutine2.process();
139 | gate.start(14);
140 | assertTrue(coroutine1.isFinished());
141 | assertTrue(coroutine2.isFinished());
142 | assertThat(value, equalTo(Arrays.asList(3, 4)));
143 | }
144 |
145 | @Test
146 | public void testBlockingWriters() {
147 | List value = new ArrayList<>();
148 | final Coroutine coroutine1 = new Coroutine(() -> {
149 | assertThat(invokeWrite(13), equalTo(26));
150 | value.add(3);
151 | });
152 | final Coroutine coroutine2 = new Coroutine(() -> {
153 | assertThat(invokeWrite(14), equalTo(28));
154 | value.add(4);
155 | });
156 | coroutine1.process();
157 | coroutine2.process();
158 | gate.start(13);
159 | gate.start(14);
160 | assertTrue(coroutine1.isFinished());
161 | assertTrue(coroutine2.isFinished());
162 | assertThat(value, equalTo(Arrays.asList(3, 4)));
163 | }
164 |
165 | @Test(expected = TestCriticalSection.NotAtGateFailure.class)
166 | public void testBlockingWritersBlocked() {
167 | List value = new ArrayList<>();
168 | final Coroutine coroutine1 = new Coroutine(() -> {
169 | assertThat(invokeWrite(13), equalTo(26));
170 | value.add(3);
171 | });
172 | final Coroutine coroutine2 = new Coroutine(() -> {
173 | assertThat(invokeWrite(14), equalTo(28));
174 | value.add(4);
175 | });
176 | coroutine1.process();
177 | coroutine2.process();
178 | gate.start(14);
179 | }
180 |
181 | @Test
182 | public void testTryWritePass() {
183 | final Coroutine coroutine1 = new Coroutine(() -> {
184 | assertThat(invokeTryUniqueWrite(8), is(true));
185 | });
186 | coroutine1.process();
187 | gate.start(8);
188 | assertTrue(coroutine1.isFinished());
189 | }
190 |
191 | @Test
192 | public void testWriteTryWriteBlocked() {
193 | List value = new ArrayList<>();
194 | final Coroutine coroutine1 = new Coroutine(() -> {
195 | assertThat(invokeTryUniqueWrite(7), is(true));
196 | value.add(3);
197 | });
198 | final Coroutine coroutine2 = new Coroutine(() -> {
199 | assertThat(invokeTryUniqueWrite(9), is(false));
200 | value.add(4);
201 | });
202 | coroutine1.process();
203 | coroutine2.process();
204 | assertTrue(coroutine2.isFinished());
205 | gate.start(7);
206 | assertTrue(coroutine1.isFinished());
207 | assertThat(value, equalTo(Arrays.asList(4, 3)));
208 | }
209 |
210 | @Test
211 | public void testReadBlockWrite() {
212 | List value = new ArrayList<>();
213 | final Coroutine coroutine1 = new Coroutine(() -> {
214 | assertThat(invokeRead(13), equalTo(26));
215 | value.add(3);
216 | });
217 | final Coroutine coroutine2 = new Coroutine(() -> {
218 | assertThat(invokeWrite(14), equalTo(28));
219 | value.add(4);
220 | });
221 | coroutine1.process();
222 | coroutine2.process();
223 | gate.start(13);
224 | gate.start(14);
225 | assertThat(value, equalTo(Arrays.asList(3, 4)));
226 | assertTrue(coroutine1.isFinished());
227 | assertTrue(coroutine2.isFinished());
228 | }
229 |
230 | @Test(expected = TestCriticalSection.NotAtGateFailure.class)
231 | public void testReadBlockWriteBlocked() {
232 | List value = new ArrayList<>();
233 | final Coroutine coroutine1 = new Coroutine(() -> {
234 | assertThat(invokeRead(13), equalTo(26));
235 | value.add(3);
236 | });
237 | final Coroutine coroutine2 = new Coroutine(() -> {
238 | assertThat(invokeWrite(14), equalTo(28));
239 | value.add(4);
240 | });
241 | coroutine1.process();
242 | coroutine2.process();
243 | gate.start(14);
244 | }
245 |
246 | @Test
247 | public void testReadTryWritePass() {
248 | List value = new ArrayList<>();
249 | final Coroutine coroutine1 = new Coroutine(() -> {
250 | assertThat(invokeRead(13), equalTo(26));
251 | value.add(3);
252 | });
253 | final Coroutine coroutine2 = new Coroutine(() -> {
254 | assertThat(invokeTryUniqueWrite(14), is(true));
255 | value.add(4);
256 | });
257 | coroutine1.process();
258 | coroutine2.process();
259 | gate.start(13);
260 | gate.start(14);
261 | assertTrue(coroutine1.isFinished());
262 | assertTrue(coroutine2.isFinished());
263 | assertThat(value, equalTo(Arrays.asList(3, 4)));
264 | }
265 |
266 | @Test
267 | public void testReadTryWriteBlocked() {
268 | List value = new ArrayList<>();
269 | final Coroutine coroutine1 = new Coroutine(() -> {
270 | assertThat(invokeRead(13), equalTo(26));
271 | value.add(3);
272 | });
273 | final Coroutine coroutine2 = new Coroutine(() -> {
274 | assertThat(invokeWrite(14), equalTo(28));
275 | value.add(4);
276 | });
277 | final Coroutine coroutine3 = new Coroutine(() -> {
278 | assertThat(invokeTryUniqueWrite(15), is(false));
279 | value.add(5);
280 | });
281 | coroutine1.process();
282 | coroutine2.process();
283 | coroutine3.process();
284 | assertTrue(coroutine3.isFinished());
285 | gate.start(13);
286 | assertTrue(coroutine1.isFinished());
287 | gate.start(14);
288 | assertTrue(coroutine2.isFinished());
289 | assertThat(value, equalTo(Arrays.asList(5, 3, 4)));
290 | }
291 |
292 | /**
293 | * Additional reads will run while a read is in progress even if write blocked.
294 | */
295 | @Test
296 | public void testAdditionalReadBlockWrite() {
297 | List value = new ArrayList<>();
298 | final Coroutine coroutine1 = new Coroutine(() -> {
299 | assertThat(invokeRead(13), equalTo(26));
300 | value.add(3);
301 | });
302 | final Coroutine coroutine2 = new Coroutine(() -> {
303 | assertThat(invokeWrite(14), equalTo(28));
304 | value.add(4);
305 | });
306 | final Coroutine coroutine3 = new Coroutine(() -> {
307 | assertThat(invokeRead(15), equalTo(30));
308 | value.add(5);
309 | });
310 | coroutine1.process();
311 | coroutine2.process();
312 | coroutine3.process();
313 | gate.start(13);
314 | gate.start(15);
315 | gate.start(14);
316 | assertThat(value, equalTo(Arrays.asList(3, 5, 4)));
317 | assertTrue(coroutine1.isFinished());
318 | assertTrue(coroutine2.isFinished());
319 | assertTrue(coroutine3.isFinished());
320 | }
321 |
322 |
323 | @Test
324 | public void testWriteBlockRead() {
325 | List value = new ArrayList<>();
326 | final Coroutine coroutine1 = new Coroutine(() -> {
327 | assertThat(invokeRead(13), equalTo(26));
328 | value.add(3);
329 | });
330 | final Coroutine coroutine2 = new Coroutine(() -> {
331 | assertThat(invokeWrite(14), equalTo(28));
332 | value.add(4);
333 | });
334 | coroutine2.process();
335 | coroutine1.process();
336 | gate.start(14);
337 | gate.start(13);
338 | assertThat(value, equalTo(Arrays.asList(4, 3)));
339 | assertTrue(coroutine1.isFinished());
340 | assertTrue(coroutine2.isFinished());
341 | }
342 |
343 | @Test(expected = TestCriticalSection.NotAtGateFailure.class)
344 | public void testWriteBlockReadBlocked() {
345 | List value = new ArrayList<>();
346 | final Coroutine coroutine1 = new Coroutine(() -> {
347 | assertThat(invokeRead(13), equalTo(26));
348 | value.add(3);
349 | });
350 | final Coroutine coroutine2 = new Coroutine(() -> {
351 | assertThat(invokeWrite(14), equalTo(28));
352 | value.add(4);
353 | });
354 | coroutine2.process();
355 | coroutine1.process();
356 | gate.start(13);
357 | }
358 |
359 | @Test
360 | public void testReadBetweenWrites() {
361 | List value = new ArrayList<>();
362 | final Coroutine coroutine1 = new Coroutine(() -> {
363 | assertThat(invokeWrite(13), equalTo(26));
364 | value.add(3);
365 | });
366 | final Coroutine coroutine2 = new Coroutine(() -> {
367 | assertThat(invokeWrite(14), equalTo(28));
368 | value.add(4);
369 | });
370 | final Coroutine coroutine3 = new Coroutine(() -> {
371 | assertThat(invokeRead(15), equalTo(30));
372 | value.add(5);
373 | });
374 | coroutine1.process();
375 | coroutine2.process();
376 | coroutine3.process();
377 | gate.start(13);
378 | gate.start(15);
379 | gate.start(14);
380 | assertThat(value, equalTo(Arrays.asList(3, 5, 4)));
381 | assertTrue(coroutine1.isFinished());
382 | assertTrue(coroutine2.isFinished());
383 | assertTrue(coroutine3.isFinished());
384 | }
385 |
386 | /**
387 | * Move to additional reads block write state after initial write finishes.
388 | */
389 | @Test
390 | public void testTransitionReadBlockWrite() {
391 | List value = new ArrayList<>();
392 | final Coroutine coroutine1 = new Coroutine(() -> {
393 | assertThat(invokeWrite(13), equalTo(26));
394 | value.add(3);
395 | });
396 | final Coroutine coroutine2 = new Coroutine(() -> {
397 | assertThat(invokeWrite(14), equalTo(28));
398 | value.add(4);
399 | });
400 | final Coroutine coroutine3 = new Coroutine(() -> {
401 | assertThat(invokeRead(15), equalTo(30));
402 | value.add(5);
403 | });
404 | final Coroutine coroutine4 = new Coroutine(() -> {
405 | assertThat(invokeRead(16), equalTo(32));
406 | value.add(6);
407 | });
408 | coroutine1.process();
409 | coroutine2.process();
410 | coroutine3.process();
411 | gate.start(13);
412 | coroutine4.process();
413 | gate.start(16);
414 | gate.start(15);
415 | gate.start(14);
416 | assertThat(value, equalTo(Arrays.asList(3, 6, 5, 4)));
417 | assertTrue(coroutine1.isFinished());
418 | assertTrue(coroutine2.isFinished());
419 | assertTrue(coroutine3.isFinished());
420 | assertTrue(coroutine4.isFinished());
421 | }
422 | }
423 |
--------------------------------------------------------------------------------
/src/test/java/com/zarbosoft/coroutines/TestWRCriticalSection.java:
--------------------------------------------------------------------------------
1 | package com.zarbosoft.coroutines;
2 |
3 | import com.zarbosoft.coroutinescore.SuspendExecution;
4 | import org.junit.Test;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Arrays;
8 | import java.util.List;
9 |
10 | import static org.hamcrest.Matchers.equalTo;
11 | import static org.hamcrest.Matchers.greaterThan;
12 | import static org.junit.Assert.assertThat;
13 | import static org.junit.Assert.assertTrue;
14 |
15 | public class TestWRCriticalSection {
16 | private final TestCriticalSection.Gate gate;
17 | private final ManualExecutor executor;
18 | private final WRCriticalSection critical;
19 |
20 | public TestWRCriticalSection() {
21 | this.gate = new TestCriticalSection.Gate();
22 | this.executor = new ManualExecutor();
23 | this.critical = new WRCriticalSection();
24 | }
25 |
26 | public int invokeRead(int arg) throws SuspendExecution {
27 | return (Integer) critical.read(executor, () -> {
28 | assertThat(arg, greaterThan(4));
29 | gate.stop(arg);
30 | return arg * 2;
31 | });
32 | }
33 |
34 | public int invokeWrite(int arg) throws SuspendExecution {
35 | return (Integer) critical.write(executor, () -> {
36 | assertThat(arg, greaterThan(4));
37 | gate.stop(arg);
38 | return arg * 2;
39 | });
40 | }
41 |
42 | @Test
43 | public void testOneRead() {
44 | final Coroutine coroutine1 = new Coroutine(() -> {
45 | assertThat(invokeRead(13), equalTo(26));
46 | });
47 | coroutine1.process();
48 | gate.start(13);
49 | assertTrue(coroutine1.isFinished());
50 | }
51 |
52 | @Test
53 | public void testParallelRead() {
54 | List value = new ArrayList<>();
55 | final Coroutine coroutine1 = new Coroutine(() -> {
56 | assertThat(invokeRead(13), equalTo(26));
57 | value.add(3);
58 | });
59 | final Coroutine coroutine2 = new Coroutine(() -> {
60 | assertThat(invokeRead(14), equalTo(28));
61 | value.add(4);
62 | });
63 | coroutine1.process();
64 | coroutine2.process();
65 | gate.start(14);
66 | gate.start(13);
67 | assertThat(value, equalTo(Arrays.asList(4, 3)));
68 | assertTrue(coroutine1.isFinished());
69 | assertTrue(coroutine2.isFinished());
70 | }
71 |
72 | @Test
73 | public void testWriteBlockRead() {
74 | List value = new ArrayList<>();
75 | final Coroutine coroutine1 = new Coroutine(() -> {
76 | assertThat(invokeRead(13), equalTo(26));
77 | value.add(3);
78 | });
79 | final Coroutine coroutine2 = new Coroutine(() -> {
80 | assertThat(invokeWrite(14), equalTo(28));
81 | value.add(4);
82 | });
83 | final Coroutine coroutine3 = new Coroutine(() -> {
84 | assertThat(invokeRead(15), equalTo(30));
85 | value.add(5);
86 | });
87 | coroutine1.process();
88 | coroutine2.process();
89 | coroutine3.process();
90 | gate.start(13);
91 | gate.start(14);
92 | gate.start(15);
93 | assertThat(value, equalTo(Arrays.asList(3, 4, 5)));
94 | assertTrue(coroutine1.isFinished());
95 | assertTrue(coroutine2.isFinished());
96 | assertTrue(coroutine3.isFinished());
97 | }
98 |
99 | @Test
100 | public void testReleaseReadBetweenWrite() {
101 | List value = new ArrayList<>();
102 | final Coroutine coroutine1 = new Coroutine(() -> {
103 | assertThat(invokeRead(13), equalTo(26));
104 | value.add(3);
105 | });
106 | final Coroutine coroutine2 = new Coroutine(() -> {
107 | assertThat(invokeWrite(14), equalTo(28));
108 | value.add(4);
109 | });
110 | final Coroutine coroutine3 = new Coroutine(() -> {
111 | assertThat(invokeWrite(15), equalTo(30));
112 | value.add(5);
113 | });
114 | final Coroutine coroutine4 = new Coroutine(() -> {
115 | assertThat(invokeRead(16), equalTo(32));
116 | value.add(6);
117 | });
118 | coroutine1.process();
119 | coroutine2.process();
120 | coroutine3.process();
121 | coroutine4.process();
122 | gate.start(13);
123 | assertTrue(coroutine1.isFinished());
124 | gate.start(14);
125 | assertTrue(coroutine2.isFinished());
126 | gate.start(16);
127 | assertTrue(coroutine4.isFinished());
128 | gate.start(15);
129 | assertTrue(coroutine3.isFinished());
130 | assertThat(value, equalTo(Arrays.asList(3, 4, 6, 5)));
131 | }
132 | }
133 |
--------------------------------------------------------------------------------