Skip to content

Commit

Permalink
erlang:trace_pattern/3 improvements
Browse files Browse the repository at this point in the history
call-traces are now properly tail-recursive,
unless return_trace or exception_trace are 
used in the tracing spec (just like BEAM).

Also, tracing now honours ‘silent’ and ‘message’
options.
  • Loading branch information
krestenkrab committed Apr 28, 2016
1 parent 1774b72 commit 43759f2
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 28 deletions.
57 changes: 43 additions & 14 deletions src/main/java/erjang/EModuleManager.java
Expand Up @@ -46,6 +46,8 @@ public class EModuleManager {
static EAtom am_prep_stop = EAtom.intern("prep_stop");
static EAtom am___info__ = EAtom.intern("__info__");
static EAtom am_call = EAtom.intern("call");
static EAtom am_return_from = EAtom.intern("return_from");
static EAtom am_exception_from = EAtom.intern("exception_from");
static EAtom am_trace = EAtom.intern("trace");

static class FunctionInfo {
Expand Down Expand Up @@ -267,34 +269,61 @@ static class TraceHandler implements EFunHandler {

@Override
public EObject invoke(EProc proc, EObject[] args) throws Pausable {
long state = before(proc, args);
ESeq argList = EList.fromArray(args);
EMatchSpec.TraceState state = before(proc, argList);

if (state == null || (state.return_trace==false && state.exception_trace==false))
return ErlBif.apply_last(proc, target, argList);

EObject result;
try {
return target.invoke(proc, args);
} finally {
after(proc, state);
result = target.invoke(proc, args);

} catch (ErlangException e) {

if (state.exception_trace && !proc.get_trace_flags().silent) {
ETuple3 info = ETuple3.make_tuple(owner.fun.module, owner.fun.function, ERT.box(owner.fun.arity));
ERT.do_trace(proc, am_exception_from, info, new ETuple2(e.getExClass(), e.reason()));
}

throw e;
}

if (state.return_trace && !proc.get_trace_flags().silent) {
ETuple3 info = ETuple3.make_tuple(owner.fun.module, owner.fun.function, ERT.box(owner.fun.arity));
ERT.do_trace(proc, am_return_from, info, result);
}

return result;
}

long before(EProc proc, EObject[] args) throws Pausable {
if (!proc.get_trace_flags().call || trace_spec.is_false) return 0;
ESeq argList = null;
if (trace_spec.is_true || trace_spec.ms.matches(proc, argList = EList.make((Object[])args))) {
EMatchSpec.TraceState before(EProc proc, ESeq argList) throws Pausable {
if (!proc.get_trace_flags().call || trace_spec.is_false)
return null;

EMatchSpec.TraceState state = null;
if ((trace_spec.ms==null && trace_spec.is_true) || (state=trace_spec.ms.matches(proc, argList)) != null) {
counter.incrementAndGet();

// send {trace, Pid, call, {M, F, Args}}

EObject arg = (proc.get_trace_flags().arity)
? ERT.box(owner.fun.arity)
: (argList == null ? EList.make((Object[])args) : argList);
: argList;

ERT.do_trace(proc, am_call, ETuple3.make_tuple(owner.fun.module, owner.fun.function, arg));
}
return 0;
}
if (!proc.get_trace_flags().silent && (state==null||state.message != ERT.FALSE)) {
ETuple3 info = ETuple3.make_tuple(owner.fun.module, owner.fun.function, arg);
if ((state == null || state.message == ERT.TRUE))
ERT.do_trace(proc, am_call, info);
else
ERT.do_trace(proc, am_call, info, state.message);

void after(EProc proc, long state) {
}

}
return state;
}

}

static class ModuleInfo {
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/erjang/ERT.java
Expand Up @@ -317,6 +317,7 @@ public static EAtom guard(boolean bool) {

public static final ENil NIL = new ENil();
public static final EAtom am_EXIT = EAtom.intern("EXIT");
public static final EAtom am_ERROR = EAtom.intern("ERROR");
public static final EAtom IGNORED = EAtom.intern("ignored");
private static final EAtom am_badmatch = EAtom.intern("badmatch");
private static final EAtom am_case_clause = EAtom.intern("case_clause");
Expand Down Expand Up @@ -1330,6 +1331,16 @@ public static void do_trace(EProc proc, EAtom what, EObject info) throws Pausabl

}

public static void do_trace(EProc proc, EAtom what, EObject info, EObject message) throws Pausable {

EPID pid = proc.get_trace_flags().tracer;

EObject msg = ETuple.make(am_trace, proc.self_handle(), what, info, message);

pid.send(proc.self_handle(), msg);

}


@BIF
public static EInteger trace_pattern(EObject arg0, EObject arg1, EObject arg2) {
Expand Down Expand Up @@ -1405,6 +1416,7 @@ public static EInteger trace(EProc self_proc, EObject arg0, EObject arg1, EObjec
static EAtom am_receive = EAtom.intern("receive");
static EAtom am_procs = EAtom.intern("procs");
static EAtom am_tracer = EAtom.intern("tracer");
static EAtom am_silent = EAtom.intern("silent");

static public class TraceFlags implements Cloneable {

Expand All @@ -1414,6 +1426,7 @@ static public class TraceFlags implements Cloneable {
boolean receive = false;
boolean procs = false;
EPID tracer = null;
public boolean silent;

public TraceFlags clone() {
try {
Expand Down Expand Up @@ -1441,6 +1454,8 @@ public void update(boolean how, ESeq flags, EPID self)
receive = how;
} else if (flag == am_procs) {
procs = how;
} else if (flag == am_silent) {
silent = how;
} else if ((t=ETuple2.cast(flag))!= null
&& t.elem1==am_tracer) {
tracer = t.elem2.testPID();
Expand Down
82 changes: 68 additions & 14 deletions src/main/java/erjang/m/ets/EMatchSpec.java
Expand Up @@ -271,11 +271,11 @@ public EObject eval(EMatchContext ctx) {

boolean what = (action == am_enable_trace);

EProc proc = EMatchSpec.current_caller();

if (proc == null) {
throw badarg(vals);
TraceState state = EMatchSpec.current_state();
if (state == null) {
return ERT.am_ERROR;
}
EProc proc = state.proc;

ERT.TraceFlags tf;
ESeq opts;
Expand All @@ -288,16 +288,20 @@ public EObject eval(EMatchContext ctx) {
tf = pid.task().get_own_trace_flags();
opts = vals[1].testSeq();
} else {
throw badarg(vals);
return ERT.FALSE;
}

tf.update(what, opts, proc.self_handle());
return ERT.TRUE;


} else if (action == am_trace) {
} else if (action == am_trace && (vals.length==2 || vals.length==3)) {

EProc proc = EMatchSpec.current_caller();
TraceState state = EMatchSpec.current_state();
if (state == null) {
return ERT.am_ERROR;
}
EProc proc = state.proc;

if (proc == null) {
throw badarg(vals);
Expand All @@ -314,7 +318,7 @@ public EObject eval(EMatchContext ctx) {
disable = vals[0].testSeq();
enable = vals[1].testSeq();
} else {
throw badarg(vals);
return ERT.FALSE;
}

if (pid == null || disable == null || enable == null) {
Expand All @@ -330,6 +334,42 @@ public EObject eval(EMatchContext ctx) {
return ERT.FALSE;
}

} else if (action == am_silent && vals.length==1) {
TraceState state = EMatchSpec.current_state();
if (state == null) {
return ERT.am_ERROR;
}
EProc proc = state.proc;

proc.get_own_trace_flags().silent = (vals[0].testBoolean() == ERT.TRUE);
return ERT.TRUE;

} else if (action == am_message && vals.length==1) {
TraceState state = EMatchSpec.current_state();
if (state == null) {
return ERT.am_ERROR;
}

state.message = vals[0];
return ERT.TRUE;

} else if (action == am_return_trace && vals.length==0) {
TraceState state = EMatchSpec.current_state();
if (state == null) {
return ERT.am_ERROR;
}
state.return_trace = true;
return ERT.TRUE;

} else if (action == am_exception_trace && vals.length==0) {
TraceState state = EMatchSpec.current_state();
if (state == null) {
return ERT.am_ERROR;
}
state.return_trace = true;
state.exception_trace = true;
return ERT.TRUE;

} else {
throw new NotImplemented("ActionCall " + action + " " + aa);
}
Expand Down Expand Up @@ -1148,18 +1188,32 @@ public boolean matches(EObject candidate) {
return match(candidate) == ERT.TRUE;
}

final static ThreadLocal<EProc> caller = new ThreadLocal<EProc>();
final static ThreadLocal<TraceState> caller = new ThreadLocal<TraceState>();

public static EProc current_caller() {
public static TraceState current_state() {
return caller.get();
}

public boolean matches(EProc proc, EObject candidate)
public static class TraceState {
final EProc proc;
public EObject message = ERT.TRUE;
public boolean return_trace;
public boolean exception_trace;

TraceState(EProc proc) { this.proc = proc; }
}

public TraceState matches(EProc proc, EObject candidate)
{
EProc old = caller.get();
caller.set(proc);
TraceState old = caller.get();
TraceState out = new TraceState(proc);
caller.set(out);
try {
return this.matches(candidate);
if ( this.matches(candidate) )
return out;
else
return null;

} finally {
caller.set(old);
}
Expand Down

0 comments on commit 43759f2

Please sign in to comment.