Wednesday, 1 April 2009
To be continued
Posted by
mue
at
10:44 PM
0
Comments
Link
Thursday, 19 March 2009
Software Architecture with Erlang/OTP - Part 7 - Supervision Trees
link(Pid) or when spawning it with spawn_link(Fun). Normal terminating processes will cause no special reaction, but abnormal terminating processes lead to the termination of all linked processes. This may already help in many situations, but there are more possibilities. Calling process_flag(trap_exit, true) inside the process that should act as a supervisor leads to transformation of signals into regular messages on the format {'EXIT', Pid, Reason}. Now it's possible to receive these messages and react, e.g. through restarting the terminated process.supervisor doesn't wonder too. *smile* Like the generic server and the generic finite state machine the supervisor needs a callback module. This time it only has to provide one function called init(Args). The result is a restart strategy, values to define the restart frequeny, and a list of child specifications. The list of the supervised childs can also be changed through adding and deleting child specifications at runtime. Each child specification is a tuple containing an identificator, the function to start with module, function name, and arguments, a restart strategy, a shutdown strategy, a flag if the process is a worker or itself a supervisor, and a list of depending modules for the release handler. This list already shows that there is much room to configure a supervisor.
%% @spec (atom(), atom(), pid()) -> {ok, Pid} | {error, Reason}
%% @doc Start of a channel.
start_channel(EasModule, ChannelId) ->
StartFunc = {EasModule, start_supervised, [ChannelId]},
ChildSpec = {{channel, ChannelId}, StartFunc, transient, 10000, worker, [EasModule]},
supervisor:start_child(?MODULE, ChildSpec).
%% @spec (atom()) -> ok | {error, Reason}
%% @doc Stop of a channel.
stop_channel(ChannelId) ->
supervisor:terminate_child(?MODULE, {channel, ChannelId}),
supervisor:delete_child(?MODULE, {channel, ChannelId}).
gen_server provides. But the state gets lost if the server process restarts. So in this case it's a common pattern to use a special second process, an ets table, or a Mnesia ram only table. But this overhead is only needed in case of process states that can't start at zero. If server processes work stateless and e.g. use a database as backend it can be kept simple.
Posted by
mue
at
11:00 PM
0
Comments
Link
Labels: erlang
Tuesday, 17 March 2009
Software Architecture with Erlang/OTP - Part 6 - gen_fsm
gen_server one of the most practical modules for own processes is the finite state machine gen_fsm. It is very similar to gen_server regarding the implementation of the user functionallity inside a callback module and its options for tracing, logging, and statistics, timeouts and hibernation, clean terminations and the handling of code changes. This makes it once again comfortable to concentrate on implementing the business logic without hassling with many runtime aspects.
init({FooModule, BarModule}) ->
{ok, FooPid} = FooModule:start(FooCfg, self()),
{ok, BarPid} = BarModule:start(BarCfg, self()),
{ok, awaiting_connection, {FooModule, false, BarModule, false}}.
awaiting_connection({connected, foo}, {FooModule, false, BarModule, false}) ->
{ok, awaiting_connection, {FooModule, true, BarModule, false}};
awaiting_connection({connected, foo}, {FooModule, false, BarModule, true}) ->
FooModule:poll(),
BarModule:poll(),
{ok, polling, {FooModule, BarModule}};
awaiting_connection({connected, bar}, {FooModule, false, BarModule, false}) ->
{ok, awaiting_connection, {FooModule, false, BarModule, true}};
awaiting_connection({connected, bar}, {FooModule, true, BarModule, false}) ->
FooModule:poll(),
BarModule:poll(),
{ok, polling, {FooModule, BarModule}}.
polling({received, foo, Data}, {FooModule, BarModule}) ->
BarModule:send(foo2bar(Data)),
{ok, polling, {FooModule, BarModule}};
polling({received, bar, Data}, {FooModule, BarModule}) ->
FooModule:send(bar2foo(Data)),
{ok, polling, {FooModule, BarModule}};
polling(stop, {FooModule, BarModule}) ->
FooModule:stop(),
BarModule:stop(),
{stop, normal, {FooModule, BarModule}}.
gen_fsm. It is really simple in Erlang to use concurrent running processes for different tasks, e.g. using the generic server. But if you do only synchronous calls there's no win opposite to sequential programming. When using asynchronous cast messages instead it's difficult to handle the also asynchonously resent results. And here the finite state machine helps a lot. A business process is a kind of state machine too. So it can be implemented using the gen_fsm module. Parallel activities can be started once and the results collected before continuing with the next step.gen_fsm:start_timer(Time, Msg) at any time and tell it to send the defined message inside a timeout event when the time is over. Depending on the current state this event can be handled inside the callback module.gen_server and gen_fsm is like the combination of services and business processes. Both work very good together. How they are supervised at runtime I'll describe in the next entry. So, stay tuned!
Posted by
mue
at
11:15 PM
0
Comments
Link
Labels: erlang
Monday, 16 March 2009
Software Architecture with Erlang/OTP - Part 5 - gen_server
gen_server. It is the base module for server processes with a standard set of functions and functionallity. This allows to concentrate on implementing the business functions while inherit many useful features like tracing, logging, and statistics, synchronous asynchronous communication, management of process states, and handling of code changes.
-module(counter).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
init({InitalCounter, Step}) ->
{ok, {InitialCounter, InitialCounter, Step}}.
handle_call(get, From, {Counter, InitialCounter, Step}) ->
{reply, Counter, {Counter, InitialCounter, Step}}.
handle_cast(count, {Counter, InitialCounter, Step}) ->
{noreply, {Counter + Step, InitialCounter, Step}};
handle_cast(reset, {Counter, InitialCounter, Step}) ->
{noreply, Counter, {InitialCounter, InitialCounter, Step}};
handle_cast(stop, {Counter, InitialCounter, Step}) ->
{stop, normal, {Counter, InitialCounter, Step}).
handle_info(Info, State) ->
{noreply, State}.
terminate(Reason, State) ->
ok.
code_change(OldVsn, State, Extra) ->
{ok, State}.
{ok, Pid} = gen_server:start(counter, {0, 5}, []),
gen_server:cast(Pid, count),
gen_server:cast(Pid, reset),
gen_server:cast(Pid, count),
gen_server:cast(Pid, count),
Result = gen_server:call(Pid, get), % Returns 10.
gen_server:cast(Pid, stop).
gen_server functions directly they are typically wrapped. This hides the mechanism and makes it more user/developer friendly and maintainable. So far, so good, but nothing special. But it gets more interesting with all the additional features of the gen_server. So it is possible to add a local or a global name of the process so that processes can be addressed more easy. All using processes can directly send messages to the registered process. And it's possible to send message to locally registered processes on multiple node parallely. Other start arguments control how long the initialization may last before it is cancelled or if the process shall be observed through the sys module. So events can be logged or trigger user defined higher order functions. Additionally system events can be traced and statistical informations retrieved.gen_server provides to interesting features. So if all messge answers also contain a timeout value it will be taken to send a special message to itself after the according idle time.
handle_call(Msg, From, State) ->
% Also return a timeout value in milliseconds.
{reply, Value, NewState, Timeout}.
handle_info(timeout, State) ->
% React on the timeout, e.g. hibernate the process.
{noreply, NewState, hibernate}.
hibernate as part of a handle_call / handle_cast return.gen_server is. But it also has some drawbacks. When doing synchronous calls there may be a dependency between process like A calls B, B calls C, and C calls A. Then, without a specified timeout, a deadlock happens. With a timeout this can be prevented, but the behaviour is not the wanted one. I'll show later in this series how this situation can be handled. Another problem may be registered processes. Here only one instance exists per node or global. So it may be a bottleneck when heavily used. In this case only a kind of broker should be registered and dispatch the messages to special worker processes. But in this case you've got realize a special way for the process state. And this solution should not be a bottleneck, too.gen_fsm. So, stay tuned!
Posted by
mue
at
10:30 PM
2
Comments
Link
Labels: erlang
Sunday, 15 March 2009
Software Architecture with Erlang/OTP - Part 4 - The Language
my_module:add(1, 2). The nice fact here is that both parts of this call, the module and the function name, can be replaced with variables. This is a very useful feature for the implementation of callback modules. They allow to change the behaviour of functions. For example take the processing of some data where you want to be flexible in the how it is persisted.
persistent_process(Id, PersistencyHandler) ->
OriginalData = PersistencyHandler:load(Id),
ProcessedData = process(OriginalData),
PersistencyHandler:store(Id, ProcessedData).
load/1 and store/2, e.g. one using the dets module, one using the Mnesia database, or any own implementation. They have to be passed in the function call:
persistent_process(4711, dets_persistency).
supervisor and gen_server use this mechanism called behaviour. The finite state machine gen_fsm even manages also the current state inside a variable and calls the according callback function:
dispatch({'$gen_event', Event}, Mod, StateName, StateData) ->
Mod:StateName(Event, StateData);
calc(discount, Amount, AnnualTurnover) when AnnualTurnover > 5000 ->
{ok, Amount * 0.9};
calc(discount, Amount, AnnualTurnover) when AnnualTurnover > 1000 ->
{ok, Amount * 0.95};
calc(discount, Amount, AnnualTurnover) ->
{ok, Amount};
calc(comission, Amount, AnnualTurnover) when Amount > 500 ->
{ok, Amount * 0.05};
calc(comission, Amount, AnnualTurnover) ->
{ok, Amount * 0.02};
calc(Any, _Amount, _AnnualTurnover) ->
{error, {unknown_operation, Any}}.
calc(discount, 100.0, 2750.0) the result is the tuple {ok, 95.0}, and the call of calc(comission, 100.0, 2750.0) leads to {ok, 2.0}. Any other operation than discount or comission leads to an error. While there are many situations where this feature is helpful it especially helps when developing a library with behaviours:
dispatch(Action, From, Handler, State) ->
case Handler:process(Action, State) of
{reply, Data, NewState} ->
From ! {ok, Data},
{ok, NewState};
{ok, NewState} ->
{ok, NewState};
{error, Reason} ->
From ! {error, Reason},
stop
end.
process/2 has to be implemented with different patterns for the action:
process({add, Principal, Description}, State) -> ...;
process({set_credential, Principal, Credential}, State) -> ...;
process({deactivate, Principal}, State) -> ...;
process({activate, Principal}, State) -> ...;
process({authenticate, Principal, Credential}, State) -> ...;
loop(Handler, State, Timeout) ->
receive
{From, Action} ->
case dispatch(Action, From, Handler, State) of
{ok, NewState} -> loop(Handler, NewState, Timeout);
stop -> stop
end;
stop ->
stop
after
Timeout ->
case Handler:timeout(State) of
{ok, NewState} -> loop(Handler, NewState, Timeout);
stop -> stop
end
end.
Pid ! {self(), {add, MyPrincipal, MyDescription}}.
Result = authentication:add(Pid, MyPrincipal, MyDescription).
Posted by
mue
at
7:30 PM
0
Comments
Link
Labels: erlang
Friday, 13 March 2009
Software Architecture with Erlang/OTP - Part 3 - EAS Overview
- The bus is the one face to the user (developer, administrator) of the EAS. There's no real bus, but one process per node managing the supervisor, the registry, and the configuration. Additionally this module provides functions for starting and stopping of bus, channel, and service, for subscribing and unsubscribing, for the start of business processes, and for the raising of events.
- The channel is used by the bus. It just manages its own service subscriptions and passes the received events to those services. There's one instance running per channel. It does no filtering of the content, this has to be done by the services themselves, e.g. using pattern matching and guards.
- The service manages it's callback module and the service status. The latter is set only once during the initialization of the service. This is because the real work is done inside a process ring buffer provided by the Tideland CEL. So multiple requests can be handled concurrently. If real states are needed the service itself has to care, e.g. through the usage of the ets module. There's one instance of the service process running per service.
- And at last there's the business process manager. This one is different from the three modules above because an instance will be created for each new process and it is terminating when the process ends. Internally it is implemented as a state machine and also the callback modules which are implementing the real business process are working as state machines.
Posted by
mue
at
1:57 PM
3
Comments
Link
Labels: erlang
Thursday, 12 March 2009
Software Architecture with Erlang/OTP - Part 2 - Motivation
- Following the event-driven paradigm of raising events in different sources (front ends, business processes, adapter to external systems, and more) and processing them in subscribed services.
- Multiple subscribers should be able to handle the same event.
- Supporting a loose coupling between and a high cohesion services.
- Flexible assignment of callback modules to services and subscription of services to channels through configuration.
- Using typical erlang mechanisms like behaviours for an easy implementation of services and business processes.
- Technical aspects like distribution and pooling are transparent for the developer of services abd business processes.
- Simple API, pure Erlang, and no XML for configuration. *smile*
Posted by
mue
at
12:58 PM
1 Comments
Link
Labels: erlang
Wednesday, 11 March 2009
Software Architecture with Erlang/OTP - Part 1 - Preface
Posted by
mue
at
7:12 PM
1 Comments
Link
Labels: erlang
Saturday, 14 February 2009
2009 whisky tasting season started
Posted by
mue
at
4:30 PM
0
Comments
Link
Labels: whisky
Thursday, 5 February 2009
On OOP, COP and the Tideland EAS
Posted by
mue
at
8:12 PM
10
Comments
Link