1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.javaflow.bytecode;
18
19 import org.apache.commons.javaflow.utils.ReflectionUtils;
20 import org.apache.commons.javaflow.ContinuationDeath;
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23
24 /**
25 * Adds additional behaviors necessary for stack capture/restore
26 * on top of {@link Stack}.
27 *
28 * @author Kohsuke Kawaguchi
29 */
30 public final class StackRecorder extends Stack {
31
32 private static final Log log = LogFactory.getLog(StackRecorder.class);
33 private static final long serialVersionUID = 2L;
34
35 private static final ThreadLocal threadMap = new ThreadLocal();
36
37 /**
38 * True, if the continuation restores the previous stack trace to the last
39 * invocation of suspend().
40 *
41 * <p>
42 * This field is accessed from the byte code injected into application code,
43 * and therefore defining a wrapper get method makes it awkward to
44 * step through the user code. That's why this field is public.
45 */
46 public transient boolean isRestoring = false;
47
48 /**
49 * True, is the continuation freeze the strack trace, and stops the
50 * continuation.
51 *
52 * @see #isRestoring
53 */
54 public transient boolean isCapturing = false;
55
56 /** Context object given by the user */
57 private transient Object context;
58
59 /**
60 * Creates a new empty {@link StackRecorder} that runs the given target.
61 */
62 public StackRecorder( final Runnable pTarget ) {
63 super(pTarget);
64 }
65
66 /**
67 * Creates a clone of the given {@link StackRecorder}.
68 */
69 public StackRecorder(final Stack pParent) {
70 super(pParent);
71 }
72
73 public static void suspend() {
74 log.debug("suspend()");
75
76 final StackRecorder stackRecorder = get();
77 if(stackRecorder == null) {
78 throw new IllegalStateException("No continuation is running");
79 }
80
81 stackRecorder.isCapturing = !stackRecorder.isRestoring;
82 stackRecorder.isRestoring = false;
83 }
84
85 public StackRecorder execute(final Object context) {
86 final StackRecorder old = registerThread();
87 try {
88 isRestoring = !isEmpty(); // start restoring if we have a filled stack
89 this.context = context;
90
91 if (isRestoring) {
92 log.debug("Restoring state of " + ReflectionUtils.getClassName(runnable) + "/" + ReflectionUtils.getClassLoaderName(runnable));
93 }
94
95 log.debug("calling runnable");
96 runnable.run();
97
98 if (isCapturing) {
99 if(isEmpty()) {
100 // if we were really capturing the stack, at least we should have
101 // one object in the reference stack. Otherwise, it usually means
102 // that the application wasn't instrumented correctly.
103 throw new IllegalStateException("stack corruption. Is "+runnable.getClass()+" instrumented for javaflow?");
104 }
105 // top of the reference stack is the object that we'll call into
106 // when resuming this continuation. we have a separate Runnable
107 // for this, so throw it away
108 popReference();
109 return this;
110 } else {
111 return null; // nothing more to continue
112 }
113 } catch(ContinuationDeath cd) {
114 // this isn't an error, so no need to log
115 throw cd;
116 } catch(Error e) {
117 log.error(e.getMessage(),e);
118 throw e;
119 } catch(RuntimeException e) {
120 log.error(e.getMessage(),e);
121 throw e;
122 } finally {
123 this.context = null;
124 deregisterThread(old);
125 }
126 }
127
128 public Object getContext() {
129 return context;
130 }
131
132 /**
133 * Bind this stack recorder to running thread.
134 */
135 private StackRecorder registerThread() {
136 StackRecorder old = get();
137 threadMap.set(this);
138 return old;
139 }
140
141 /**
142 * Unbind the current stack recorder to running thread.
143 */
144 private void deregisterThread(final StackRecorder old) {
145 threadMap.set(old);
146 }
147
148 /**
149 * Return the continuation, which is associated to the current thread.
150 */
151 public static StackRecorder get() {
152 return (StackRecorder)threadMap.get();
153 }
154 }