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");
    }
}
  1. savepoint in 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 main method has no parameters.

  • The Workflow has two imported COPPER internal members __stack and __stackPosition using 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");
        }
    }
}
  1. Condition is true for first call

  2. Storing StackEntry to __stack (no call stack parameters, jumpNo=0 and locals)

  3. Confusion and irrelevant increment of __stackPosition

  4. End of first call

  5. Condition is true for second call

  6. Get locals to restore local variables

  7. Assignment to this in line 26 is invalid

  8. Restore i1

  9. Restore i2

  10. Removing StackEntry from top of __stack

We see the original code embedded in conditional enhanced scopes.

It is important to know, that

  • __stack is stored in COPPER after the calls (may be empty)

  • __stack is used for next call

  • __stackPosition is not stored. It is 0 each time the main is 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
}
  1. Irrelevant constructor init

  2. Relevant `main`method

  3. 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
}
  1. Goto the end of the method

  2. Start of original main

  3. The savepoint

  4. Creating the StackEntry

  5. Continue label after safepoint incl. adjusting stack and stackPosition

  6. Behind the original end of method

  7. Goto start of original main

  8. Reading the StackEntry

  9. 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");
    }
}
  1. Call of subWorkflow with 2 parameters

  2. Call of subWorkflows with 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");
        }
    }
}
  1. Variable used as parameter for the subWorkflow

  2. Variable used as parameter for the subWorkflow

  3. Create StackEntry with jumpNo=3 incl. stack with parameters

  4. Prepare variables used as parameter before 1st call of subWorkflow

  5. Increment __stackPosition before calling subWorkflow

  6. Condition for jumpNo=3 to call subWorkflow

  7. Prepare variables used as parameter before other calls of subWorkflow

  8. Increment __stackPosition before calling subWorkflow

  9. Call of subWorkflow

  10. 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

WorkflowRepository

The AbstractWorkflowRepository controls the instrumentation using the major classes:

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
}
  1. Start ScottyMethodAdapter.visitMethodInsn

  2. End ScottyMethodAdapter.visitMethodInsn

  3. Start TryCatchBlockHandler.instrument

  4. End TryCatchBlockHandler.instrument

  5. Start TryCatchBlockHandler.instrument

  6. End TryCatchBlockHandler.instrument

  7. Start ScottyMethodAdapter.visitEnd

  8. Inside ScottyMethodAdapter.visitEnd

  9. 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
}
  1. 1st finally sequence

  2. 2nd finally sequence

AnalyseTest

One test, that shows some internal is the AnalyseTest.