Simple increment in erlang - erlang

I am looking to build simple erlang logic where an actor maintains the count of how many times it was invoked.
For example, here is the actor(many actors can be possible):
-module(actor).
-export([do_work/0]).
do_work() ->
increment = increment + 1
Suppose I invoke actor (with some Pid: XYZ) 5 times, XYZ = 5 because it was executed 5 times. But in Erlang, variables are immutable. So if I do increment++ for the first run, I cannot store the new result in increment or do increment = increment + 1. How do I maintain the count because I cannot create new variables dynamically say, increment_iteration_1 = increment + 1 and then do increment_iteration_2 = increment1 + 1 and so on.....in code for 1000 iterations?

Your question touches the main difference between Erlang and other popular languages. In order to maintain and update some state - as the counter in your example - in Erlang you need to do some specific things, which I describe below. (Other solutions, like using a database, I will skip here since I assume you want to know how to do state maintenance directly).
In Erlang what you call actor is called process. And yes, to maintain a state, you need a process. The code you presented, though named actor, is not a process. It is just a module, which means that it is just some code. To use a process you need to start it, keep it running, and communicate with it using messages. It may sound complicated, but Erlang standard library provides gen_server, which does most of the work for you.
The sample code implementing a counter with gen_server would look like that:
-module(actor).
-behaviour(gen_server).
-export([do_work/0, init/1, handle_cast/2, handle_call/3]).
do_work() ->
gen_server:cast(actor_server, do_work).
init(_Arguments) ->
{ok, 0}.
handle_cast(do_work, Counter) ->
{noreply, Counter + 1}.
handle_call(_Msg, _From, Counter) ->
{noreply, Counter}.
Now somewhere in your code you need to start your process with
gen_server:start_link({local, actor_server}, actor, [], [])
Then, whenever you call actor:do_work(), it will increment the counter.
Few things worth mentioning are:
actor is a callback module. Gen_server internally does the hard work and calls callback functions from your module when it needs
The init callback function is called once when your process starts. I used it here to initialise the counter.
The process is registered with the name actor_server. When do_work calls gen_server:cast internally it sends a message to the process registered under the actor_server name. If you need several processes doing the same counter separately, more housekeeping is required, which I will skip here.
The handle_call part, not used here, provides the functionality where you can receive a reply (e.g. updated counter) from the process.
calling gen_server:start_link directly is good for testing, but in real code you will have a supervisor starting the process for you
More information about gen_server you will find its documentation.

Related

what's the process state when a erlang process is running `receive after`?

I want know the the erlang process state when this process is running receive after:
receive
X ->
ok
after 1000 ->
ok
end
1、Is the process state is running or waiting?
2、Does this process will use cpu schedler time?
3、If i have 120000 erlang process like this, every process will run code like this:
receive
X ->
ok
after 1000 ->
ok
end
So, Does this code will be a bottleneck?
The process is just moving along with whatever comes after the receive expression.
For example, let's say a inline a request/response:
ask_foo(SomePID) ->
Ref = make_ref(),
SomePID ! {self(), Ref, why},
receive
{Ref, Answer} ->
io:format("The answer: ~tp~n", [Answer])
after
1000 ->
io:format("~p is too slow. Moving on...~n", [SomePID])
end,
io:format("I'll print this in any case, and then exit.").
receive blocks until it either receives a message that matches one of its receive clauses, or the timeout occurs -- whichever happens first. Then it continues on doing whatever else is in its code. Very often there is a single receive loop, but it is not uncommon to use a series of inline receive clauses for things that should block, like waiting on a fixed sequence of inputs from a user or something similar.
The "process's state" is not changing in terms of its state data at all. It is blocking -- which means it is suspended until a message or a timeout occurrs. But, unlike polling systems, this does not carry an overhead penalty with it because the VM is managing the scheduling (the process doesn't have to wake itself up, it can safely block on receive).
You asked if this will be a bottleneck: No. No other processes are blocking, only this one. All other processes are executing on their own schedule, and they have nothing to do with this one. So when blocking on a receive you are only holding up the rest of the things this particular process is supposed to do. Whether or not that is a bottleneck becomes, therefore, an architectural question.

Passing variables through exit signals [Erlang]

I'm wondering if it's possible to send variables from a dying process to it's calling process. I have a process A that spawned another process B through spawn_link. B is about to die by calling exit(killed). I can catch this in A through {'EXIT', From, killed}, but I'd like to pass some variables from B to A before it dies. I can do this by sending a message from B to A right before it dies, but I'm wondering if this is a 'bad' thing to do. Because technically I'd be sending two messages from B to A. Right now, what I have looks like this:
B sends a message with values to A
A receives values and re-enters receive loop
B calls exit(killed)
A receives EXIT message and spawns another linked process
The idea is that B should always exist and when it gets killed, it should be 'resurrected' immediately. What seems like a better alternative in my opinion is to have something like exit(killed, [Variables]) and to catch it with {'EXIT', From, killed, [Variables]}. Is this possible? And if so, are there any reasons for not doing it? Having A store values for B when B hasn't even died yet seems like a bad move. I'd have to start implementing atomic actions to prevent problems with two linked processes dying at the same time. It also forces me to keep the variables in my receive loop.
What I mean is, if I could send values directly with the EXIT call, my loop would look like this:
loop() ->
receive ->
{'EXIT', From, killed, Variables} -> % spawn new linked process with variables
end.
But if I first need to receive a message, get into the loop again to then receive the exit message, I would get;
loop(Vars) ->
receive ->
{values, Variables} -> loop(Variables);
{'EXIT', From, killed} -> % spawn new linked process with variables
end.
This means I keep the list of variables long after I don't need them anymore and I need to enter my loop twice for what could be considered one action.
To answer your question directly: the exit reason can be any term, which means it can also be a tuple like exit({killed, Values}), so instead of receiving {'EXIT', From, killed, Values} you would received {'EXIT', From, {killed, Values}}.
But!
The way you are doing it now is not wrong. Its not particularly ugly, either. Sending a message (especially an asynchronous one) isn't some major operation to be minimized as much as possible, and neither is spawning/killing processes. If your way works for you, fine.
But! (again!)
Why are you doing this in the first place? Consider what it is about state that you need to be shuttling between two processes, one of which you are terminating just then? Should this value be a permanent entity held by the spawning process? Should it die with the worker? Should it be a quantity maintained by a third process and asked for as part of the worker's startup (a more general phrasing of what Łukasz Ptaszyński was getting at)?
I don't know the answers to those questions, because I don't know your program, but they are the things I would think about if I was finding it necessary to do this sort of work. In particular, if there is some base value that process A must seed process B with for it to work, and the next version of the base value is dependent on something process B does, then process B should be returning it as a part of its processing, not as a part of its shutdown.
This seems like a minor semantic difference, but its important to think about. You may find that you shouldn't be terminating B at all, or that you really need A to manage a directory for several concurrent B's and they should seed themselves as they move along, or whatever. You might even find that this means A should be spawning B as a synchronous, monitored operation, not an asynchronous linked one, and the whole herd of processes should be spawned as a complex of multiple managed A-B pairs! I don't know the answers in your case, but these are the things that come to mind on reading what you are doing.
I think you can try this method:
main()->
ParentPid = self(),
From = spawn_link(?MODULE, child, [ParentPid]),
receive
{'EXIT', From, Reason} ->
Reason
end.
child(ParentPid) ->
Value = 2*2,
exit(ParentPid, {killed, Value}).
Please read this link about erlang:exit/2

How to maintain state in Erlang?

I have seen people use dict, ordict, record for maintaining state in many blogs that I have read. I find it as very vital concept.
Generally I understand the meaning of maintaining state and recursions but when it comes to Erlang..I am a little vague about how it is handled.
Any help?
State is the present arrangement of data. It is sometimes hard to remember this for two reasons:
State means both the data in the program and the program's current point of execution and "mode".
We build this up to be some magical thing unnecessarily.
Consider this:
"What is the process's state?" is asking about the present value of variables.
"What state is the process in?" usually refers to the mode, options, flags or present location of execution.
If you are a Turing machine then these are the same question; we have separated the ideas to give us handy abstractions to build on (like everything else in programming).
Let's think about state variables for a moment...
In many older languages you can alter state variables from whatever context you like, whether the modification of state is appropriate or not, because you manage this directly. In more modern languages this is a bit more restricted by imposing type declarations, scoping rules and public/private context to variables. This is really a rules arms-race, each language finding more ways to limit when assignment is permitted. If scheduling is the Prince of Frustration in concurrent programming, assignment is the Devil Himself. Hence the various cages built to manage him.
Erlang restricts the situations that assignment is permitted in a different way by setting the basic rule that assignment is only once per entry to a function, and functions are themselves the sole definition of procedural scope, and that all state is purely encapsulated by the executing process. (Think about the statement on scope to understand why many people feel that Erlang macros are a bad thing.)
These rules on assignment (use of state variables) encourage you to think of state as discreet slices of time. Every entry to a function starts with a clean slate, whether the function is recursive or not. This is a fundamentally different situation than the ongoing chaos of in-place modifications made from anywhere to anywhere in most other languages. In Erlang you never ask "what is the value of X right now?" because it can only ever be what it was initially assigned to be in the context of the current run of the current function. This significantly limits the chaos of state changes within functions and processes.
The details of those state variables and how they are assigned is incidental to Erlang. You already know about lists, tuples, ETS, DETS, mnesia, db connections, etc. Whatever. The core idea to understand about Erlang's style is how assignment is managed, not the incidental details of this or that particular data type.
What about "modes" and execution state?
If we write something like:
has_cheeseburger(BurgerName) ->
receive
{From, ask, burger_name} ->
From ! {ok, BurgerName},
has_cheeseburger(BurgerName);
{From, new_burger, _SomeBurger} ->
From ! {error, already_have_a_burger},
has_cheeseburger(BurgerName);
{From, eat_burger} ->
From ! {ok, {ate, BurgerName}},
lacks_cheeseburger()
end.
lacks_cheeseburger() ->
receive
{From, ask, burger_name} ->
From ! {error, no_burger},
lacks_cheeseburger();
{From, new_burger, BurgerName} ->
From ! {ok, thanks},
has_cheeseburger(BurgerName);
{From, eat_burger} ->
From ! {error, no_burger},
lacks_cheeseburger()
end.
What are we looking at? A loop. Conceptually its just one loop. Quite often a programmer would choose to write just one loop in code and add an argument like IsHoldingBurger to the loop and check it after each message in the receive clause to determine what action to take.
Above, though, the idea of two operating modes is both more explicit (its baked into the structure, not arbitrary procedural tests) and less verbose. We have separated the context of execution by writing basically the same loop twice, once for each condition we might be in, either having a burger or lacking one. This is at the heart of how Erlang deals with a concept called "finite state machines" and its really useful. OTP includes a tool build around this idea in the gen_fsm module. You can write your own FSMs by hand as I did above or use gen_fsm -- either way, when you identify you have a situation like this writing code in this style makes reasoning much easier. (For anything but the most trivial FSM you will really appreciate gen_fsm.)
Conclusion
That's it for state handling in Erlang. The chaos of untamed assignment is rendered impotent by the basic rules of single-assignment and absolute data encapsulation within each process (this implies that you shouldn't write gigantic processes, by the way). The supremely useful concept of a limited set of operating modes is abstracted by the OTP module gen_fsm or can be rather easily written by hand.
Since Erlang does such a good job limiting the chaos of state within a single process and makes the nightmare of concurrent scheduling among processes entirely invisible, that only leaves one complexity monster: the chaos of interactions among loosely coupled actors. In the mind of an Erlanger this is where the complexity belongs. The hard stuff should generally wind up manifesting there, in the no-man's-land of messages, not within functions or processes themselves. Your functions should be tiny, your needs for procedural checking relatively rare (compared to C or Python), your need for mode flags and switches almost nonexistant.
Edit
To reiterate Pascal's answer, in a super limited way:
loop(State) ->
receive
{async, Message} ->
NewState = do_something_with(Message),
loop(NewState);
{sync, From, Message} ->
NewState = do_something_with(Message),
Response = process_some_response_on(NewState),
From ! {ok, Response},
loop(NewState);
shutdown ->
exit(shutdown);
Any ->
io:format("~p: Received: ~tp~n", [self(), Any]),
loop(State)
end.
Re-read tkowal's response for the most minimal version of this. Re-read Pascal's for an expansion of the same idea to include servicing messages. Re-read the above for a slightly different style of the same pattern of state handling with the addition of ouputting unexpected messages. Finally, re-read the two-state loop I wrote above and you'll see its actually just another expansion on this same idea.
Remember, you can't re-assign a variable within the same iteration of a function but the next call can have different state. That is the extent of state handling in Erlang.
These are all variations on the same thing. I think you're expecting there to be something more, a more expansive mechanism or something. There is not. Restricting assignment eliminates all the stuff you're probably used to seeing in other languages. In Python you do somelist.append(NewElement) and the list you had now has changed. In Erlang you do NewList = lists:append(NewElement, SomeList) and SomeList is sill exactly the same as it used to be, and a new list has been returned that includes the new element. Whether this actually involves copying in the background is not your problem. You don't handle those details, so don't think about them. This is how Erlang is designed, and that leaves single assignment and making fresh function calls to enter a fresh slice of time where the slate has been wiped clean again.
The easiest way to maintain state is using gen_server behaviour. You can read more on Learn you some Erlang and in the docs.
gen_server is process, that can be:
initialised with given state,
can have defined synchronous and asynchronous callbacks (synchronous for querying the data in "request-response style" and asynchronous for changing the state with "fire and forget" style)
It also has couple of nice OTP mechanisms:
it can be supervised
it gives you basic logging
its code can be upgraded while the server is running without loosing the state
and so on...
Conceptually gen_server is an endless loop, that looks like this:
loop(State) ->
NewState = handle_requests(State),
loop(NewState).
where handle requests receives messages. This way all requests are serialised, so there are no race conditions. Of course it is a little bit more complicated to give you all the goodies, that I described.
You can choose what data structure you want to use for State. It is common to use records, because they have named fields, but since Erlang 17 maps can come in handy. This one depends on, what you want to store.
Variable are not mutable, so when you want to have an evolution of state, you create a new variable, and later recall the same function with this new state as parameter.
This structure is meant for processes like server, there is no base condition as in the factorial usual example, generally there is a specific message to stop the server smoothly.
loop(State) ->
receive
{add,Item} -> NewState = [Item|State], % create a new variable
loop(NewState); % recall loop with the new variable
{remove,Item} -> NewState = lists:filter(fun(X) -> X /= Item end,State) , % create a new variable
loop(NewState); % recall loop with the new variable
{items,Pid} -> Pid ! {items,State},
loop(State);
stop -> stopped; % this will be the stop condition
_ -> loop(State) % ignoring other message may be interesting in a never ending loop
end

Mnesia: How to lock multiple rows simultaneously so that I can write/read a "consistent" set of of records

HOW I WISH I HAD PHRASED MY QUESTION TO BEGIN WITH
Take a table with 26 keys, a-z and let them have integer values.
Create a process, Ouch, that does two things over and over again
In one transaction, write random values for a, b, and c such that those values always sum to 10
In another transaction, read the values for a, b and c and complain if their values do not sum to 10
If you spin-up even a few of these processes you will see that very quickly a, b and c are in a state where their values do not sum to 10. I believe there is no way to ask mnesia to "lock these 3 records before starting the writes (or reads)", one can only have mnesia lock the records as it gets to them (so to speak) which allows for the set of records' values to violate my "must sum to 10" constraint.
If I am right, solutions to this problem include
lock the entire table before writing (or reading) the set of 3 records -- I hate to lock whole table for 3 recs,
Create a process that keeps track of who is reading or writing which keys and protects bulk operations from anyone else writing or reading until the operation is completed. Of course I would have to make sure that all processes made use of this... crap, I guess this means writing my own AccessMod as the fourth parameter to activity/4 which seems like a non-trivial exercise
Some other thing that I am not smart enough to figure out.
thoughts?
Ok, I'm an ambitious Erlang newbee, so sorry if this is a dumb question, but
I am building an application-specific, in-memory distributed cache and I need to be able to write sets of Key, Value pairs in one transaction and also retrieve sets of values in one transaction. In other words I need to
1) Write 40 key,value pairs into the cache and ensure that no one else can read or write any of these 40 keys during this multi-key write operation; and,
2) Read 40 keys in one operation and get back 40 values knowing that all 40 values have been unchanged from the moment that this read operation started until it ended.
The only way I can think of doing this is to lock the entire table at the beginning of the fetch_keylist([ListOfKeys]) or at the beginning of the write_keylist([KeyValuePairs], but I don't want to do this because I have many processes simultaneously doing their own multi_key reads and writes and I don't want to lock the entire table any time any process needs to read/write a relatively small subset of records.
Help?
Trying to be more clear: I do not think this is just about using vanilla transactions
I think I am asking a more subtle question than this. Imagine that I have a process that, within a transaction, iterates through 10 records, locking them as it goes. Now imagine this process starts but before it iterates to the 3rd record ANOTHER process updates the 3rd record. This will be just fine as far as transactions go because the first process hadn't locked the 3rd record (yet) and the OTHER process modified it and released it before the first process got to it. What I want is to be guaranteed that once my first process starts that no other process can touch the 10 records until the first process is done with them.
PROBLEM SOLVED - I'M AN IDIOT... I guess...
Thank you all for your patients, especially Hynek -Pichi- Vychodil!
I prepared my test code to show the problem, and I could in fact reproduce the problem. I then simplified the code for readability and the problem went away. I was not able to again reproduce the problem. This is both embarrassing and mysterious to me since I had this problem for days. Also mnesia never complained that I was executing operations outside of a transaction and I have no dirty transactions anywhere in my code, I have no idea how I was able to introduce this bug into my code!
I have pounded the notion of Isolation into my head and will not doubt that it exists again.
Thanks for the education.
Actually, turns out the problem was using try/catch around mnesia operations within a transaction. See here for more.
Mnesia transaction will do exactly this thing for you. It is what is transaction for unless you do dirty operations. So just place your write and read operations to one transaction a mnesia will do rest. All operations in one transaction is done as one atomic operation. Mnesia transaction isolation level is what is sometimes known as "serializable" i.e. strongest isolation level.
Edit:
It seems you missed one important point about concurrent processes in Erlang. (To be fair it is not only true in Erlang but in any truly concurrent environment and when someone arguing else it is not really concurrent environment.) You can't distinguish which action happen first and which happen second unless you do some synchronization. Only way you can do this synchronization is using message passing. You have guaranteed only one thing about messages in Erlang, ordering of messages sent from one process to other process. It means when you send two messages M1 and M2 from process A to process B they arrives in same order. But if you send message M1 from A to B and message M2 from C to B they can arrive in any order. Simply because how you can even tell which message you sent first? It is even worse if you send message M1 from A to B and then M2 from A to C and when M2 arrives to C send M3 from C to B you don't have guarantied that M1 arrives to B before M3. Even it will happen in one VM in current implementation. But you can't rely on it because it is not guaranteed and can change even in next version of VM just due message passing implementation between different schedulers.
It illustrates problems of event ordering in concurrent processes. Now back to the mnesia transaction. Mnesia transaction have to be side effect free fun. It means there may not be any message sending outside from transaction. So you can't tell which transaction starts first and when starts. Only thing you can tell if transaction succeed and they order you can only determine by its effect. When you consider this your subtle clarification makes no sense. One transaction will read all keys in atomic operation even it is implemented as reading one key by one in transaction implementation and your write operation will be also performed as atomic operation. You can't tell if write to 4th key in second transaction was happen after you read 1st key in first transaction because there it is not observable from outside. Both transaction will be performed in particular order as separate atomic operation. From outside point of view all keys will be read in same point of time and it is work of mnesia to force it. If you send message from inside of transaction you violate mnesia transaction property and you can't be surprised it will behave strange. To be concrete, this message can be send many times.
Edit2:
If you spin-up even a few of these processes you will see that very
quickly a, b and c are in a state where their values do not sum to 10.
I'm curious why you think it would happen or you tested it? Show me your test case and I will show mine:
-module(transactions).
-export([start/2, sum/0, write/0]).
start(W, R) ->
mnesia:start(),
{atomic, ok} = mnesia:create_table(test, [{ram_copies,[node()]}]),
F = fun() ->
ok = mnesia:write({test, a, 10}),
[ ok = mnesia:write({test, X, 0}) || X <-
[b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z]],
ok
end,
{atomic, ok} = mnesia:transaction(F),
F2 = fun() ->
S = self(),
erlang:send_after(1000, S, show),
[ spawn_link(fun() -> writer(S) end) || _ <- lists:seq(1,W) ],
[ spawn_link(fun() -> reader(S) end) || _ <- lists:seq(1,R) ],
collect(0,0)
end,
spawn(F2).
collect(R, W) ->
receive
read -> collect(R+1, W);
write -> collect(R, W+1);
show ->
erlang:send_after(1000, self(), show),
io:format("R: ~p, W: ~p~n", [R,W]),
collect(R, W)
end.
keys() ->
element(random:uniform(6),
{[a,b,c],[a,c,b],[b,a,c],[b,c,a],[c,a,b],[c,b,a]}).
sum() ->
F = fun() ->
lists:sum([X || K<-keys(), {test, _, X} <- mnesia:read(test, K)])
end,
{atomic, S} = mnesia:transaction(F),
S.
write() ->
F = fun() ->
[A, B ] = L = [ random:uniform(10) || _ <- [1,2] ],
[ok = mnesia:write({test, K, V}) || {K, V} <- lists:zip(keys(),
[10-A-B|L])],
ok
end,
{atomic, ok} = mnesia:transaction(F),
ok.
reader(P) ->
case sum() of
10 ->
P ! read,
reader(P);
_ ->
io:format("ERROR!!!~n",[]),
exit(error)
end.
writer(P) ->
ok = write(),
P ! write,
writer(P).
If it would not work it would be really serious problem. There are serious applications including payment systems which rely on it. If you have test case which shows it is broken, please report bug at erlang-bugs#erlang.org
Have you tried mnesia Events ? You can have the reader subscribe to mnesia's Table Events especially write events so as not to interrupt the process doing the writing. In this way, mnesia just keeps sending a copy of what has been written in real-time to the other process which checks what the values are at any one time. take a look at this:
subscriber()->
mnesia:subscribe({table,YOUR_TABLE_NAME,simple}),
%% OR mnesia:subscribe({table,YOUR_TABLE_NAME,detailed}),
wait_events().
wait_events()->
receive
%% For simple events
{mnesia_table_event,{write, NewRecord, ActivityId}} ->
%% Analyse the written record as you wish
wait_events();
%% For detailed events
{mnesia_table_event,{write, YOUR_TABLE, NewRecord, [OldRecords], ActivityId}} ->
%% Analyse the written record as you wish
wait_events();
_Any -> wait_events()
end.
Now you spawn your analyser as a process like this:
spawn(?MODULE,subscriber,[]).
This makes the whole process to run without any process being blocked, mnesia needs not lock any tabel or record because now what you have is a writer process and an analyser process. The whole thing will run in real-time. Remember that there are many other events that you can make use of if you wish by pattern matching them in the subscriber wait_events() receive body.
Its possible to build a heavy duty gen_server or complete application intended for reception and analysis of all your mnesia events. Its usually better to have one capable subscriber than many failing event subscribers. If i have understood you question well, this unblocking solution fits your requirements.
mnesia:read/3 with write locks seems to be suffient.
Mnesia's transaction is implemented by read-write lock and locks are well-formed (holding lock untill the end of transaction). So the isolation level is serializable.
The granularity of locks are per record as long as you access by primary key.

how do we efficiently handle time related constraints on mnesia records?

i am writing records into mnesia which should be kept there
only for an allowed time (24 hours). after 24 hours, before a user modifies part of them,
the system should remove them automatically. forexample, a user is given free airtime (for voice calls)
which they should use in a given time. if they do not use it, after 24 hours, the system should
remove these resource reservation from the users record.
Now, this has brought in timers. an example of a record structure is:
-record(free_airtime,
{
reference_no,
timer_object, %% value returned by timer:apply_after/4
amount
}).
The timer object in the record is important because in case the user
finally puts to use the resources reserved before they are timed out
(or if they time out),the system can call timer:cancel/1 so as to relieve
the timer server from this object.
Now the problem, i have two ways of handling timers on these records:
Option 1: timers handled within the transaction
reserve_resources(Reference_no,Amnt)->
F = fun(Ref_no,Amount) ->
case mnesia:read({free_airtime,Ref_no}) of
[] ->
case mnesia:write(#free_airtime{reference_no = Ref_no,amount = Amount}) == ok of
true ->
case timer:apply_after(timer:hours(24),?MODULE,reference_no_timed_out,[Ref_no]) of
{ok,Timer_obj} ->
[Obj] = mnesia:read({free_airtime,Ref_no}),
mnesia:write(Obj#free_airtime{timer_object = Timer_obj});
_ -> mnesia:abort({error,failed_to_time_object})
end;
false -> mnesia:abort({error,write_failed})
end;
[_] -> mnesia:abort({error,exists,Ref_no})
end
end,
mnesia:activity(transaction,F,[Reference_no,Amnt],mnesia_frag).
About the above option.
Mnesia docs say that transactions maybe repeated by the tm manager (due to some reason)
until they are successful, and so when you put code which is io:format/2 or any other which has nothing to do with
writes or reads, it may get executed several times. This statement made me pause at this point
and think of a way of handling timers out of the transaction it self, so i modified the code as
follows:
Option 2: timers handled outside the transaction
reserve_resources(Reference_no,Amnt)->
F = fun(Ref_no,Amount) ->
case mnesia:read({free_airtime,Ref_no}) of
[] ->
P = #free_airtime{reference_no = Ref_no,amount = Amount},
ok = mnesia:write(P),
P;
[_] -> mnesia:abort({error,exists,Ref_no})
end
end,
Result = try mnesia:activity(transaction,F,[Reference_no,Amnt],mnesia_frag) of
Any -> Any
catch
exit:{aborted,{error,exists,XX}} -> {exists,XX}
E1:E2 -> {error,{E1,E2}}
end,
on_reservation(Result).
on_reservation(#free_airtime{reference_no = Some_Ref})->
case timer:apply_after(timer:hours(24),?MODULE,reference_no_timed_out,[Some_Ref]) of
{ok,Timer_obj} ->
[Obj] = mnesia:activity(transaction,fun(XX) -> mnesia:read({free_airtime,XX}) end,[Some_Ref],mnesia_frag),
ok = mnesia:activity(transaction,fun(XX) -> mnesia:write(XX) end,[Obj#free_airtime{timer_object = Timer_obj}],mnesia_frag);
_ ->
ok = mnesia:activity(transaction,fun(XX) -> mnesia:delete({free_airtime,XX}) end,[Some_Ref],mnesia_frag),
{error,failed_to_time_object}
end;
on_reservation(Any)-> Any.
The code to handle time out of the reservation:
reference_no_timed_out(Ref_no)->
do_somethings_here.....
then later remove this reservation from the database....below..
ok = mnesia:activity(transaction,fun(XX) -> mnesia:delete({free_airtime,XX}) end,[Ref_no],mnesia_frag).
Now i thought that in option 2, i am safer by keeping the timer processing
code out, even when mnesia_tm re-executes the transaction due to its reasons
, this piece of code is not run twice (i avoid having several timer objects
against the same record).
Question 1: Which of these two implementations is right? and/or wrong? Tell me (also)
wether both of them are wrong
Question 2: The module timer, is it well suited for handling large numbers of timer
jobs in production?
Question 3: As compared to Sean Hinde's timer_mn-1.1,
which runs on top of mnesia, is the timer module (possibly running on top of Ets tables) less
capable (for real) in production?
(am asking this because using Sean Hinde's timer_mn on a system which itself is using mnesia appears
to be a problem in terms schema changes, node problems e.t.c)
If any one has another way of handling timer related problems with mnesia, update me
thanx guys...
Question 1:
Handle the timer outside the transaction. When transactions collide in Mnesia, they are simply repeated. That would give you more than one timer reference and two triggers of the timer. It is not a problem per se, but if you wait until the success of the transaction before installing the timer, you can avoid the problem.
The second solution is what I would do. If the TX is okay, you can install a timer on it. If the timer triggers and there is no reference to the object, it doesn't matter. You are only to worry about if this situation happens a lot since you would then have a large number of stray timers.
Question 2:
The timer module is neat, but the performance guide recommends you use the erlang:start_timer BIFs instead, see
http://www.erlang.org/doc/efficiency_guide/commoncaveats.html#id58959
I would introduce a separate process as a gen_server which handles the timing stuff. You send it a remove(timer:hours(24), RefNo) message and then it starts up a timer, gets a TRef and installs a mapping {TRef, RefNo, AuxData} in either Mnesia or ETS. When the timer trigger, the process can spawn a helper removing the RefNo entry from the main table.
At this point, you must wonder about crashes. The removal gen_server may crash. Also, the whole node may crash. How you want to reinstall timers in the case this happens is up to you, but you ought to ponder on it happening so you can solve it. Suppose we come up again and the timer information is loaded in from disk. How do you plan on reinstalling the timers?
One way is to have AuxData contain information about the timeout point. Every hour or 15 minutes, you scan all of the table, removing guys that shouldn't be there. In fact, you could opt for this being the main way to remove timer structures. Yes, you will give people 15 minutes of extra time in the worst case, but it may be easier to handle code-wise. At least it better handles the case where the node (and thus the timers) die.
Another option again is to cheat and only store timings rougly in a data structure which makes it very cheap to find all expired RefNo's in the last 5 minutes and then run that every 5 minutes. Doing stuff in bulk is probably going to be more effective. This kind of bulk-handling is used a lot by operating system kernels for instance.
Question 3
I know nothing about timer-tm, sorry :)

Resources