With the introduction of TraceObject it becomes possible to create and store trace logs for further
analysis. In March, I started to create a little tracetool.rex/tracutil.cls to help the Rexx
programmers analyze their code. Testing and analyzing what can be inferred, it turned out that in
the case of a started message and a reply statement, no information about the caller/invoker is
currently available, making it very difficult, if not impossible, at times to find out which
instruction caused the creation of a new activity.
In the discussions on the developer list, it became clear that storing a StackFrame object itself
may cause crashes if the activity for which it was created has gone away if I understood the problem
correctly. Therefore, TraceObject would not store the StackFrame object but rather create a
StringTable receiving the relevant StackFrame information such that accessing the StringTable later
would not cause any problems.
This information gets only added to TraceObject if a TRACE_PREFIX_INVOCATION is active (tracing the
invocation of a routine or method).
Looking around in the source code for ways to safely make the caller's stackframe information
available in the case that a new activity gets spawned as a result of a start message or a reply
keyword statement, the following idea would be proposed, hoping that it is safe and does not impact
the interpreter unduly:
*
define a method Activity::generateCallerStackFrame(bool skipFirst)
o
this returns the caller's StackFrame
*
define for Activity a field "StringTable *spawnerStackFrameInfo"
o
this is used to store a StringTable with the caller's StackFrame
information
*
define a method Activity::setCallerStackFrameAsStringTable(Activity
*oldActivity, Activity
*newActivity, bool skipFirst)
o
o this method gets only invoked in "Method::start()" and in the reply
section of
"RexxActivation::run(...)", which are the two cases where a new activity
gets spawned to
execute the message in the case of "Method:start()" and the remaining
instructions in the
case of a reply keyword instruction
o
this method queries the caller's stackframe using
Activity::generateCallerStackFrame(skipFirst) and using
"RexxActivation::getStackFrameAsStringTable(StackFrameClass *
stackFrame)" returning a
StringTable with the stackframe information and storing it with
"spawnerStackFrameInfo" field
o
in addition, it will get an entry THREAD with the thread ID added, such
that a) the correct
invocation can be located (the same location could be used over and over
from different
threads; it is important to become able to identify which
activity/thread was used) and b)
one can distinguish with the callerStackFrame in TraceObject whether it
got created
An experimental implementation seems to work, and so far, no adverse behavior
has been observed.
Here the diff:
*Index: interpreter/classes/MessageClass.cpp*
===================================================================
--- interpreter/classes/MessageClass.cpp (revision 12859)
+++ interpreter/classes/MessageClass.cpp (working copy)
@@ -537,6 +537,10 @@
// spawn a new activity off of the old activity
Activity *oldActivity = ActivityManager::currentActivity;
Activity *newActivity = oldActivity->spawnReply();
+ + // save current frame info in a StringTable as it has caused the
creation of a new activity
+ Activity::setCallerStackFrameAsStringTable(oldActivity, newActivity,
true); // create
stackframe from parent/caller +
// mark which activity we're running on, then dispatch the message
// on the new activity (which is sitting waiting for work to perform)
setField(startActivity, newActivity);
*Index: interpreter/concurrency/Activity.cpp*
===================================================================
--- interpreter/concurrency/Activity.cpp (revision 12859)
+++ interpreter/concurrency/Activity.cpp (working copy)
@@ -1159,31 +1159,54 @@
}
+
/**
- * Generates a StackFrame from the parent and returns it.
+ * Generate the caller's stack frame.
*
- * @return parent's StackFrame or NULLOBJECT, if no parent exists
+ * @param skipFirst Determines if we should skip the first frame: + * false
for reply in
RexxActivation::run(...), + * true for Message::start() and in the general
case. + * + * @return
The stackframe of the caller which caused a spawned activity.
*/
-StackFrameClass* Activity::generateParentStackFrame()
+StackFrameClass* Activity::generateCallerStackFrame(bool skipFirst)
{
- // create lists for both the stack frames and the traceback lines
- StackFrameClass *parentStackFrame = NULLOBJECT;
-
ActivationFrame *frame = activationFrames;
+ StackFrameClass *callerStackFrame = NULL;
- if (frame != NULL)
+ if (frame && skipFirst)
{
- frame = frame->next; // get parent
- if (frame != NULL)
- {
- parentStackFrame = frame->createStackFrame();
- }
+ frame = frame->next;
}
- return parentStackFrame;
+ if (frame) + { + callerStackFrame = frame->createStackFrame(); + } +
return callerStackFrame;
}
/**
+ * Generates and saves a StringTable to store stackFrame infos of
oldActivity in newActivity +
* (for TraceObject), used by MessageClass::start(). + * + */ +void
Activity::setCallerStackFrameAsStringTable(Activity *oldActivity, Activity
*newActivity, bool
skipFirst) +{ + // create lists for both the stack frames and the traceback
lines + StringTable
*spawnerStackFrameInfo = NULLOBJECT; + + StackFrameClass *oldActivityStackFrame
= oldActivity ->
generateCallerStackFrame(skipFirst); + // returns a StringTable that may be
empty if
oldActivityStackFrame==NULLOBJECT + newActivity ->
spawnerStackFrameInfo=RexxActivation::getStackFrameAsStringTable(oldActivityStackFrame);
+ //
save thread ID to indicate from which thread ID the spawnReply() came from
(helpful, if
StringTable is empty) + newActivity -> spawnerStackFrameInfo ->
put(new_integer(oldActivity ->
getIdntfr()), GlobalNames::THREAD ); + return; +}
+
+
+
+/**
* Build a message and perform the indicated substitutions.
*
* @param messageCode
*Index: interpreter/concurrency/Activity.hpp*
===================================================================
--- interpreter/concurrency/Activity.hpp (revision 12859)
+++ interpreter/concurrency/Activity.hpp (working copy)
@@ -159,8 +159,12 @@
void unwindToFrame(RexxActivation *frame);
void cleanupStackFrame(ActivationBase *poppedStackFrame);
ArrayClass *generateStackFrames(bool skipFirst);
- StackFrameClass *generateParentStackFrame();
+ // allow TraceObject's callerStackFrame entry to indicate the caller that caused the spawned
activity + StackFrameClass* Activity::generateCallerStackFrame(bool ); +
static void
Activity::setCallerStackFrameAsStringTable(Activity *oldActivity, Activity
*newActivity, bool);
+ StringTable *spawnerStackFrameInfo;
+
Activity *spawnReply();
void exitKernel();
*Index: interpreter/execution/RexxActivation.cpp*
===================================================================
--- interpreter/execution/RexxActivation.cpp (revision 12859)
+++ interpreter/execution/RexxActivation.cpp (working copy)
@@ -702,6 +702,9 @@
activity = oldActivity->spawnReply();
+ // save current frame info in a StringTable as it has caused the creation of a new activity +
Activity::setCallerStackFrameAsStringTable(oldActivity, activity, false);
+
// save the pointer to the start of our stack frame. We're
// going to need to release this after we migrate
everything
// over.
@@ -5157,8 +5160,23 @@
}
else if (tracePrefix == TRACE_PREFIX_INVOCATION) // tracing an
invocation entry
{
- StackFrameClass *stackFrame = activity ->
generateParentStackFrame();
- traceObject -> put(stackFrame ?
getStackFrameAsStringTable(stackFrame) : TheNilObject,
GlobalNames::CALLERSTACKFRAME);
+ StackFrameClass *stackFrame = activity -> generateCallerStackFrame(true);
+ if (stackFrame) +
{ + traceObject -> put(getStackFrameAsStringTable(stackFrame),
GlobalNames::CALLERSTACKFRAME); +
} + else + { + if (activity -> spawnerStackFrameInfo) // spawner's frame
infos saved as a
StringTable? + { + // allows for analyzing trace logs to identify the caller
of a spawned
activity + traceObject -> put(activity -> spawnerStackFrameInfo,
GlobalNames::CALLERSTACKFRAME);
+ } + else + { + traceObject -> put(TheNilObject,
GlobalNames::CALLERSTACKFRAME); + } + }
}
// METHODCALL, fill in method related information
@@ -5232,26 +5250,28 @@
* the identityHash of the StackFrame entries EXECUTABLE and TARGET if
they are not
* .nil.
*
-* @param stackFrame the StackFrame object to use
-* @return a StringTable with all the entries of stackFrame
+* @param stackFrame the StackFrame object to use, may be NULLOBJECT +*
@return a StringTable
with all the entries of stackFrame, if available
*/
StringTable * RexxActivation::getStackFrameAsStringTable(StackFrameClass *
stackFrame)
{
- ProtectedObject result;
StringTable *tmpStringTable = new_string_table();
- // array
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::ARGUMENTS ,
result), GlobalNames::ARGUMENTS );
+ if (stackFrame != NULLOBJECT) + { + ProtectedObject result; + // array +
tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::ARGUMENTS , result), GlobalNames::ARGUMENTS );
- // strings
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::LINE ,
result), GlobalNames::LINE );
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::NAME ,
result), GlobalNames::NAME );
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::TRACELINE ,
result), GlobalNames::TRACELINE );
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::TYPE ,
result), GlobalNames::TYPE );
+ // strings + tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::LINE , result),
GlobalNames::LINE ); + tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::NAME ,
result), GlobalNames::NAME ); + tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::TRACELINE , result),
GlobalNames::TRACELINE ); +
tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::TYPE , result),
GlobalNames::TYPE );
- // objects
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::EXECUTABLE,
result), GlobalNames::EXECUTABLE);
- tmpStringTable -> put(stackFrame->sendMessage(GlobalNames::TARGET ,
result), GlobalNames::TARGET );
-
+ // objects + tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::EXECUTABLE, result),
GlobalNames::EXECUTABLE); + tmpStringTable ->
put(stackFrame->sendMessage(GlobalNames::TARGET ,
result), GlobalNames::TARGET );
+ }
return tmpStringTable;
}
To get at the patch directly, I created a RFE with the above patch at
<https://sourceforge.net/p/oorexx/feature-requests/842/>.
Any comments, suggestions?
---rony
_______________________________________________
Oorexx-devel mailing list
Oorexx-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/oorexx-devel