tags:

views:

595

answers:

3

A common pattern in Erlang is the recursive loop that maintains state:

loop(State) ->
  receive
    Msg ->
      NewState = whatever(Msg),
      loop(NewState)
  end.

Is there any way to query the state of a running process with a bif or tracing or something? Since crash messages say "...when state was..." and show the crashed process's state, I thought this would be easy, but I was disappointed that I haven't been able to find a bif to do this.

So, then, I figured using the dbg module's tracing would do it. Unfortunately, I believe because these loops are tail call optimized, dbg will only capture the first call to the function.

Any solution?

+3  A: 

It looks like you're making the problem out of nothing. erlang:process_info/1 gives enough information for debugging purposes. If your REALLY need loop function arguments, why don't you give it back to caller in response to one of the special messages that you define yourself?

UPDATE: Just to clarify terminology. The closest thing to the 'state of the process' on the language level is process dictionary, usage of which is highly discouraged. It can be queried by erlang:process_info/1 or erlang:process/2. What you actually need is to trace process's local functions calls along with their arguments:

-module(ping).
-export([start/0, send/1, loop/1]).                                                          

start() ->                                                                                   
     spawn(?MODULE, loop, [0]).                                                              

send(Pid) ->                                                                                 
    Pid ! {self(), ping},                                                                    
    receive                                                                                  
    pong ->                                                                                  
         pong                                                                                
    end.                                                                                     

loop(S) ->                                                                                   
    receive                                                                                  
    {Pid, ping} ->                                                                           
        Pid ! pong,                                                                          
        loop(S + 1)                                                                          
    end.

Console:

Erlang (BEAM) emulator version 5.6.5 [source] [smp:2] [async-threads:0] [kernel-poll:false]  

Eshell V5.6.5  (abort with ^G)                                                               
1> l(ping).                                                                                  
{module,ping}                                                                                
2> erlang:trace(all, true, [call]).                                                          
23                                                                                           
3> erlang:trace_pattern({ping, '_', '_'}, true, [local]).                                    
5                                                                                            
4> Pid = ping:start().                                                                       
<0.36.0>                                                                                     
5> ping:send(Pid).                                                                           
pong                                                                                         
6> flush().                                                                                  
Shell got {trace,<0.36.0>,call,{ping,loop,[0]}}                                              
Shell got {trace,<0.36.0>,call,{ping,loop,[1]}}                                              
ok                                                                                           
7>
zakovyrya
Since erlang has all these cool facilities for examining and debugging live systems, I was hoping for something general.You don't think knowing the state of a process is useful for debugging? Why do debuggers in any language have plenty of features for examining and interacting with variable state?
mwt
Awesome. This is what I was looking for.
mwt
+2  A: 

As far as I know you cant get the arguments passed to a locally called function. I would love for someone to prove me wrong.

-module(loop).
-export([start/0, loop/1]).
start() ->
  spawn_link(fun () -> loop([]) end).
loop(State) ->
  receive 
    Msg ->
      loop([Msg|State])
  end.

If we want to trace this module you do the following in the shell.

dbg:tracer().
dbg:p(new,[c]).                   
dbg:tpl(loop, []).

Using this tracing setting you get to see local calls (the 'l' in tpl means that local calls will be traced as well, not only global ones).

5> Pid = loop:start().
(<0.39.0>) call loop:'-start/0-fun-0-'/0
(<0.39.0>) call loop:loop/1
<0.39.0>
6> Pid ! foo.
(<0.39.0>) call loop:loop/1
foo

As you see, just the calls are included. No arguments in sight.

My recommendation is to base correctness in debugging and testing on the messages sent rather than state kept in processes. I.e. if you send the process a bunch of messages, assert that it does the right thing, not that it has a certain set of values.

But of course, you could also sprinkle some erlang:display(State) calls in your code temporarily. Poor man's debugging.

Christian
I want to keep the number of http requests (using ibrowse) that any of my children are using simultaneously to under 100. In this case, I have a throttling process that uses a counter. I'm worried that the counter is going to get out of sync with the resource it's counting, and that the app will get slower and slower. So, the problem is that I can't easily test whether it's doing the right thing. The program will continue to run fine, just more slowly.
mwt
+6  A: 

If your process is using OTP, it is enough to do sys:get_status(Pid).

The error message you mentions is displayed by SASL. SASL is an error reporting daemon in OTP.

The state you are referring in your example code is just an argument of tail recursive function. There is no way to extract it using anything except for tracing BIFs. I guess this would be not a proper solution in production code, since tracing is intended to be used only for debug purposes.

Proper, and industry tested, solution would be make extensive use of OTP in your project. The you would take full advantage of SASL error reporting, rb module to collect these reports, sys - to inspect the state of the running OTP-compatible process, proc_lib - to make short-lived processes OTP-compliant, etc.

gleber
The function is sys:get_status/1.
cthulahoops
+1: sys:get_status/1 is your friend. I use this all the time.
Cayle Spandon
Haha, awesome stuff! I'll use this all the time, too.Incidentally, gleber, I do intend to only use it for debugging, not for long term logging on a production system. And, of course, I know what the state is that I mention. I'm not sure why folks in this thread keep wanting to clarify this. I'm using state in exactly the same way as, e.g., Joe Armstrong does in his book. There's no other proper way to maintain temporary state in Erlang than by threading it through recursive loops. In fact, my understanding is that that's precisely what's going on behind the scenes in gen_server, too.
mwt
As a matter of fact, notice that the function for OTP use is even CALLED get_state and you pass it a Pid. That's getting the state of a process. Which is what I asked. I don't understand why every single question everywhere has to inspire silly semantic rebukes.
mwt
@mwt: Oh, come on! It's not that bad :) To get right answers you have to ask right questions, it's that simple
zakovyrya
Thanks for pointing out a typo with sys:get_status/1.@mwt, sorry if I provided too obvious information in my answer. I just wanted to make it as throughout as possible - with as much context as possible (even a obvious/silly one).@mwt, if you like my answer - please accept it ^^
gleber
Didn't know about this accepting answer thing...sorry for the delay.
mwt