From e31837fcf03d6cd88a186ba7eaf9adb05166ef50 Mon Sep 17 00:00:00 2001 From: "yunyao.zxl" Date: Wed, 22 Jul 2020 17:07:00 +0800 Subject: [PATCH] [Coroutine] Import coroutine patch Summary: - From http://hg.openjdk.java.net/mlvm/mlvm/jdk/file/93a6c619b0ca/coro-simple.patch - Add 'EnableCoroutine' option to test/java/dyn/CoroutineTest.java Test Plan: test/java/dyn/CoroutineTest.java jtreg test Reviewed-by: yuleil, shiyuexw, sanhong Issue: https://github.com/alibaba/dragonwell8/issues/113 --- src/share/classes/java/dyn/AsymCoroutine.java | 179 ++++++ src/share/classes/java/dyn/AsymRunnable.java | 30 + src/share/classes/java/dyn/Coroutine.java | 121 ++++ src/share/classes/java/dyn/CoroutineBase.java | 94 +++ .../java/dyn/CoroutineExitException.java | 45 ++ .../classes/java/dyn/CoroutineLocal.java | 541 ++++++++++++++++++ .../classes/java/dyn/CoroutineSupport.java | 313 ++++++++++ src/share/classes/java/lang/Thread.java | 17 + src/share/classes/sun/misc/VM.java | 10 + test/java/dyn/CoroutineTest.java | 320 +++++++++++ 10 files changed, 1670 insertions(+) create mode 100644 src/share/classes/java/dyn/AsymCoroutine.java create mode 100644 src/share/classes/java/dyn/AsymRunnable.java create mode 100644 src/share/classes/java/dyn/Coroutine.java create mode 100644 src/share/classes/java/dyn/CoroutineBase.java create mode 100644 src/share/classes/java/dyn/CoroutineExitException.java create mode 100644 src/share/classes/java/dyn/CoroutineLocal.java create mode 100644 src/share/classes/java/dyn/CoroutineSupport.java create mode 100644 test/java/dyn/CoroutineTest.java diff --git a/src/share/classes/java/dyn/AsymCoroutine.java b/src/share/classes/java/dyn/AsymCoroutine.java new file mode 100644 index 000000000..3a34d026e --- /dev/null +++ b/src/share/classes/java/dyn/AsymCoroutine.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.dyn; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Implementation of asymmetric coroutines. A AsymCoroutine can be called by any coroutine (Coroutine and AsymCoroutine) and will return to + * its caller upon {@link #ret()}. + *

+ * Similar to {@link Thread} there are two ways to implement a AsymCoroutine: either by implementing a subclass of AsymCoroutine (and + * overriding {@link #run()}) or by providing a {@link Runnable} to the AsymCoroutine constructor. + *

+ * An implementation of a simple AsymCoroutine that always returns the average of all its previous inputs might look like this: + *

+ *


+ *
+ * + *
+ * class Average extends AsymCoroutine<Integer, Integer> {
+ * 	public Integer run(Integer value) {
+ * 		int sum = value;
+ * 		int count = 1;
+ * 		while (true) {
+ * 			sum += ret(sum / count++);
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *
+ *
+ *

+ * This AsymCoroutine can be invoked either by reading and writing the {@link #output} and {@link #input} fields and invoking the + * {@link #call()} method: + *

+ *

+ * + *
+ * Average avg = new Average();
+ * avg.input = 10;
+ * avg.call();
+ * System.out.println(avg.output);
+ * 
+ * + *
+ *

+ * Another way to invoke this AsymCoroutine is by using the shortcut {@link #call(Object)} methods: + *

+ *

+ * + *
+ * Average avg = new Average();
+ * System.out.println(avg.call(10));
+ * 
+ * + *
+ *

+ * + * @author Lukas Stadler + * + * @param + * input type of this AsymCoroutine, Void if no input value is expected + * @param + * output type of this AsymCoroutine, Void if no output is produced + */ +public class AsymCoroutine extends CoroutineBase implements Iterable { + CoroutineBase caller; + + private final AsymRunnable target; + + private InT input; + private OutT output; + + public AsymCoroutine() { + target = null; + threadSupport.addCoroutine(this, -1); + } + + public AsymCoroutine(long stacksize) { + target = null; + threadSupport.addCoroutine(this, stacksize); + } + + public AsymCoroutine(AsymRunnable target) { + this.target = target; + threadSupport.addCoroutine(this, -1); + } + + public AsymCoroutine(AsymRunnable target, long stacksize) { + this.target = target; + threadSupport.addCoroutine(this, stacksize); + } + + public final OutT call(final InT input) { + this.input = input; + Thread.currentThread().getCoroutineSupport().asymmetricCall(this); + return output; + } + + public final OutT call() { + Thread.currentThread().getCoroutineSupport().asymmetricCall(this); + return output; + } + + public final InT ret(final OutT value) { + output = value; + Thread.currentThread().getCoroutineSupport().asymmetricReturn(this); + return input; + } + + public final InT ret() { + Thread.currentThread().getCoroutineSupport().asymmetricReturn(this); + return input; + } + + protected OutT run(InT value) { + return target.run(this, value); + } + + private static class Iter implements Iterator { + private final AsymCoroutine fiber; + + public Iter(final AsymCoroutine fiber) { + this.fiber = fiber; + } + + @Override + public boolean hasNext() { + return !fiber.isFinished(); + } + + @Override + public OutT next() { + if (fiber.isFinished()) + throw new NoSuchElementException(); + return fiber.call(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + } + + @Override + public final Iterator iterator() { + return new Iter(this); + } + + protected final void run() { + output = run(input); + } +} diff --git a/src/share/classes/java/dyn/AsymRunnable.java b/src/share/classes/java/dyn/AsymRunnable.java new file mode 100644 index 000000000..e28f35284 --- /dev/null +++ b/src/share/classes/java/dyn/AsymRunnable.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.dyn; + +public interface AsymRunnable { + public OutT run(AsymCoroutine coroutine, InT value); +} diff --git a/src/share/classes/java/dyn/Coroutine.java b/src/share/classes/java/dyn/Coroutine.java new file mode 100644 index 000000000..479d4335f --- /dev/null +++ b/src/share/classes/java/dyn/Coroutine.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.dyn; + +/** + * Implementation of symmetric coroutines. A Coroutine will take part in thread-wide scheduling of coroutines. It transfers control to + * the next coroutine whenever yield is called. + *

+ * Similar to {@link Thread} there are two ways to implement a Coroutine: either by implementing a subclass of Coroutine (and overriding + * {@link #run()}) or by providing a {@link Runnable} to the Coroutine constructor. + *

+ * An implementation of a simple Coroutine might look like this: + *

+ *


+ *
+ * + *
+ * class Numbers extends Coroutine {
+ * 	public void run() {
+ * 		for (int i = 0; i < 10; i++) {
+ * 			System.out.println(i);
+ * 			yield();
+ * 		}
+ * 	}
+ * }
+ * 
+ * + *
+ *
+ *

+ * A Coroutine is active as soon as it is created, and will run as soon as control is transferred to it: + *

+ *

+ * + *
+ * new Numbers();
+ * for (int i = 0; i < 10; i++)
+ * 	yield();
+ * 
+ * + *
+ *

+ * @author Lukas Stadler + */ +public class Coroutine extends CoroutineBase { + private final Runnable target; + + Coroutine next; + Coroutine last; + + public Coroutine() { + this.target = null; + threadSupport.addCoroutine(this, -1); + } + + public Coroutine(Runnable target) { + this.target = target; + threadSupport.addCoroutine(this, -1); + } + + public Coroutine(long stacksize) { + this.target = null; + threadSupport.addCoroutine(this, stacksize); + } + + public Coroutine(Runnable target, long stacksize) { + this.target = target; + threadSupport.addCoroutine(this, stacksize); + } + + // creates the initial coroutine for a new thread + Coroutine(CoroutineSupport threadSupport, long data) { + super(threadSupport, data); + this.target = null; + } + + /** + * Yields execution to the next coroutine in the current threads coroutine queue. + */ + public static void yield() { + Thread.currentThread().getCoroutineSupport().symmetricYield(); + } + + public static void yieldTo(Coroutine target) { + Thread.currentThread().getCoroutineSupport().symmetricYieldTo(target); + } + + public void stop() { + Thread.currentThread().getCoroutineSupport().symmetricStopCoroutine(this); + } + + protected void run() { + assert Thread.currentThread() == threadSupport.getThread(); + if (target != null) { + target.run(); + } + } +} \ No newline at end of file diff --git a/src/share/classes/java/dyn/CoroutineBase.java b/src/share/classes/java/dyn/CoroutineBase.java new file mode 100644 index 000000000..aab2d5914 --- /dev/null +++ b/src/share/classes/java/dyn/CoroutineBase.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.dyn; + +public abstract class CoroutineBase { + transient long data; + + transient CoroutineLocal.CoroutineLocalMap coroutineLocals = null; + + boolean finished = false; + + transient CoroutineSupport threadSupport; + + CoroutineBase() { + Thread thread = Thread.currentThread(); + assert thread.getCoroutineSupport() != null; + this.threadSupport = thread.getCoroutineSupport(); + } + + // creates the initial coroutine for a new thread + CoroutineBase(CoroutineSupport threadSupport, long data) { + this.threadSupport = threadSupport; + this.data = data; + } + + protected abstract void run(); + + @SuppressWarnings({ "unused" }) + private final void startInternal() { + assert threadSupport.getThread() == Thread.currentThread(); + try { + if (CoroutineSupport.DEBUG) { + System.out.println("starting coroutine " + this); + } + run(); + } catch (Throwable t) { + if (!(t instanceof CoroutineExitException)) { + t.printStackTrace(); + } + } finally { + finished = true; + // use Thread.currentThread().getCoroutineSupport() because we might have been migrated to another thread! + if (this instanceof Coroutine) { + Thread.currentThread().getCoroutineSupport().terminateCoroutine(); + } else { + Thread.currentThread().getCoroutineSupport().terminateCallable(); + } + } + assert threadSupport.getThread() == Thread.currentThread(); + } + + /** + * Returns true if this coroutine has reached its end. Under normal circumstances this happens when the {@link #run()} method returns. + */ + public final boolean isFinished() { + return finished; + } + + /** + * @return the thread that this coroutine is associated with + * @throws NullPointerException + * if the coroutine has terminated + */ + public Thread getThread() { + return threadSupport.getThread(); + } + + public static CoroutineBase current() { + return Thread.currentThread().getCoroutineSupport().getCurrent(); + } +} diff --git a/src/share/classes/java/dyn/CoroutineExitException.java b/src/share/classes/java/dyn/CoroutineExitException.java new file mode 100644 index 000000000..c4a12c6ab --- /dev/null +++ b/src/share/classes/java/dyn/CoroutineExitException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.dyn; + +public class CoroutineExitException extends RuntimeException { + private static final long serialVersionUID = -2651365020938997924L; + + public CoroutineExitException() { + } + + public CoroutineExitException(String message, Throwable cause) { + super(message, cause); + } + + public CoroutineExitException(String message) { + super(message); + } + + public CoroutineExitException(Throwable cause) { + super(cause); + } +} diff --git a/src/share/classes/java/dyn/CoroutineLocal.java b/src/share/classes/java/dyn/CoroutineLocal.java new file mode 100644 index 000000000..604689237 --- /dev/null +++ b/src/share/classes/java/dyn/CoroutineLocal.java @@ -0,0 +1,541 @@ +/* + * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.dyn; + +import java.lang.Object; +import java.lang.Thread; +import java.lang.UnsupportedOperationException; +import java.lang.ref.*; +import java.util.concurrent.atomic.AtomicInteger; + +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class CoroutineLocal { + private final int coroutineLocalHashCode = nextHashCode(); + private static AtomicInteger nextHashCode = new AtomicInteger(); + private static final int HASH_INCREMENT = 0x61c88647; + + private static int nextHashCode() { + return nextHashCode.getAndAdd(HASH_INCREMENT); + } + + protected T initialValue() { + return null; + } + + public CoroutineLocal() { + } + + public T get() { + assert Thread.currentThread().getCoroutineSupport() != null; + CoroutineBase t = Thread.currentThread().getCoroutineSupport().getCurrent(); + CoroutineLocalMap map = getMap(t); + if (map != null) { + CoroutineLocalMap.Entry e = map.getEntry(this); + if (e != null) + return (T) e.value; + } + return setInitialValue(); + } + + /** + * Variant of set() to establish initialValue. Used instead of set() in case user has overridden the set() method. + * + * @return the initial value + */ + private T setInitialValue() { + T value = initialValue(); + assert Thread.currentThread().getCoroutineSupport() != null; + CoroutineBase t = Thread.currentThread().getCoroutineSupport().getCurrent(); + CoroutineLocalMap map = getMap(t); + if (map != null) + map.set(this, value); + else + createMap(t, value); + return value; + } + + /** + * Sets the current thread's copy of this thread-local variable to the specified value. Most subclasses will have no need to override + * this method, relying solely on the {@link #initialValue} method to set the values of thread-locals. + * + * @param value the value to be stored in the current thread's copy of this thread-local. + */ + public void set(T value) { + assert Thread.currentThread().getCoroutineSupport() != null; + CoroutineBase t = Thread.currentThread().getCoroutineSupport().getCurrent(); + CoroutineLocalMap map = getMap(t); + if (map != null) + map.set(this, value); + else + createMap(t, value); + } + + /** + * Removes the current thread's value for this thread-local variable. If this thread-local variable is subsequently {@linkplain #get + * read} by the current thread, its value will be reinitialized by invoking its {@link #initialValue} method, unless its value is + * {@linkplain #set set} by the current thread in the interim. This may result in multiple invocations of the initialValue + * method in the current thread. + * + * @since 1.5 + */ + public void remove() { + assert Thread.currentThread().getCoroutineSupport() != null; + CoroutineLocalMap m = getMap(Thread.currentThread().getCoroutineSupport().getCurrent()); + if (m != null) + m.remove(this); + } + + CoroutineLocalMap getMap(CoroutineBase t) { + return t.coroutineLocals; + } + + /** + * Create the map associated with a ThreadLocal. Overridden in InheritableThreadLocal. + * + * @param t the current thread + * @param firstValue value for the initial entry of the map + * @param map the map to store. + */ + void createMap(CoroutineBase t, T firstValue) { + t.coroutineLocals = new CoroutineLocalMap(this, firstValue); + } + + /** + * Factory method to create map of inherited thread locals. Designed to be called only from Thread constructor. + * + * @param parentMap the map associated with parent thread + * @return a map containing the parent's inheritable bindings + */ + static CoroutineLocalMap createInheritedMap(CoroutineLocalMap parentMap) { + return new CoroutineLocalMap(parentMap); + } + + /** + * Method childValue is visibly defined in subclass InheritableThreadLocal, but is internally defined here for the sake of providing + * createInheritedMap factory method without needing to subclass the map class in InheritableThreadLocal. This technique is preferable + * to the alternative of embedding instanceof tests in methods. + */ + T childValue(T parentValue) { + throw new UnsupportedOperationException(); + } + + /** + * ThreadLocalMap is a customized hash map suitable only for maintaining thread local values. No operations are exported outside of the + * ThreadLocal class. The class is package private to allow declaration of fields in class Thread. To help deal with very large and + * long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries + * are guaranteed to be removed only when the table starts running out of space. + */ + static class CoroutineLocalMap { + + /** + * The entries in this hash map extend WeakReference, using its main ref field as the key (which is always a ThreadLocal object). + * Note that null keys (i.e. entry.get() == null) mean that the key is no longer referenced, so the entry can be expunged from + * table. Such entries are referred to as "stale entries" in the code that follows. + */ + static class Entry extends WeakReference { + /** The value associated with this ThreadLocal. */ + Object value; + + Entry(CoroutineLocal k, Object v) { + super(k); + value = v; + } + } + + /** + * The initial capacity -- MUST be a power of two. + */ + private static final int INITIAL_CAPACITY = 16; + + /** + * The table, resized as necessary. table.length MUST always be a power of two. + */ + private Entry[] table; + + /** + * The number of entries in the table. + */ + private int size = 0; + + /** + * The next size value at which to resize. + */ + private int threshold; // Default to 0 + + /** + * Set the resize threshold to maintain at worst a 2/3 load factor. + */ + private void setThreshold(int len) { + threshold = len * 2 / 3; + } + + /** + * Increment i modulo len. + */ + private static int nextIndex(int i, int len) { + return ((i + 1 < len) ? i + 1 : 0); + } + + /** + * Decrement i modulo len. + */ + private static int prevIndex(int i, int len) { + return ((i - 1 >= 0) ? i - 1 : len - 1); + } + + /** + * Construct a new map initially containing (firstKey, firstValue). ThreadLocalMaps are constructed lazily, so we only create one + * when we have at least one entry to put in it. + */ + CoroutineLocalMap(CoroutineLocal firstKey, Object firstValue) { + table = new Entry[INITIAL_CAPACITY]; + int i = firstKey.coroutineLocalHashCode & (INITIAL_CAPACITY - 1); + table[i] = new Entry(firstKey, firstValue); + size = 1; + setThreshold(INITIAL_CAPACITY); + } + + /** + * Construct a new map including all Inheritable ThreadLocals from given parent map. Called only by createInheritedMap. + * + * @param parentMap the map associated with parent thread. + */ + private CoroutineLocalMap(CoroutineLocalMap parentMap) { + Entry[] parentTable = parentMap.table; + int len = parentTable.length; + setThreshold(len); + table = new Entry[len]; + + for (int j = 0; j < len; j++) { + Entry e = parentTable[j]; + if (e != null) { + CoroutineLocal key = e.get(); + if (key != null) { + Object value = key.childValue(e.value); + Entry c = new Entry(key, value); + int h = key.coroutineLocalHashCode & (len - 1); + while (table[h] != null) + h = nextIndex(h, len); + table[h] = c; + size++; + } + } + } + } + + /** + * Get the entry associated with key. This method itself handles only the fast path: a direct hit of existing key. It otherwise + * relays to getEntryAfterMiss. This is designed to maximize performance for direct hits, in part by making this method readily + * inlinable. + * + * @param key the thread local object + * @return the entry associated with key, or null if no such + */ + private Entry getEntry(CoroutineLocal key) { + int i = key.coroutineLocalHashCode & (table.length - 1); + Entry e = table[i]; + if (e != null && e.get() == key) + return e; + else + return getEntryAfterMiss(key, i, e); + } + + /** + * Version of getEntry method for use when key is not found in its direct hash slot. + * + * @param key the thread local object + * @param i the table index for key's hash code + * @param e the entry at table[i] + * @return the entry associated with key, or null if no such + */ + private Entry getEntryAfterMiss(CoroutineLocal key, int i, Entry e) { + Entry[] tab = table; + int len = tab.length; + + while (e != null) { + CoroutineLocal k = e.get(); + if (k == key) + return e; + if (k == null) + expungeStaleEntry(i); + else + i = nextIndex(i, len); + e = tab[i]; + } + return null; + } + + /** + * Set the value associated with key. + * + * @param key the thread local object + * @param value the value to be set + */ + private void set(CoroutineLocal key, Object value) { + + // We don't use a fast path as with get() because it is at + // least as common to use set() to create new entries as + // it is to replace existing ones, in which case, a fast + // path would fail more often than not. + + Entry[] tab = table; + int len = tab.length; + int i = key.coroutineLocalHashCode & (len - 1); + + for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { + CoroutineLocal k = e.get(); + + if (k == key) { + e.value = value; + return; + } + + if (k == null) { + replaceStaleEntry(key, value, i); + return; + } + } + + tab[i] = new Entry(key, value); + int sz = ++size; + if (!cleanSomeSlots(i, sz) && sz >= threshold) + rehash(); + } + + /** + * Remove the entry for key. + */ + private void remove(CoroutineLocal key) { + Entry[] tab = table; + int len = tab.length; + int i = key.coroutineLocalHashCode & (len - 1); + for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { + if (e.get() == key) { + e.clear(); + expungeStaleEntry(i); + return; + } + } + } + + /** + * Replace a stale entry encountered during a set operation with an entry for the specified key. The value passed in the value + * parameter is stored in the entry, whether or not an entry already exists for the specified key. + * + * As a side effect, this method expunges all stale entries in the "run" containing the stale entry. (A run is a sequence of entries + * between two null slots.) + * + * @param key the key + * @param value the value to be associated with key + * @param staleSlot index of the first stale entry encountered while searching for key. + */ + private void replaceStaleEntry(CoroutineLocal key, Object value, int staleSlot) { + Entry[] tab = table; + int len = tab.length; + Entry e; + + // Back up to check for prior stale entry in current run. + // We clean out whole runs at a time to avoid continual + // incremental rehashing due to garbage collector freeing + // up refs in bunches (i.e., whenever the collector runs). + int slotToExpunge = staleSlot; + for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) + if (e.get() == null) + slotToExpunge = i; + + // Find either the key or trailing null slot of run, whichever + // occurs first + for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { + CoroutineLocal k = e.get(); + + // If we find key, then we need to swap it + // with the stale entry to maintain hash table order. + // The newly stale slot, or any other stale slot + // encountered above it, can then be sent to expungeStaleEntry + // to remove or rehash all of the other entries in run. + if (k == key) { + e.value = value; + + tab[i] = tab[staleSlot]; + tab[staleSlot] = e; + + // Start expunge at preceding stale entry if it exists + if (slotToExpunge == staleSlot) + slotToExpunge = i; + cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); + return; + } + + // If we didn't find stale entry on backward scan, the + // first stale entry seen while scanning for key is the + // first still present in the run. + if (k == null && slotToExpunge == staleSlot) + slotToExpunge = i; + } + + // If key not found, put new entry in stale slot + tab[staleSlot].value = null; + tab[staleSlot] = new Entry(key, value); + + // If there are any other stale entries in run, expunge them + if (slotToExpunge != staleSlot) + cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); + } + + /** + * Expunge a stale entry by rehashing any possibly colliding entries lying between staleSlot and the next null slot. This also + * expunges any other stale entries encountered before the trailing null. See Knuth, Section 6.4 + * + * @param staleSlot index of slot known to have null key + * @return the index of the next null slot after staleSlot (all between staleSlot and this slot will have been checked for + * expunging). + */ + private int expungeStaleEntry(int staleSlot) { + Entry[] tab = table; + int len = tab.length; + + // expunge entry at staleSlot + tab[staleSlot].value = null; + tab[staleSlot] = null; + size--; + + // Rehash until we encounter null + Entry e; + int i; + for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { + CoroutineLocal k = e.get(); + if (k == null) { + e.value = null; + tab[i] = null; + size--; + } + else { + int h = k.coroutineLocalHashCode & (len - 1); + if (h != i) { + tab[i] = null; + + // Unlike Knuth 6.4 Algorithm R, we must scan until + // null because multiple entries could have been stale. + while (tab[h] != null) + h = nextIndex(h, len); + tab[h] = e; + } + } + } + return i; + } + + /** + * Heuristically scan some cells looking for stale entries. This is invoked when either a new element is added, or another stale one + * has been expunged. It performs a logarithmic number of scans, as a balance between no scanning (fast but retains garbage) and a + * number of scans proportional to number of elements, that would find all garbage but would cause some insertions to take O(n) + * time. + * + * @param i a position known NOT to hold a stale entry. The scan starts at the element after i. + * + * @param n scan control: log2(n) cells are scanned, unless a stale entry is found, in which case + * log2(table.length)-1 additional cells are scanned. When called from insertions, this parameter is the number + * of elements, but when from replaceStaleEntry, it is the table length. (Note: all this could be changed to be either + * more or less aggressive by weighting n instead of just using straight log n. But this version is simple, fast, and + * seems to work well.) + * + * @return true if any stale entries have been removed. + */ + private boolean cleanSomeSlots(int i, int n) { + boolean removed = false; + Entry[] tab = table; + int len = tab.length; + do { + i = nextIndex(i, len); + Entry e = tab[i]; + if (e != null && e.get() == null) { + n = len; + removed = true; + i = expungeStaleEntry(i); + } + } + while ((n >>>= 1) != 0); + return removed; + } + + /** + * Re-pack and/or re-size the table. First scan the entire table removing stale entries. If this doesn't sufficiently shrink the + * size of the table, double the table size. + */ + private void rehash() { + expungeStaleEntries(); + + // Use lower threshold for doubling to avoid hysteresis + if (size >= threshold - threshold / 4) + resize(); + } + + /** + * Double the capacity of the table. + */ + private void resize() { + Entry[] oldTab = table; + int oldLen = oldTab.length; + int newLen = oldLen * 2; + Entry[] newTab = new Entry[newLen]; + int count = 0; + + for (int j = 0; j < oldLen; ++j) { + Entry e = oldTab[j]; + if (e != null) { + CoroutineLocal k = e.get(); + if (k == null) { + e.value = null; // Help the GC + } + else { + int h = k.coroutineLocalHashCode & (newLen - 1); + while (newTab[h] != null) + h = nextIndex(h, newLen); + newTab[h] = e; + count++; + } + } + } + + setThreshold(newLen); + size = count; + table = newTab; + } + + /** + * Expunge all stale entries in the table. + */ + private void expungeStaleEntries() { + Entry[] tab = table; + int len = tab.length; + for (int j = 0; j < len; j++) { + Entry e = tab[j]; + if (e != null && e.get() == null) + expungeStaleEntry(j); + } + } + } +} diff --git a/src/share/classes/java/dyn/CoroutineSupport.java b/src/share/classes/java/dyn/CoroutineSupport.java new file mode 100644 index 000000000..cc245cd4a --- /dev/null +++ b/src/share/classes/java/dyn/CoroutineSupport.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.dyn; + +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +public class CoroutineSupport { + // Controls debugging and tracing, for maximum performance the actual if(DEBUG/TRACE) code needs to be commented out + static final boolean DEBUG = false; + static final boolean TRACE = false; + + static final Object TERMINATED = new Object(); + + // The thread that this CoroutineSupport belongs to. There's only one CoroutineSupport per Thread + private final Thread thread; + // The initial coroutine of the Thread + private final Coroutine threadCoroutine; + + // The currently executing, symmetric or asymmetric coroutine + CoroutineBase currentCoroutine; + // The anchor of the doubly-linked ring of coroutines + Coroutine scheduledCoroutines; + + static { + registerNatives(); + } + + public CoroutineSupport(Thread thread) { + if (thread.getCoroutineSupport() != null) { + throw new IllegalArgumentException("Cannot instantiate CoroutineThreadSupport for existing Thread"); + } + this.thread = thread; + threadCoroutine = new Coroutine(this, getThreadCoroutine()); + threadCoroutine.next = threadCoroutine; + threadCoroutine.last = threadCoroutine; + currentCoroutine = threadCoroutine; + scheduledCoroutines = threadCoroutine; + } + + public Coroutine threadCoroutine() { + return threadCoroutine; + } + + void addCoroutine(Coroutine coroutine, long stacksize) { + assert scheduledCoroutines != null; + assert currentCoroutine != null; + + coroutine.data = createCoroutine(coroutine, stacksize); + if (DEBUG) { + System.out.println("add Coroutine " + coroutine + ", data" + coroutine.data); + } + + // add the coroutine into the doubly linked ring + coroutine.next = scheduledCoroutines.next; + coroutine.last = scheduledCoroutines; + scheduledCoroutines.next = coroutine; + coroutine.next.last = coroutine; + } + + void addCoroutine(AsymCoroutine coroutine, long stacksize) { + coroutine.data = createCoroutine(coroutine, stacksize); + if (DEBUG) { + System.out.println("add AsymCoroutine " + coroutine + ", data" + coroutine.data); + } + + coroutine.caller = null; + } + + Thread getThread() { + return thread; + } + + public void drain() { + if (Thread.currentThread() != thread) { + throw new IllegalArgumentException("Cannot drain another threads CoroutineThreadSupport"); + } + + if (DEBUG) { + System.out.println("draining"); + } + try { + // drain all scheduled coroutines + while (scheduledCoroutines.next != scheduledCoroutines) { + symmetricExitInternal(scheduledCoroutines.next); + } + + CoroutineBase coro; + while ((coro = cleanupCoroutine()) != null) { + System.out.println(coro); + throw new NotImplementedException(); + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + + void symmetricYield() { + if (scheduledCoroutines != currentCoroutine) { + throw new IllegalThreadStateException("Cannot call yield from within an asymmetric coroutine"); + } + assert currentCoroutine instanceof Coroutine; + + if (TRACE) { + System.out.println("locking for symmetric yield..."); + } + + Coroutine next = scheduledCoroutines.next; + if (next == scheduledCoroutines) { + return; + } + + if (TRACE) { + System.out.println("symmetric yield to " + next); + } + + final Coroutine current = scheduledCoroutines; + scheduledCoroutines = next; + currentCoroutine = next; + + switchTo(current, next); + } + + public void symmetricYieldTo(Coroutine target) { + if (scheduledCoroutines != currentCoroutine) { + throw new IllegalThreadStateException("Cannot call yield from within an asymmetric coroutine"); + } + assert currentCoroutine instanceof Coroutine; + + moveCoroutine(scheduledCoroutines, target); + + final Coroutine current = scheduledCoroutines; + scheduledCoroutines = target; + currentCoroutine = target; + + switchTo(current, target); + } + + private void moveCoroutine(Coroutine a, Coroutine position) { + // remove a from the ring + a.last.next = a.next; + a.next.last = a.last; + + // ... and insert at the new position + a.next = position.next; + a.last = position; + a.next.last = a; + position.next = a; + } + + public void symmetricStopCoroutine(Coroutine target) { + if (scheduledCoroutines != currentCoroutine) { + throw new IllegalThreadStateException("Cannot call yield from within an asymmetric coroutine"); + } + assert currentCoroutine instanceof Coroutine; + + moveCoroutine(scheduledCoroutines, target); + + final Coroutine current = scheduledCoroutines; + scheduledCoroutines = target; + currentCoroutine = target; + + switchToAndExit(current, target); + } + + void symmetricExitInternal(Coroutine coroutine) { + if (scheduledCoroutines != currentCoroutine) { + throw new IllegalThreadStateException("Cannot call exitNext from within an unscheduled coroutine"); + } + assert currentCoroutine instanceof Coroutine; + assert currentCoroutine != coroutine; + + // remove the coroutine from the ring + coroutine.last.next = coroutine.next; + coroutine.next.last = coroutine.last; + + if (!isDisposable(coroutine.data)) { + // and insert it before the current coroutine + coroutine.last = scheduledCoroutines.last; + coroutine.next = scheduledCoroutines; + coroutine.last.next = coroutine; + scheduledCoroutines.last = coroutine; + + final Coroutine current = scheduledCoroutines; + scheduledCoroutines = coroutine; + currentCoroutine = coroutine; + switchToAndExit(current, coroutine); + } + } + + void asymmetricCall(AsymCoroutine target) { + if (target.threadSupport != this) { + throw new IllegalArgumentException("Cannot activate a coroutine that belongs to another thread"); + } + if (target.caller != null) { + throw new IllegalArgumentException("Coroutine already in use"); + } + if (target.data == 0) { + throw new IllegalArgumentException("Target coroutine has already finished"); + } + if (TRACE) { + System.out.println("yieldCall " + target + " (" + target.data + ")"); + } + + final CoroutineBase current = currentCoroutine; + target.caller = current; + currentCoroutine = target; + switchTo(target.caller, target); + } + + void asymmetricReturn(final AsymCoroutine current) { + if (current != currentCoroutine) { + throw new IllegalThreadStateException("cannot return from non-current fiber"); + } + final CoroutineBase caller = current.caller; + if (TRACE) { + System.out.println("yieldReturn " + caller + " (" + caller.data + ")"); + } + + current.caller = null; + currentCoroutine = caller; + switchTo(current, currentCoroutine); + } + + void asymmetricReturnAndTerminate(final AsymCoroutine current) { + if (current != currentCoroutine) { + throw new IllegalThreadStateException("cannot return from non-current fiber"); + } + final CoroutineBase caller = current.caller; + if (TRACE) { + System.out.println("yieldReturn " + caller + " (" + caller.data + ")"); + } + + current.caller = null; + currentCoroutine = caller; + switchToAndTerminate(current, currentCoroutine); + } + + void terminateCoroutine() { + assert currentCoroutine == scheduledCoroutines; + assert currentCoroutine != threadCoroutine : "cannot exit thread coroutine"; + assert scheduledCoroutines != scheduledCoroutines.next : "last coroutine shouldn't call coroutineexit"; + + Coroutine old = scheduledCoroutines; + Coroutine forward = old.next; + currentCoroutine = forward; + scheduledCoroutines = forward; + old.last.next = old.next; + old.next.last = old.last; + + if (DEBUG) { + System.out.println("to be terminated: " + old); + } + switchToAndTerminate(old, forward); + } + + void terminateCallable() { + assert currentCoroutine != scheduledCoroutines; + assert currentCoroutine instanceof AsymCoroutine; + + if (DEBUG) { + System.out.println("to be terminated: " + currentCoroutine); + } + asymmetricReturnAndTerminate((AsymCoroutine) currentCoroutine); + } + + public boolean isCurrent(CoroutineBase coroutine) { + return coroutine == currentCoroutine; + } + + public CoroutineBase getCurrent() { + return currentCoroutine; + } + + private static native void registerNatives(); + + private static native long getThreadCoroutine(); + + private static native long createCoroutine(CoroutineBase coroutine, long stacksize); + + private static native void switchTo(CoroutineBase current, CoroutineBase target); + + private static native void switchToAndTerminate(CoroutineBase current, CoroutineBase target); + + private static native void switchToAndExit(CoroutineBase current, CoroutineBase target); + + private static native boolean isDisposable(long coroutine); + + private static native CoroutineBase cleanupCoroutine(); + +} diff --git a/src/share/classes/java/lang/Thread.java b/src/share/classes/java/lang/Thread.java index 1592d50e9..005a47e38 100644 --- a/src/share/classes/java/lang/Thread.java +++ b/src/share/classes/java/lang/Thread.java @@ -25,6 +25,7 @@ package java.lang; +import java.dyn.CoroutineSupport; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; @@ -274,6 +275,18 @@ class Thread implements Runnable { */ public final static int MAX_PRIORITY = 10; + private CoroutineSupport coroutineSupport; + + public CoroutineSupport getCoroutineSupport() { + return coroutineSupport; + } + + private void initializeCoroutineSupport() { + if (sun.misc.VM.isEnableCoroutine()) { + coroutineSupport = new CoroutineSupport(this); + } + } + /** * Returns a reference to the currently executing thread object. * @@ -781,6 +794,10 @@ class Thread implements Runnable { * a chance to clean up before it actually exits. */ private void exit() { + if (sun.misc.VM.isEnableCoroutine() && (coroutineSupport != null)) { + coroutineSupport.drain(); + } + if (group != null) { group.threadTerminated(this); group = null; diff --git a/src/share/classes/sun/misc/VM.java b/src/share/classes/sun/misc/VM.java index 3e64628c6..2e2c1cc49 100644 --- a/src/share/classes/sun/misc/VM.java +++ b/src/share/classes/sun/misc/VM.java @@ -179,6 +179,12 @@ public class VM { } } + private static boolean enableCoroutine; + + public static boolean isEnableCoroutine() { + return enableCoroutine; + } + // A user-settable upper limit on the maximum amount of allocatable direct // buffer memory. This value may be changed during VM initialization if // "java" is launched with "-XX:MaxDirectMemorySize=". @@ -276,6 +282,10 @@ public class VM { savedProps.putAll(props); + //Set the enableCoroutine flag, This value is controlled + //by the vm option -XX:+EnableCoroutine + enableCoroutine = Boolean.valueOf((String) props.remove("com.alibaba.coroutine.enableCoroutine")); + // Set the maximum amount of direct memory. This value is controlled // by the vm option -XX:MaxDirectMemorySize=. // The maximum amount of allocatable direct buffer memory (in bytes) diff --git a/test/java/dyn/CoroutineTest.java b/test/java/dyn/CoroutineTest.java new file mode 100644 index 000000000..968fc9ac1 --- /dev/null +++ b/test/java/dyn/CoroutineTest.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary unit tests for coroutines + * @run junit/othervm -XX:+EnableCoroutine test.java.dyn.CoroutineTest + */ + +package test.java.dyn; + +import java.dyn.Coroutine; +import java.dyn.AsymCoroutine; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +public class CoroutineTest { + private StringBuilder seq; + + @Before + public void before() { + seq = new StringBuilder(); + seq.append("a"); + } + + @Test + public void symSequence() { + Coroutine coro = new Coroutine() { + protected void run() { + seq.append("c"); + for (int i = 0; i < 3; i++) { + yield(); + seq.append("e"); + } + } + }; + seq.append("b"); + assertFalse(coro.isFinished()); + Coroutine.yield(); + for (int i = 0; i < 3; i++) { + seq.append("d"); + assertFalse(coro.isFinished()); + Coroutine.yield(); + } + seq.append("f"); + assertTrue(coro.isFinished()); + Coroutine.yield(); + seq.append("g"); + assertEquals("abcdededefg", seq.toString()); + } + + @Test + public void symMultiSequence() { + for (int i = 0; i < 10; i++) + new Coroutine() { + protected void run() { + seq.append("c"); + yield(); + seq.append("e"); + } + }; + seq.append("b"); + Coroutine.yield(); + seq.append("d"); + Coroutine.yield(); + seq.append("f"); + Coroutine.yield(); + seq.append("g"); + assertEquals("abccccccccccdeeeeeeeeeefg", seq.toString()); + } + + @Test + public void asymSequence() { + AsymCoroutine coro = new AsymCoroutine() { + protected Void run(Void value) { + seq.append(value + "b"); + Object o = ret(); + seq.append(o + "d"); + return null; + } + }; + assertFalse(coro.isFinished()); + coro.call(); + assertFalse(coro.isFinished()); + seq.append("c"); + coro.call(); + seq.append("e"); + assertTrue(coro.isFinished()); + + RuntimeException exception = null; + try { + coro.call(); + } catch (RuntimeException e) { + exception = e; + } + assertNotNull(exception); + assertEquals("anullbcnullde", seq.toString()); + } + + @Test + public void asymMultiSequence() { + AsymCoroutine coro = null; + for (int j = 4; j >= 0; j--) { + final AsymCoroutine last = coro; + final int i = j; + coro = new AsymCoroutine() { + protected Void run(Void value) { + seq.append("b" + i); + if (last != null) + last.call(); + seq.append("c" + i); + ret(); + seq.append("e" + i); + if (last != null) + last.call(); + seq.append("f" + i); + return null; + } + }; + } + seq.append("_"); + assertFalse(coro.isFinished()); + coro.call(); + assertFalse(coro.isFinished()); + seq.append("d"); + coro.call(); + seq.append("g"); + assertTrue(coro.isFinished()); + + RuntimeException exception = null; + try { + coro.call(); + } catch (RuntimeException e) { + exception = e; + } + assertNotNull(exception); + assertEquals("a_b0b1b2b3b4c4c3c2c1c0de0e1e2e3e4f4f3f2f1f0g", seq.toString()); + } + + @Test + public void asymReturnValue() { + AsymCoroutine coro = new AsymCoroutine() { + protected Integer run(Integer value) { + value = ret(value * 2 + 1); + value = ret(value * 2 + 2); + value = ret(value * 2 + 3); + value = ret(value * 2 + 4); + value = ret(value * 2 + 5); + return value * 2 + 6; + } + }; + assertFalse(coro.isFinished()); + assertEquals(2001, (int) coro.call(1000)); + assertEquals(4002, (int) coro.call(2000)); + assertEquals(6003, (int) coro.call(3000)); + assertEquals(8004, (int) coro.call(4000)); + assertEquals(10005, (int) coro.call(5000)); + assertEquals(12006, (int) coro.call(6000)); + assertTrue(coro.isFinished()); + } + + @Test + public void gcTest1() { + new Coroutine() { + protected void run() { + seq.append("c"); + Integer v1 = 1; + Integer v2 = 14555668; + yield(); + seq.append("e"); + seq.append("(" + v1 + "," + v2 + ")"); + } + }; + seq.append("b"); + System.gc(); + Coroutine.yield(); + System.gc(); + seq.append("d"); + Coroutine.yield(); + seq.append("f"); + Coroutine.yield(); + seq.append("g"); + assertEquals("abcde(1,14555668)fg", seq.toString()); + } + + @Test + public void exceptionTest1() { + Coroutine coro = new Coroutine() { + protected void run() { + seq.append("c"); + long temp = System.nanoTime(); + if (temp != 0) + throw new RuntimeException(); + yield(); + seq.append("e"); + } + }; + seq.append("b"); + assertFalse(coro.isFinished()); + Coroutine.yield(); + seq.append("d"); + Coroutine.yield(); + seq.append("f"); + assertEquals("abcdf", seq.toString()); + } + + @Test + public void largeStackframeTest() { + new Coroutine() { + protected void run() { + seq.append("c"); + Integer v0 = 10000; + Integer v1 = 10001; + Integer v2 = 10002; + Integer v3 = 10003; + Integer v4 = 10004; + Integer v5 = 10005; + Integer v6 = 10006; + Integer v7 = 10007; + Integer v8 = 10008; + Integer v9 = 10009; + Integer v10 = 10010; + Integer v11 = 10011; + Integer v12 = 10012; + Integer v13 = 10013; + Integer v14 = 10014; + Integer v15 = 10015; + Integer v16 = 10016; + Integer v17 = 10017; + Integer v18 = 10018; + Integer v19 = 10019; + yield(); + int sum = v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 + v12 + v13 + v14 + v15 + v16 + v17 + v18 + v19; + seq.append("e" + sum); + } + }; + seq.append("b"); + System.gc(); + Coroutine.yield(); + System.gc(); + seq.append("d"); + Coroutine.yield(); + seq.append("f"); + assertEquals("abcde200190f", seq.toString()); + } + + @Test + public void shaTest() { + Coroutine coro = new Coroutine(65536) { + protected void run() { + try { + MessageDigest digest = MessageDigest.getInstance("SHA"); + digest.update("TestMessage".getBytes()); + seq.append("b"); + yield(); + seq.append(digest.digest()[0]); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + }; + Coroutine.yield(); + seq.append("c"); + assertFalse(coro.isFinished()); + Coroutine.yield(); + assertTrue(coro.isFinished()); + assertEquals("abc72", seq.toString()); + } + + public void stackoverflowTest() { + for (int i = 0; i < 10; i++) { + new Coroutine(65536) { + int i = 0; + + protected void run() { + System.out.println("start"); + try { + iter(); + } catch (StackOverflowError e) { + System.out.println("i: " + i); + } + System.out.println("asdf"); + } + + private void iter() { + System.out.print("."); + i++; + iter(); + } + }; + } + Coroutine.yield(); + } +} -- GitLab