Skip to content

Commit 97c942b

Browse files
committed
For #250 #273 jdk19 native coroutine support
1 parent cc0b301 commit 97c942b

File tree

10 files changed

+1324
-33
lines changed

10 files changed

+1324
-33
lines changed

.travis.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,22 @@ jobs:
109109
- lein with-profile cljremotetest test :all
110110
- cd /home/who/git/nginx-clojure/test/nginx-working-dir
111111
- ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-coroutine.conf -s stop
112+
- stage: jdk19 native coroutine test
113+
script:
114+
- apt-get install openjdk-19-jdk -y
115+
- java -version
116+
- cd /home/who/git/nginx-clojure/
117+
- lein with-profile nativeCoroutine jar
118+
- cd /home/who/git/nginx-clojure/test/nginx-working-dir
119+
- ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-coroutine-jdk19.conf &
120+
- sleep 30
121+
- tail -f logs/error.log &
122+
- curl -v http://localhost:8080/clojure
123+
- killall tail
124+
- cd /home/who/git/nginx-clojure/
125+
- lein with-profile cljremotetest test :all
126+
- cd /home/who/git/nginx-clojure/test/nginx-working-dir
127+
- ./nginx -c /home/who/git/nginx-clojure/test/nginx-working-dir/conf/nginx-coroutine-jdk19.conf -s stop
112128

113129
after_failure:
114130
- echo "******************************** Test Failed (Start of Nginx error.log)*****************"

project.clj

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,60 @@
7272
[javax.xml.bind/jaxb-api "2.3.1"]
7373
[org.clojure/tools.trace "0.7.10"]
7474
]}
75-
:unittest {
75+
:nativeCoroutine {
76+
:source-paths ["src/clojure"]
77+
:target-path "target/"
78+
:global-vars {*warn-on-reflection* true
79+
*assert* false}
80+
:java-source-paths ["src/java", "src/nativeCoroutine"]
81+
:dependencies [;only for test / compile usage
82+
[org.clojure/clojure "1.9.0"]
83+
[ring/ring-core "1.7.1"]
84+
[compojure "1.1.6"]
85+
[clj-http "0.7.8"]
86+
[clj-http-lite "0.3.0"]
87+
[junit/junit "4.13.1"]
88+
[org.clojure/java.jdbc "0.3.3"]
89+
[mysql/mysql-connector-java "5.1.30"]
90+
[redis.clients/jedis "3.1.0"]
91+
;for test file upload with ring-core which need it
92+
[javax.servlet/servlet-api "2.5"]
93+
[org.clojure/data.json "0.2.5"]
94+
[org.codehaus.jackson/jackson-mapper-asl "1.9.13"]
95+
[org.codehaus.groovy/groovy "2.5.8"]
96+
[stylefruits/gniazdo "1.1.2"]
97+
[javax.xml.bind/jaxb-api "2.3.1"]
98+
[org.clojure/tools.trace "0.7.10"]
99+
]
100+
}
101+
:jdk17unittest {
102+
:jvm-opts ["-javaagent:target/nginx-clojure-0.6.0.jar=mb"
103+
"-Dfile.encoding=UTF-8"
104+
"-Dnginx.clojure.wave.udfs=pure-clj.txt,compojure.txt,compojure-http-clj.txt,mysql-jdbc.txt,test-groovy.txt"
105+
"--add-opens=java.base/java.lang=ALL-UNNAMED" "--add-opens=java.base/sun.nio.cs=ALL-UNNAMED" "--add-opens=java.base/sun.nio.ch=ALL-UNNAMED"
106+
"-Xbootclasspath/a:target/nginx-clojure-0.6.0.jar"]
107+
:junit-options {:fork "on"}
108+
:java-source-paths ["test/java" "test/clojure"]
109+
:test-paths ["src/test/clojure"]
110+
:source-paths ["test/clojure" "test/java" "test/nginx-working-dir/coroutine-udfs"]
111+
:junit ["test/java"]
112+
:compile-path "target/testclasses"
113+
:dependencies [
114+
[org.clojure/clojure "1.9.0"]
115+
[ring/ring-core "1.7.1"]
116+
[compojure "1.1.6"]
117+
[clj-http "0.7.8"]
118+
[clj-http-lite "0.3.0"]
119+
[junit/junit "4.13.1"]
120+
[org.clojure/java.jdbc "0.3.3"]
121+
[org.codehaus.jackson/jackson-mapper-asl "1.9.13"]
122+
[javax.xml.bind/jaxb-api "2.3.1"]
123+
;[mysql/mysql-connector-java "5.1.30"]
124+
[redis.clients/jedis "3.1.0"]
125+
[org.clojure/tools.trace "0.7.10"]
126+
]
127+
}
128+
:unittest {
76129
:jvm-opts ["-javaagent:target/nginx-clojure-0.6.0.jar=mb"
77130
"-Dfile.encoding=UTF-8"
78131
"-Dnginx.clojure.wave.udfs=pure-clj.txt,compojure.txt,compojure-http-clj.txt,mysql-jdbc.txt,test-groovy.txt"

src/java/nginx/clojure/Coroutine.java

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import java.io.IOException;
3333
import java.io.Serializable;
3434

35+
import nginx.clojure.NativeCoroutineBuilder.NativeCoroutine;
36+
3537

3638
/**
3739
* <p>A Coroutine is used to run a CoroutineProto.</p>
@@ -53,6 +55,12 @@ public class Coroutine implements Runnable, Serializable {
5355

5456
private static final long serialVersionUID = 2783452871536981L;
5557

58+
private static boolean useNative = false;
59+
60+
private static NativeCoroutineBuilder nativeCoroutineBuilder;
61+
62+
private NativeCoroutine nativeCoroutine;
63+
5664
public enum State {
5765
/** The Coroutine has not yet been executed */
5866
NEW,
@@ -79,14 +87,22 @@ public interface FinishAwaredRunnable extends Runnable {
7987
private Object locals;
8088
private Object inheritableLocals;
8189

90+
public static boolean isUseNative() {
91+
return useNative;
92+
}
93+
8294
/**
8395
* Suspend the currently running Coroutine on the calling thread.
8496
*
8597
* @throws de.matthiasmann.continuations.SuspendExecution This exception is used for control transfer - don't catch it !
8698
* @throws java.lang.IllegalStateException If not called from a Coroutine
8799
*/
88100
public static void yield() throws SuspendExecution, IllegalStateException {
89-
throw new Error("Calling function not instrumented");
101+
if (useNative) {
102+
nativeCoroutineBuilder.yield();
103+
} else {
104+
throw new Error("Calling function not instrumented");
105+
}
90106
}
91107

92108
/**
@@ -121,29 +137,40 @@ public Coroutine(Runnable proto) {
121137
*/
122138
public Coroutine(Runnable proto, int stackSize) {
123139
this.proto = proto;
124-
this.stack = new Stack(this, stackSize);
125-
this.cstack = new SuspendableConstructorUtilStack(stackSize/8);
126140
this.state = State.NEW;
127141
Thread thread = Thread.currentThread();
128142
Object currentLocals = HackUtils.getThreadLocals(Thread.currentThread());
129143
this.locals = HackUtils.cloneThreadLocalMap(currentLocals);
130-
try {
131-
HackUtils.setThreadLocals(thread, this.locals);
132-
Stack.setStack(this.stack);
133-
SuspendableConstructorUtilStack.setStack(this.cstack);
134-
}finally {
135-
HackUtils.setThreadLocals(thread, currentLocals);
136-
}
137144

138-
Object inheritableLocals = HackUtils.getInheritableThreadLocals(Thread.currentThread());
139-
if (inheritableLocals != null) {
140-
this.inheritableLocals = HackUtils.createInheritedMap(inheritableLocals);
141-
}
142-
143-
if(proto == null) {
144-
throw new NullPointerException("proto");
145+
if (useNative) {
146+
this.nativeCoroutine = nativeCoroutineBuilder.build(proto);
147+
this.stack = new Stack(this, 0);;
148+
try {
149+
HackUtils.setThreadLocals(thread, this.locals);
150+
Stack.setStack(this.stack);
151+
} finally {
152+
HackUtils.setThreadLocals(thread, currentLocals);
153+
}
154+
155+
this.cstack = null;
156+
} else {
157+
this.stack = new Stack(this, stackSize);
158+
this.cstack = new SuspendableConstructorUtilStack(stackSize/8);
159+
try {
160+
HackUtils.setThreadLocals(thread, this.locals);
161+
Stack.setStack(this.stack);
162+
SuspendableConstructorUtilStack.setStack(this.cstack);
163+
} finally {
164+
HackUtils.setThreadLocals(thread, currentLocals);
165+
}
166+
167+
Object inheritableLocals = HackUtils.getInheritableThreadLocals(Thread.currentThread());
168+
if (inheritableLocals != null) {
169+
this.inheritableLocals = HackUtils.createInheritedMap(inheritableLocals);
170+
}
171+
172+
assert isInstrumented(proto) : "Not instrumented";
145173
}
146-
assert isInstrumented(proto) : "Not instrumented";
147174
}
148175

149176
public void reset() {
@@ -153,8 +180,13 @@ public void reset() {
153180
this.locals = HackUtils.cloneThreadLocalMap(currentLocals);
154181
try {
155182
HackUtils.setThreadLocals(thread, this.locals);
156-
Stack.setStack(this.stack);
157-
SuspendableConstructorUtilStack.setStack(this.cstack);
183+
if (useNative) {
184+
this.nativeCoroutine = nativeCoroutineBuilder.build(proto);
185+
Stack.setStack(this.stack);
186+
} else {
187+
Stack.setStack(this.stack);
188+
SuspendableConstructorUtilStack.setStack(this.cstack);
189+
}
158190
}finally {
159191
HackUtils.setThreadLocals(thread, currentLocals);
160192
}
@@ -204,7 +236,12 @@ public State getState() {
204236
return state;
205237
}
206238

207-
239+
/**
240+
* @param state the state to set
241+
*/
242+
protected void setState(State state) {
243+
this.state = state;
244+
}
208245

209246
/**
210247
* Runs the Coroutine until it is finished or suspended. This method must only
@@ -241,14 +278,20 @@ public void resume() {
241278
state = State.RUNNING;
242279
// Stack.setStack(stack);
243280
// SuspendableConstructorUtilStack.setStack(cstack);
244-
245-
try {
281+
if (useNative) {
282+
nativeCoroutine.resume();
283+
if (state == State.SUSPENDED) {
284+
result = State.SUSPENDED;
285+
}
286+
} else {
287+
try {
246288
proto.run();
247-
} catch (SuspendExecution ex) {
248-
assert ex == SuspendExecution.instance;
249-
result = State.SUSPENDED;
250-
//stack.dump();
251-
stack.resumeStack();
289+
} catch (SuspendExecution ex) {
290+
assert ex == SuspendExecution.instance;
291+
result = State.SUSPENDED;
292+
//stack.dump();
293+
stack.resumeStack();
294+
}
252295
}
253296
} finally {
254297
if (result == State.FINISHED) {
@@ -333,4 +376,14 @@ private boolean isInstrumented(Runnable proto) {
333376
return true; // it's just a check - make sure we don't fail if something goes wrong
334377
}
335378
}
379+
380+
public static void prepareNative() {
381+
useNative = true;
382+
try {
383+
nativeCoroutineBuilder = (NativeCoroutineBuilder) Thread.currentThread().getContextClassLoader()
384+
.loadClass("nginx.clojure.NativeCoroutineBuilderImp").newInstance();
385+
} catch (Throwable e) {
386+
throw new IllegalStateException("can not load nginx.clojure.NativeCoroutineBuilderImp", e);
387+
}
388+
}
336389
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright (C) Zhang,Yuexiang (xfeep)
3+
*
4+
*/
5+
package nginx.clojure;
6+
7+
/**
8+
* @author Zhang,Yuexiang (xfeep)
9+
*
10+
*/
11+
public interface NativeCoroutineBuilder {
12+
13+
static interface NativeCoroutine {
14+
void resume();
15+
}
16+
17+
public NativeCoroutine build(Runnable r);
18+
19+
public boolean yield();
20+
}

src/java/nginx/clojure/NginxClojureRT.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,11 @@ private static void initWorkers(int n) {
451451
"worker won't be blocked when access services provide by the same nginx instance");
452452
n = Runtime.getRuntime().availableProcessors() * 2;
453453
}
454-
}else {
454+
} else {
455455
log.info("java agent configured so we turn on coroutine support!");
456+
if (JavaAgent.db.isEnableNativeCoroutine()) {
457+
Coroutine.prepareNative();
458+
}
456459
if (n > 0) {
457460
log.warn("found jvm_workers = %d, and not = 0 we just ignored!", n);
458461
}

src/java/nginx/clojure/Stack.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ public final class Stack implements Serializable {
7474

7575

7676
Stack(Coroutine co, int stackSize) {
77+
78+
if (Coroutine.isUseNative()) {
79+
this.co = co;
80+
return;
81+
}
82+
7783
if(stackSize <= 0) {
7884
throw new IllegalArgumentException("stackSize");
7985
}
@@ -542,12 +548,18 @@ public boolean allObjsAreNull() {
542548
}
543549

544550
protected void release() {
551+
552+
if (Coroutine.isUseNative()) {
553+
return;
554+
}
555+
545556
methodTOS = -1;
546557
if (verifyInfo != null) {
547558
verifyInfo.tracerStacks.clear();
548559
fillNull(verifyInfo.methodIdxInfos, 0, verifyInfo.methodIdxInfos.length);
549560
}
550-
fillNull(dataObject, 0, dataObject.length);
561+
562+
fillNull(dataObject, 0, dataObject.length);
551563
}
552564

553565
public static void fillNull(Object[] array, int s, int len) {

src/java/nginx/clojure/wave/JavaAgent.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
import java.security.ProtectionDomain;
7171
import java.util.regex.Pattern;
7272

73+
import nginx.clojure.Coroutine;
7374
import nginx.clojure.Stack;
7475
import nginx.clojure.asm.ClassReader;
7576
import nginx.clojure.asm.ClassVisitor;
@@ -98,7 +99,7 @@ public class JavaAgent {
9899

99100
public static void premain(String agentArguments, Instrumentation instrumentation) {
100101
ClassFileTransformer cft = buildClassFileTransformer(agentArguments);
101-
if (cft != null) {
102+
if (cft != null && !db.isEnableNativeCoroutine()) {
102103
instrumentation.addTransformer(cft, true);
103104
for (String c : db.getRetransformedClasses()) {
104105
try {
@@ -149,6 +150,9 @@ public static ClassFileTransformer buildClassFileTransformer(String agentArgumen
149150
case 'p' :
150151
db.setDump(true);
151152
break;
153+
case 'N' :
154+
db.setEnableNativeCoroutine(true);
155+
break;
152156
case 'n':
153157
TinyLogService.createDefaultTinyLogService().info("nginx clojure will do nothing about class waving!");
154158
//do nothing!!

src/java/nginx/clojure/wave/MethodDatabase.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ public class MethodDatabase implements LoggerService {
121121
private boolean hookDumpWaveCfg = false;
122122
private boolean doNothing = false;
123123
private boolean runTool = false;
124+
private boolean enableNativeCoroutine = false;
124125
private Pattern traceClassPattern = null;
125126
private Pattern traceClassMethodPattern = null;
126127

@@ -184,8 +185,18 @@ public boolean isAllowOutofCoroutine() {
184185
public void setAllowOutofCoroutine(boolean allowOutofCoroutine) {
185186
this.allowOutofCoroutine = allowOutofCoroutine;
186187
}
188+
189+
190+
191+
public boolean isEnableNativeCoroutine() {
192+
return enableNativeCoroutine;
193+
}
194+
195+
public void setEnableNativeCoroutine(boolean enableNativeCoroutine) {
196+
this.enableNativeCoroutine = enableNativeCoroutine;
197+
}
187198

188-
public ConcurrentHashMap<String, ClassEntry> getClasses() {
199+
public ConcurrentHashMap<String, ClassEntry> getClasses() {
189200
return classes;
190201
}
191202

0 commit comments

Comments
 (0)