Version 1.0.1
This documentation should help to understand the implementation of continuations in the COPPER Engine Workflows.
COPPER Workflows can be interrupted and continued by using several methods:
-
savepoint -
wait -
waitForAll.
In the following examples savepoint is used, but the concept is the same for the other methods.
Continuation is realized by using instrumentation with the ASM Framework.
The abstract concept is to throw a Throwable (Interrupt) at each savepoint
and to store the Workflow object with local variables incl. method parameters for next continuation.
The Workflow and local variables are restored and the Java Call Stack is reconstructed on next continuation.
Now we start with analyzing a first simple Workflow and a second more complex Workflow. Then we visit some Implementation details.
Example with one Savepoint
The original Workflow
This is the original Workflow with correct Java code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package test;
import org.copperengine.core.Interrupt;
import org.copperengine.core.Workflow;
public class OneSavepoint extends Workflow<Void> {
@Override
public void main() throws Interrupt {
System.out.println("main start");
int i1 = 1;
final int i2 = 2;
savepoint(); // (1)
System.out.println("jumpNo=0");
System.out.println("main end");
}
}
-
savepointin line 13.
When the main method it called the 1st time, the code up to the savepoint will be run. Then the execution will be stopped until a 2nd call of the method will run the code up to its end. How this is realized, the following chapters should explain.
The decompiled instrumented Workflow
This is the instrumented Workflow with incorrect Java code, generated by a decompiler. Nevertheless, it helps to understand more of the COPPER internals. In the following chapters we dive into the byte code, that holds the truth.
Keep in mind:
-
The
mainmethod has no parameters. -
The
Workflowhas two imported COPPER internal members__stackand__stackPositionusing in the conditions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package test;
import org.copperengine.core.Interrupt;
import org.copperengine.core.StackEntry;
import org.copperengine.core.Workflow;
import org.copperengine.core.instrument.Transformed;
@Transformed
public class OneSavepoint extends Workflow<Void> {
public void main() throws Interrupt {
if (this.__stack.size() == this.__stackPosition) { // (1)
System.out.println("main start");
int i1 = 1;
int i2 = 2;
this.savepoint();
this.__stack.push(new StackEntry(new Object[0], 0, new Object[]{this, i1, i2})); // (2)
++this.__stackPosition; // (3)
throw new Interrupt(); // (4)
} else if (((StackEntry)this.__stack.get(this.__stackPosition)).jumpNo == 0) { // (5)
Object[] var10000 = ((StackEntry)this.__stack.get(this.__stackPosition)).locals; // (6)
this = (OneSavepoint)var10000[0]; // (7)
int i1 = (Integer)var10000[1]; // (8)
int i2 = (Integer)var10000[2]; // (9)
++this.__stackPosition;
this.__stack.pop(); // (10)
--this.__stackPosition;
System.out.println("jumpNo=0");
System.out.println("main end");
} else {
throw new RuntimeException("No such label");
}
}
}
-
Condition is
truefor first call -
Storing
StackEntryto__stack(no call stack parameters, jumpNo=0 and locals) -
Confusion and irrelevant increment of
__stackPosition -
End of first call
-
Condition is
truefor second call -
Get
localsto restore local variables -
Assignment to
thisin line 26 is invalid -
Restore
i1 -
Restore
i2 -
Removing
StackEntryfrom top of__stack
We see the original code embedded in conditional enhanced scopes.
It is important to know, that
-
__stackis stored in COPPER after the calls (may be empty) -
__stackis used for next call -
__stackPositionis not stored. It is0each time themainis called
The byte code of the original Workflow
Our first Java byte code assembly belongs to the original Workflow.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// class version 61.0 (61)
// access flags 0x21
// signature Lorg/copperengine/core/Workflow<Ljava/lang/Void;>;
// declaration: test/OneSavepoint extends org.copperengine.core.Workflow<java.lang.Void>
public class test/OneSavepoint extends org/copperengine/core/Workflow {
// compiled from: OneSavepoint.java
// access flags 0x1
public <init>()V // (1)
L0
LINENUMBER 6 L0
ALOAD 0
INVOKESPECIAL org/copperengine/core/Workflow.<init> ()V
RETURN
L1
LOCALVARIABLE this Ltest/OneSavepoint; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public main()V throws org/copperengine/core/Interrupt // (2)
L0
LINENUMBER 10 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "main start"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 11 L1
ICONST_1
ISTORE 1
L2
LINENUMBER 12 L2
ICONST_2
ISTORE 2
L3
LINENUMBER 13 L3
ALOAD 0
INVOKEVIRTUAL test/OneSavepoint.savepoint ()V // (3)
L4
LINENUMBER 14 L4
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "jumpNo=0"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L5
LINENUMBER 15 L5
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "main end"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L6
LINENUMBER 16 L6
RETURN
L7
LOCALVARIABLE this Ltest/OneSavepoint; L0 L7 0
LOCALVARIABLE i1 I L2 L7 1
LOCALVARIABLE i2 I L3 L7 2
MAXSTACK = 2
MAXLOCALS = 3
}
-
Irrelevant constructor
init -
Relevant `main`method
-
The
savepoint
If you often use Java code and no byte code, you also can understand it without all details.
You can see the original LINENUMBER lines, connected to label L1, L2, …
The LDC lines holds string, you also find in Java the code.
The byte code of the instrumented Workflow
As we have seen the Java byte code assembly belonging to the original Workflow, we can now look into the instrumented. It is harder to understand, than the decompiled Java code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// class version 61.0 (61)
// access flags 0x21
// signature Lorg/copperengine/core/Workflow<Ljava/lang/Void;>;
// declaration: test/OneSavepoint extends org.copperengine.core.Workflow<java.lang.Void>
public class test/OneSavepoint extends org/copperengine/core/Workflow {
// compiled from: OneSavepoint.java
@Lorg/copperengine/core/instrument/Transformed;()
// access flags 0x1
public <init>()V
L0
LINENUMBER 6 L0
ALOAD 0
INVOKESPECIAL org/copperengine/core/Workflow.<init> ()V
RETURN
L1
LOCALVARIABLE this Ltest/OneSavepoint; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public main()V throws org/copperengine/core/Interrupt
GOTO L0 // (1)
L1 // (2)
LINENUMBER 10 L1
FRAME SAME
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "main start"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L2
LINENUMBER 11 L2
ICONST_1
ISTORE 1
L3
LINENUMBER 12 L3
ICONST_2
ISTORE 2
L4
LINENUMBER 13 L4
ALOAD 0
INVOKEVIRTUAL test/OneSavepoint.savepoint ()V // (3)
SIPUSH 0
ANEWARRAY java/lang/Object
NEW org/copperengine/core/StackEntry // (4)
DUP_X1
DUP_X1
POP
SIPUSH 0
SIPUSH 3
ANEWARRAY java/lang/Object
DUP
SIPUSH 0
ALOAD 0
AASTORE
DUP
SIPUSH 1
ILOAD 1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
DUP
SIPUSH 2
ILOAD 2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
INVOKESPECIAL org/copperengine/core/StackEntry.<init> ([Ljava/lang/Object;I[Ljava/lang/Object;)V
ALOAD 0
GETFIELD test/OneSavepoint.__stack : Ljava/util/Stack;
SWAP
INVOKEVIRTUAL java/util/Stack.push (Ljava/lang/Object;)Ljava/lang/Object;
POP
ALOAD 0
DUP
GETFIELD test/OneSavepoint.__stackPosition : I
ICONST_1
IADD
PUTFIELD test/OneSavepoint.__stackPosition : I
NEW org/copperengine/core/Interrupt
DUP
INVOKESPECIAL org/copperengine/core/Interrupt.<init> ()V
ATHROW
L5 // (5)
FRAME APPEND [I I]
ALOAD 0
GETFIELD test/OneSavepoint.__stack : Ljava/util/Stack;
INVOKEVIRTUAL java/util/Stack.pop ()Ljava/lang/Object;
POP
ALOAD 0
DUP
GETFIELD test/OneSavepoint.__stackPosition : I
ICONST_1
ISUB
PUTFIELD test/OneSavepoint.__stackPosition : I
L6
LINENUMBER 14 L6
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "jumpNo=0"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L7
LINENUMBER 15 L7
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "main end"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L8
LINENUMBER 16 L8
RETURN
L0 // (6)
FRAME CHOP 2
ALOAD 0
GETFIELD test/OneSavepoint.__stack : Ljava/util/Stack;
INVOKEVIRTUAL java/util/Stack.size ()I
ALOAD 0
GETFIELD test/OneSavepoint.__stackPosition : I
IF_ICMPNE L9
GOTO L1 // (7)
L9
FRAME SAME
ALOAD 0
GETFIELD test/OneSavepoint.__stack : Ljava/util/Stack;
ALOAD 0
GETFIELD test/OneSavepoint.__stackPosition : I
INVOKEVIRTUAL java/util/Stack.get (I)Ljava/lang/Object;
CHECKCAST org/copperengine/core/StackEntry
GETFIELD org/copperengine/core/StackEntry.jumpNo : I
DUP
SIPUSH 0
IF_ICMPNE L10
POP
ALOAD 0
GETFIELD test/OneSavepoint.__stack : Ljava/util/Stack;
ALOAD 0
GETFIELD test/OneSavepoint.__stackPosition : I
INVOKEVIRTUAL java/util/Stack.get (I)Ljava/lang/Object; // (8)
CHECKCAST org/copperengine/core/StackEntry
GETFIELD org/copperengine/core/StackEntry.locals : [Ljava/lang/Object;
DUP
SIPUSH 0
AALOAD
CHECKCAST test/OneSavepoint
ASTORE 0
DUP
SIPUSH 1
AALOAD
CHECKCAST java/lang/Integer
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ISTORE 1
DUP
SIPUSH 2
AALOAD
CHECKCAST java/lang/Integer
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ISTORE 2
POP
ALOAD 0
DUP
GETFIELD test/OneSavepoint.__stackPosition : I
ICONST_1
IADD
PUTFIELD test/OneSavepoint.__stackPosition : I
GOTO L5 // (9)
L10
FRAME SAME1 I
NEW java/lang/RuntimeException
DUP
LDC "No such label"
INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/String;)V
ATHROW
LOCALVARIABLE this Ltest/OneSavepoint; L1 L0 0
LOCALVARIABLE i1 I L3 L0 1
LOCALVARIABLE i2 I L4 L0 2
MAXSTACK = 8
MAXLOCALS = 3
}
-
Goto the end of the method
-
Start of original
main -
The
savepoint -
Creating the
StackEntry -
Continue label after
safepointincl. adjustingstackandstackPosition -
Behind the original end of method
-
Goto start of original
main -
Reading the
StackEntry -
Goto continue label after
safepoint
We see, that creating the StackEntry is in the method, behind the savepoint.
Reading the StackEntry ist behind the original end of method.
Example with Savepoints and Subworkflows
Here is a more complex Workflow with several `savepoint`s.
The original Workflow
It also has subWorkflow, called in main.
And there is a subWorkflow2, called in subWorkflow.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package test;
import org.copperengine.core.Interrupt;
import org.copperengine.core.Workflow;
public class SavepointsAndSubWorkflows extends Workflow<Void> {
@Override
public void main() throws Interrupt {
int i1 = 1;
final int i2 = 2;
System.out.println("main start");
savepoint();
System.out.println("jumpNo=0");
savepoint();
System.out.println("jumpNo=1");
savepoint();
System.out.println("jumpNo=2");
subWorkflow(i1, 666L); // (1)
System.out.println("main end");
}
public void subWorkflow(int i1, long l) throws Interrupt {
System.out.println("subWorklow start");
savepoint();
System.out.println("jumpNo=0");
savepoint();
System.out.println("jumpNo=1");
savepoint();
System.out.println("jumpNo=2");
subWorkflow2(new long[] { 777L, 888L, 999L }); // (2)
System.out.println("subWorklow end");
}
public void subWorkflow2(long[] l) throws Interrupt {
System.out.println("subWorklow2 start");
savepoint();
System.out.println("jumpNo=0");
System.out.println("subWorklow2 end");
}
}
-
Call of
subWorkflowwith 2 parameters -
Call of
subWorkflowswith 1 parameter (array of long)
The decompiled instrumented Workflow
This is the instrumented Workflow with incorrect Java code, generated by a decompiler. Nevertheless, it helps to understand more of the COPPER internals.
We see the handling of SubWorkflows (Interruptable methods).
The parameters are also stored in a StackEntry.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package test;
import org.copperengine.core.Interrupt;
import org.copperengine.core.StackEntry;
import org.copperengine.core.Workflow;
import org.copperengine.core.instrument.Transformed;
@Transformed
public class SavepointsAndSubWorkflows extends Workflow<Void> {
public void main() throws Interrupt {
if (this.__stack.size() == this.__stackPosition) {
int i1 = 1;
int i2 = 2;
System.out.println("main start");
this.savepoint();
this.__stack.push(new StackEntry(new Object[0], 0, new Object[]{this, i1, i2}));
++this.__stackPosition;
throw new Interrupt();
} else {
int var10000 = ((StackEntry)this.__stack.get(this.__stackPosition)).jumpNo;
if (var10000 == 0) {
Object[] var22 = ((StackEntry)this.__stack.get(this.__stackPosition)).locals;
this = (SavepointsAndSubWorkflows)var22[0];
int var10 = (Integer)var22[1];
int var14 = (Integer)var22[2];
++this.__stackPosition;
this.__stack.pop();
--this.__stackPosition;
System.out.println("jumpNo=0");
this.savepoint();
this.__stack.push(new StackEntry(new Object[0], 1, new Object[]{this, var10, var14}));
++this.__stackPosition;
throw new Interrupt();
} else if (var10000 == 1) {
Object[] var21 = ((StackEntry)this.__stack.get(this.__stackPosition)).locals;
this = (SavepointsAndSubWorkflows)var21[0];
int var9 = (Integer)var21[1];
int var13 = (Integer)var21[2];
++this.__stackPosition;
this.__stack.pop();
--this.__stackPosition;
System.out.println("jumpNo=1");
this.savepoint();
this.__stack.push(new StackEntry(new Object[0], 2, new Object[]{this, var9, var13}));
++this.__stackPosition;
throw new Interrupt();
} else {
SavepointsAndSubWorkflows var18;
int var23; // (1)
long var26; // (2)
if (var10000 == 2) {
Object[] var16 = ((StackEntry)this.__stack.get(this.__stackPosition)).locals;
this = (SavepointsAndSubWorkflows)var16[0];
int i1 = (Integer)var16[1];
int i2 = (Integer)var16[2];
++this.__stackPosition;
this.__stack.pop();
--this.__stackPosition;
System.out.println("jumpNo=2");
this.__stack.push(new StackEntry(new Object[]{this, i1, 666L}, 3, new Object[]{this, i1, i2})); // (3)
var16 = ((StackEntry)this.__stack.get(this.__stackPosition)).stack; // (4)
var18 = (SavepointsAndSubWorkflows)var16[0]; // (4)
var23 = (Integer)var16[1]; // (4)
var26 = (Long)var16[2]; // (4)
++this.__stackPosition; // (5)
} else { // (6)
if (var10000 != 3) {
throw new RuntimeException("No such label");
}
Object[] var19 = ((StackEntry)this.__stack.get(this.__stackPosition)).locals;
this = (SavepointsAndSubWorkflows)var19[0];
int var8 = (Integer)var19[1];
int var12 = (Integer)var19[2];
var19 = ((StackEntry)this.__stack.get(this.__stackPosition)).stack; // (7)
var18 = (SavepointsAndSubWorkflows)var19[0]; // (7)
var23 = (Integer)var19[1]; // (7)
var26 = (Long)var19[2]; // (7)
++this.__stackPosition; // (8)
}
try {
var18.subWorkflow(var23, var26); // (9)
} catch (Interrupt var3) { // (10)
throw var3; // (10)
} catch (Throwable var4) { // (10)
this.__stack.pop(); // (10)
--this.__stackPosition; // (10)
throw var4; // (10)
} // (10)
this.__stack.pop();
--this.__stackPosition;
System.out.println("main end");
}
}
}
public void subWorkflow(int i1, long l) throws Interrupt {
if (this.__stack.size() == this.__stackPosition) {
System.out.println("subWorklow start");
this.savepoint();
this.__stack.push(new StackEntry(new Object[0], 0, new Object[]{this, i1, l, null}));
++this.__stackPosition;
throw new Interrupt();
} else {
int var10000 = ((StackEntry)this.__stack.get(this.__stackPosition)).jumpNo;
if (var10000 == 0) {
Object[] var23 = ((StackEntry)this.__stack.get(this.__stackPosition)).locals;
this = (SavepointsAndSubWorkflows)var23[0];
i1 = (Integer)var23[1];
l = (Long)var23[2];
++this.__stackPosition;
this.__stack.pop();
--this.__stackPosition;
System.out.println("jumpNo=0");
this.savepoint();
this.__stack.push(new StackEntry(new Object[0], 1, new Object[]{this, i1, l, null}));
++this.__stackPosition;
throw new Interrupt();
} else if (var10000 == 1) {
Object[] var22 = ((StackEntry)this.__stack.get(this.__stackPosition)).locals;
this = (SavepointsAndSubWorkflows)var22[0];
i1 = (Integer)var22[1];
l = (Long)var22[2];
++this.__stackPosition;
this.__stack.pop();
--this.__stackPosition;
System.out.println("jumpNo=1");
this.savepoint();
this.__stack.push(new StackEntry(new Object[0], 2, new Object[]{this, i1, l, null}));
++this.__stackPosition;
throw new Interrupt();
} else {
SavepointsAndSubWorkflows var19;
long[] var24;
if (var10000 == 2) {
Object[] var17 = ((StackEntry)this.__stack.get(this.__stackPosition)).locals;
this = (SavepointsAndSubWorkflows)var17[0];
i1 = (Integer)var17[1];
l = (Long)var17[2];
++this.__stackPosition;
this.__stack.pop();
--this.__stackPosition;
System.out.println("jumpNo=2");
this.__stack.push(new StackEntry(new Object[]{this, new long[]{777L, 888L, 999L}}, 3, new Object[]{this, i1, l, null}));
var17 = ((StackEntry)this.__stack.get(this.__stackPosition)).stack;
var19 = (SavepointsAndSubWorkflows)var17[0];
var24 = (long[])var17[1];
++this.__stackPosition;
} else {
if (var10000 != 3) {
throw new RuntimeException("No such label");
}
Object[] var20 = ((StackEntry)this.__stack.get(this.__stackPosition)).locals;
this = (SavepointsAndSubWorkflows)var20[0];
i1 = (Integer)var20[1];
l = (Long)var20[2];
var20 = ((StackEntry)this.__stack.get(this.__stackPosition)).stack;
var19 = (SavepointsAndSubWorkflows)var20[0];
var24 = (long[])var20[1];
++this.__stackPosition;
}
try {
var19.subWorkflow2(var24);
} catch (Interrupt var4) {
throw var4;
} catch (Throwable var5) {
this.__stack.pop();
--this.__stackPosition;
throw var5;
}
this.__stack.pop();
--this.__stackPosition;
System.out.println("subWorklow end");
}
}
}
public void subWorkflow2(long[] l) throws Interrupt {
if (this.__stack.size() == this.__stackPosition) {
System.out.println("subWorklow2 start");
this.savepoint();
this.__stack.push(new StackEntry(new Object[0], 0, new Object[]{this, l}));
++this.__stackPosition;
throw new Interrupt();
} else if (((StackEntry)this.__stack.get(this.__stackPosition)).jumpNo == 0) {
Object[] var10000 = ((StackEntry)this.__stack.get(this.__stackPosition)).locals;
this = (SavepointsAndSubWorkflows)var10000[0];
l = (long[])var10000[1];
++this.__stackPosition;
this.__stack.pop();
--this.__stackPosition;
System.out.println("jumpNo=0");
System.out.println("subWorklow2 end");
} else {
throw new RuntimeException("No such label");
}
}
}
-
Variable used as parameter for the
subWorkflow -
Variable used as parameter for the
subWorkflow -
Create
StackEntrywithjumpNo=3incl.stackwith parameters -
Prepare variables used as parameter before 1st call of
subWorkflow -
Increment
__stackPositionbefore callingsubWorkflow -
Condition for
jumpNo=3to callsubWorkflow -
Prepare variables used as parameter before other calls of
subWorkflow -
Increment
__stackPositionbefore callingsubWorkflow -
Call of
subWorkflow -
Instrumented handling for
Interrupt
subWorkflow2 is added for analyzing deeper call structures.
The concept for the implementation of continuations in COPPER should be cleared now.
Implementation
In this chapter we step deeper into the implementation.
It contains several links into the source code repository, where you might start more analysis.
We start with more information about Interrupt.
Interrupt
We saw, that throwing an Interrupt is the was to end a continuation before it will be resumed later.
To avoid unexpected side effects in try, catch and finally handling must be instrumented.
In the last example it was visible in the decompiled Workflow.
The major responsible class for this aspect is the TryCatchBlockHandler
Interrupt throwable must not be handled or thrown.
Catching it, leads to an exception.
Creating it, leads to a warning.
Workflow
The class
Workflow
holds the above-mentioned __stack and __stackPosition and
public void __beforeProcess() {
__stackPosition = 0;
}
Analysing the transient members might be interesting
Engine
COPPER offers two engines, which control and store the continuation.
WorkflowRepository
The AbstractWorkflowRepository controls the instrumentation using the major classes:
-
TryCatchBlockHandlerwe already discussed above -
ScottyClassAdapterconnecting [ScottyMethodAdapter] and [BuildStackInfoAdapter] -
BuildStackInfoAdapterkeeping track of locals, stacks and type information inStackEntry -
ScottyMethodAdapterdelegate inBuildStackInfoAdapterimplementing the major methodsvisitMethodInsnandvisitEnd
Last Workflow
The last Workflow we analyze has one savepoint and a try, catch, finally.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package test;
import org.copperengine.core.Interrupt;
import org.copperengine.core.Workflow;
public class OneSavepointWithTryCatchFinally extends Workflow<Void> {
@Override
public void main() throws Interrupt {
System.out.println("main start");
try {
int i1 = 1;
final int i2 = 2;
savepoint(); // (1)
System.out.println("jumpNo=0");
} catch (Exception e) {
System.out.println("Exception");
throw new RuntimeException(e);
} finally {
System.out.println("Finally");
}
System.out.println("main end");
}
}
The byte code of the instrumented Workflow with trace
To analyze the responsibilities for instrumentation, we look at the instrumented Workflow with trace.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// class version 61.0 (61)
// access flags 0x21
// signature Lorg/copperengine/core/Workflow<Ljava/lang/Void;>;
// declaration: test/OneSavepointWithTryCatchFinally extends org.copperengine.core.Workflow<java.lang.Void>
public class test/OneSavepointWithTryCatchFinally extends org/copperengine/core/Workflow {
// compiled from: OneSavepointWithTryCatchFinally.java
@Lorg/copperengine/core/instrument/Transformed;()
// access flags 0x1
public <init>()V
L0
LINENUMBER 21 L0
ALOAD 0
INVOKESPECIAL org/copperengine/core/Workflow.<init> ()V
RETURN
L1
LOCALVARIABLE this Ltest/OneSavepointWithTryCatchFinally; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public main()V throws org/copperengine/core/Interrupt
TRYCATCHBLOCK L0 L1 L2 java/lang/Exception
TRYCATCHBLOCK L0 L1 L3 null
TRYCATCHBLOCK L2 L4 L3 null
GOTO L5
L6
LINENUMBER 25 L6
FRAME SAME
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "main start"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L0
LINENUMBER 27 L0
ICONST_1
ISTORE 1
L7
LINENUMBER 28 L7
ICONST_2
ISTORE 2
L8
LINENUMBER 29 L8
ALOAD 0
INVOKEVIRTUAL test/OneSavepointWithTryCatchFinally.savepoint ()V
GETSTATIC org/copperengine/core/Workflow.logger : Lorg/slf4j/Logger;
LDC "Start instrumentation waitMethods in visitMethodInsn (savepoint)" // (1)
INVOKEINTERFACE org/slf4j/Logger.trace (Ljava/lang/String;)V (itf)
SIPUSH 0
ANEWARRAY java/lang/Object
NEW org/copperengine/core/StackEntry
DUP_X1
DUP_X1
POP
SIPUSH 0
SIPUSH 3
ANEWARRAY java/lang/Object
DUP
SIPUSH 0
ALOAD 0
AASTORE
DUP
SIPUSH 1
ILOAD 1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
DUP
SIPUSH 2
ILOAD 2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
INVOKESPECIAL org/copperengine/core/StackEntry.<init> ([Ljava/lang/Object;I[Ljava/lang/Object;)V
ALOAD 0
GETFIELD test/OneSavepointWithTryCatchFinally.__stack : Ljava/util/Stack;
SWAP
INVOKEVIRTUAL java/util/Stack.push (Ljava/lang/Object;)Ljava/lang/Object;
POP
ALOAD 0
DUP
GETFIELD test/OneSavepointWithTryCatchFinally.__stackPosition : I
ICONST_1
IADD
PUTFIELD test/OneSavepointWithTryCatchFinally.__stackPosition : I
NEW org/copperengine/core/Interrupt
DUP
INVOKESPECIAL org/copperengine/core/Interrupt.<init> ()V
ATHROW
L9
FRAME APPEND [I I]
ALOAD 0
GETFIELD test/OneSavepointWithTryCatchFinally.__stack : Ljava/util/Stack;
INVOKEVIRTUAL java/util/Stack.pop ()Ljava/lang/Object;
POP
ALOAD 0
DUP
GETFIELD test/OneSavepointWithTryCatchFinally.__stackPosition : I
ICONST_1
ISUB
PUTFIELD test/OneSavepointWithTryCatchFinally.__stackPosition : I
GETSTATIC org/copperengine/core/Workflow.logger : Lorg/slf4j/Logger;
LDC "End instrumentation waitMethods in visitMethodInsn (savepoint)" // (2)
INVOKEINTERFACE org/slf4j/Logger.trace (Ljava/lang/String;)V (itf)
L10
LINENUMBER 30 L10
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "jumpNo=0"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 35 L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Finally"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L11
LINENUMBER 36 L11
GOTO L12
L2
LINENUMBER 31 L2
FRAME FULL [test/OneSavepointWithTryCatchFinally] [java/lang/Exception]
ASTORE 1
GETSTATIC org/copperengine/core/Workflow.logger : Lorg/slf4j/Logger;
LDC "Start instrumentation tryCatchBlock" // (3)
INVOKEINTERFACE org/slf4j/Logger.trace (Ljava/lang/String;)V (itf)
ALOAD 1
INSTANCEOF org/copperengine/core/Interrupt
IFEQ L13
ALOAD 1
CHECKCAST org/copperengine/core/Interrupt
ATHROW
L13
FRAME APPEND [java/lang/Exception]
GETSTATIC org/copperengine/core/Workflow.logger : Lorg/slf4j/Logger;
LDC "End instrumentation tryCatchBlock" // (4)
INVOKEINTERFACE org/slf4j/Logger.trace (Ljava/lang/String;)V (itf)
L14
LINENUMBER 32 L14
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Exception"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L15
LINENUMBER 33 L15
NEW java/lang/RuntimeException
DUP
ALOAD 1
INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/Throwable;)V
ATHROW
L3
LINENUMBER 35 L3
FRAME FULL [test/OneSavepointWithTryCatchFinally] [java/lang/Throwable]
ASTORE 3
L4
GETSTATIC org/copperengine/core/Workflow.logger : Lorg/slf4j/Logger;
LDC "Start instrumentation tryCatchBlock" // (5)
INVOKEINTERFACE org/slf4j/Logger.trace (Ljava/lang/String;)V (itf)
ALOAD 3
INSTANCEOF org/copperengine/core/Interrupt
IFEQ L16
ALOAD 3
CHECKCAST org/copperengine/core/Interrupt
ATHROW
L16
FRAME APPEND [T T java/lang/Throwable]
GETSTATIC org/copperengine/core/Workflow.logger : Lorg/slf4j/Logger;
LDC "End instrumentation tryCatchBlock" // (6)
INVOKEINTERFACE org/slf4j/Logger.trace (Ljava/lang/String;)V (itf)
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Finally"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L17
LINENUMBER 36 L17
ALOAD 3
ATHROW
L12
LINENUMBER 37 L12
FRAME FULL [test/OneSavepointWithTryCatchFinally I I] []
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "main end"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L18
LINENUMBER 38 L18
RETURN
L5
FRAME CHOP 2
GETSTATIC org/copperengine/core/Workflow.logger : Lorg/slf4j/Logger;
LDC "Start instrumentation in visitEnd" // (7)
INVOKEINTERFACE org/slf4j/Logger.trace (Ljava/lang/String;)V (itf)
ALOAD 0
GETFIELD test/OneSavepointWithTryCatchFinally.__stack : Ljava/util/Stack;
INVOKEVIRTUAL java/util/Stack.size ()I
ALOAD 0
GETFIELD test/OneSavepointWithTryCatchFinally.__stackPosition : I
IF_ICMPNE L19
GOTO L6
L19
FRAME SAME
ALOAD 0
GETFIELD test/OneSavepointWithTryCatchFinally.__stack : Ljava/util/Stack;
ALOAD 0
GETFIELD test/OneSavepointWithTryCatchFinally.__stackPosition : I
INVOKEVIRTUAL java/util/Stack.get (I)Ljava/lang/Object;
CHECKCAST org/copperengine/core/StackEntry
GETFIELD org/copperengine/core/StackEntry.jumpNo : I
GETSTATIC org/copperengine/core/Workflow.logger : Lorg/slf4j/Logger;
LDC "Inside instrumentation in visitEnd" // (8)
INVOKEINTERFACE org/slf4j/Logger.trace (Ljava/lang/String;)V (itf)
DUP
SIPUSH 0
IF_ICMPNE L20
POP
ALOAD 0
GETFIELD test/OneSavepointWithTryCatchFinally.__stack : Ljava/util/Stack;
ALOAD 0
GETFIELD test/OneSavepointWithTryCatchFinally.__stackPosition : I
INVOKEVIRTUAL java/util/Stack.get (I)Ljava/lang/Object;
CHECKCAST org/copperengine/core/StackEntry
GETFIELD org/copperengine/core/StackEntry.locals : [Ljava/lang/Object;
DUP
SIPUSH 0
AALOAD
CHECKCAST test/OneSavepointWithTryCatchFinally
ASTORE 0
DUP
SIPUSH 1
AALOAD
CHECKCAST java/lang/Integer
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ISTORE 1
DUP
SIPUSH 2
AALOAD
CHECKCAST java/lang/Integer
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ISTORE 2
POP
ALOAD 0
DUP
GETFIELD test/OneSavepointWithTryCatchFinally.__stackPosition : I
ICONST_1
IADD
PUTFIELD test/OneSavepointWithTryCatchFinally.__stackPosition : I
GOTO L9
L20
FRAME SAME1 I
GETSTATIC org/copperengine/core/Workflow.logger : Lorg/slf4j/Logger;
LDC "End instrumentation in visitEnd" // (9)
INVOKEINTERFACE org/slf4j/Logger.trace (Ljava/lang/String;)V (itf)
NEW java/lang/RuntimeException
DUP
LDC "No such label"
INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/String;)V
ATHROW
LOCALVARIABLE i1 I L7 L1 1
LOCALVARIABLE i2 I L8 L1 2
LOCALVARIABLE e Ljava/lang/Exception; L14 L3 1
LOCALVARIABLE this Ltest/OneSavepointWithTryCatchFinally; L6 L5 0
MAXSTACK = 8
MAXLOCALS = 4
}
-
Start
ScottyMethodAdapter.visitMethodInsn -
End
ScottyMethodAdapter.visitMethodInsn -
Start
TryCatchBlockHandler.instrument -
End
TryCatchBlockHandler.instrument -
Start
TryCatchBlockHandler.instrument -
End
TryCatchBlockHandler.instrument -
Start
ScottyMethodAdapter.visitEnd -
Inside
ScottyMethodAdapter.visitEnd -
End
ScottyMethodAdapter.visitEnd
The byte code of the instrumented Workflow with trace
You might have noticed the two sequences for finally.
These already existed in the not instrumented byte code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// class version 61.0 (61)
// access flags 0x21
// signature Lorg/copperengine/core/Workflow<Ljava/lang/Void;>;
// declaration: test/OneSavepointWithTryCatchFinally extends org.copperengine.core.Workflow<java.lang.Void>
public class test/OneSavepointWithTryCatchFinally extends org/copperengine/core/Workflow {
// compiled from: OneSavepointWithTryCatchFinally.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 21 L0
ALOAD 0
INVOKESPECIAL org/copperengine/core/Workflow.<init> ()V
RETURN
L1
LOCALVARIABLE this Ltest/OneSavepointWithTryCatchFinally; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public main()V throws org/copperengine/core/Interrupt
TRYCATCHBLOCK L0 L1 L2 java/lang/Exception
TRYCATCHBLOCK L0 L1 L3 null
TRYCATCHBLOCK L2 L4 L3 null
L5
LINENUMBER 25 L5
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "main start"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L0
LINENUMBER 27 L0
ICONST_1
ISTORE 1
L6
LINENUMBER 28 L6
ICONST_2
ISTORE 2
L7
LINENUMBER 29 L7
ALOAD 0
INVOKEVIRTUAL test/OneSavepointWithTryCatchFinally.savepoint ()V
L8
LINENUMBER 30 L8
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "jumpNo=0"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 35 L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Finally" // (1)
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L9
LINENUMBER 36 L9
GOTO L10
L2
LINENUMBER 31 L2
FRAME SAME1 java/lang/Exception
ASTORE 1
L11
LINENUMBER 32 L11
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Exception"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L12
LINENUMBER 33 L12
NEW java/lang/RuntimeException
DUP
ALOAD 1
INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/Throwable;)V
ATHROW
L3
LINENUMBER 35 L3
FRAME SAME1 java/lang/Throwable
ASTORE 3
L4
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Finally" // (2)
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L13
LINENUMBER 36 L13
ALOAD 3
ATHROW
L10
LINENUMBER 37 L10
FRAME SAME
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "main end"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L14
LINENUMBER 38 L14
RETURN
L15
LOCALVARIABLE i1 I L6 L1 1
LOCALVARIABLE i2 I L7 L1 2
LOCALVARIABLE e Ljava/lang/Exception; L11 L3 1
LOCALVARIABLE this Ltest/OneSavepointWithTryCatchFinally; L5 L15 0
MAXSTACK = 3
MAXLOCALS = 4
}
-
1st finally sequence
-
2nd finally sequence
AnalyseTest
One test, that shows some internal is the
AnalyseTest.