[erlang-questions] Deterministic playback of a simulation?

26 views
Skip to first unread message

Richard Evans

unread,
May 17, 2012, 10:36:05 PM5/17/12
to erlang-q...@erlang.org
I have a multiplayer simulation game running in erlang, involving
multiple characters making decisions etc. Each game instance uses a
few (currently, 4) erlang processes running concurrently,
communicating with an AI simulator written in C.

Everything is working well and I am enjoying using erlang.

But there is one thing I am finding tricky.

For QA purposes, I want to be able to record the exact sequence of
function-calls, so I can play the game back exactly,
deterministically.

My current approach is this: I have a global ets table storing a list
of function-calls. Then, whenever I was calling a function I want to
record - instead of calling the function directly - I call a procedure
which adds the function to the list of function-calls, and then calls
it:

call_and_store(Fun, Args) ->
add_to_script(Fun, Args);
apply(lobby, Fun, Args).

add_to_script(Fun, Args) ->
Info = ets:info(script),
{size, Size} = lists:keyfind(size, 1, Info),
io:format("Inserting {~p,~p,~p}~n", [Size,Fun,Args]),
ets:insert(script, {Size, Fun, Args}).

This approach does not seem to work well because of erlang's
pre-emptive scheduling. If two concurrent processes both invoke
call_and_store, then it is possible (and seems to actually be
happening) that erlang's pre-emptive scheduler may stop processing one
instance of call_and_store between execution of add_to_script and
execution of apply. If this happens, the order of execution and the
order of recorded function-calls will diverge, we don't have an
accurate recording of the set of function-calls, and we won't be able
to deterministically playback.

So my question is: is there a way to prevent the scheduler from
yielding during a block of code? Some way to insist the block is
called as one unit, like this:

call_and_store(Fun, Args) ->
!!!prevent_yielding,
add_to_script(Fun, Args);
apply(lobby, Fun, Args),
!!!allow_yielding.

My guess is: no. This does not seem very erlangy.

Alternatively, is there a different approach which can produce the
desired list of function-calls? My current approach seems to go
against the erlang grain, involving writing to a shared global table.
Is there a better, more erlangy, way of doing this?



thanks,
~Richard
_______________________________________________
erlang-questions mailing list
erlang-q...@erlang.org
http://erlang.org/mailman/listinfo/erlang-questions

Enrique Paz

unread,
May 18, 2012, 2:45:14 AM5/18/12
to Richard Evans, erlang-q...@erlang.org
If I get it right, your issue is that the logged function calls might be in the wrong order. Couldn't you add a timestamp to the call info you are logging? erlang:now() or os:timestamp/0 (faster) return a timestamp up to microseconds, then the only action left to do would be ordering your logs before using them.

  
2012/5/18 Richard Evans <richardpri...@gmail.com>



--
quique

Attila Rajmund Nohl

unread,
May 18, 2012, 4:49:25 AM5/18/12
to erlang-questions
2012/5/18 Richard Evans <richardpri...@gmail.com>:
[...]
> For QA purposes, I want to be able to record the exact sequence of
> function-calls, so I can play the game back exactly,
> deterministically.

You could try to trace on the function calls with timestamps.

Hynek Vychodil

unread,
May 18, 2012, 5:04:26 AM5/18/12
to Richard Evans, erlang-q...@erlang.org
Hi.

What about using erlang tracing facility. It hase some nice features.
There will be zero inpact in production code. There will be zero
inpact in production environment if you turn it on only in testing
environment and also there is "automatic" ordering of events. I think
it is best fit for purpose.

Hynek Vychodil

Ulf Wiger

unread,
May 18, 2012, 7:38:05 AM5/18/12
to Richard Evans, erlang-q...@erlang.org

On 18 May 2012, at 04:36, Richard Evans wrote:

> So my question is: is there a way to prevent the scheduler from
> yielding during a block of code? Some way to insist the block is
> called as one unit, like this:
>
> call_and_store(Fun, Args) ->
> !!!prevent_yielding,
> add_to_script(Fun, Args);
> apply(lobby, Fun, Args),
> !!!allow_yielding.
>
> My guess is: no. This does not seem very erlangy.

No, not at that level.

But you could use e.g.

global:trans({call_and_store, self()}, fun() -> call_and_store(Fun, Args) end).

See erl -man global

You can specify on how many nodes you want to acquire a lock - default is all connected nodes, but you can set it e.g. to [node()] if that suits you better.

BR,
Ulf W

Ulf Wiger, Co-founder & Developer Advocate, Feuerlabs Inc.
http://feuerlabs.com

Richard Evans

unread,
May 18, 2012, 12:30:00 PM5/18/12
to Ulf Wiger, erlang-q...@erlang.org
Thanks guys for all your helpful responses :)
Reply all
Reply to author
Forward
0 new messages