|
%% Based on code from |
|
%% Erlang Programming |
|
%% Francecso Cesarini and Simon Thompson |
|
%% O'Reilly, 2008 |
|
%% http://oreilly.com/catalog/9780596518189/ |
|
%% http://www.erlangprogramming.org/ |
|
%% (c) Francesco Cesarini and Simon Thompson |
|
|
|
-module(frequency). |
|
-export([start/0, allocate/0, deallocate/1, stop/0, sup_start/0, sim_clients/1]). |
|
-export([init/0, sup_init/0, sim_client_init/0]). |
|
|
|
%% These are the start functions used to create and |
|
%% initialize the server. |
|
|
|
start() -> |
|
% Registering io for sending messages back to the shell. |
|
register(io, self()), |
|
register(frequency, |
|
spawn(frequency, init, [])). |
|
|
|
init() -> |
|
process_flag(trap_exit, true), |
|
Frequencies = {get_frequencies(), []}, |
|
loop(Frequencies). |
|
|
|
% Hard Coded |
|
get_frequencies() -> [10,11,12,13,14,15]. |
|
|
|
%% The Main Loop |
|
|
|
loop(Frequencies) -> |
|
receive |
|
{request, Pid, allocate} -> |
|
%% Sleep timer for testing the clear/0 call. |
|
%timer:sleep(10000), |
|
{NewFrequencies, Reply} = allocate(Frequencies, Pid), |
|
Pid ! {reply, Reply}, |
|
loop(NewFrequencies); |
|
{request, Pid, {deallocate, Freq}} -> |
|
NewFrequencies = deallocate(Frequencies, Freq), |
|
Pid ! {reply, ok}, |
|
loop(NewFrequencies); |
|
{'EXIT', Pid, _Reason} -> |
|
io:format("Received exit for Pid ~w", [Pid]), |
|
NewFrequencies = exited(Frequencies, Pid), |
|
loop(NewFrequencies); |
|
{request, Pid, stop} -> |
|
Pid ! {reply, stop} |
|
end. |
|
|
|
%% Functional interface |
|
|
|
allocate() -> |
|
server_request(allocate). |
|
|
|
deallocate(Freq) -> |
|
server_request({deallocate, Freq}). |
|
|
|
stop() -> |
|
server_request(stop). |
|
|
|
server_request(Msg) -> |
|
Server = whereis(frequency), |
|
case Server of |
|
undefined -> |
|
{error, no_server}; |
|
_ -> |
|
frequency ! {request, self(), Msg}, |
|
receive |
|
{reply, Reply} -> Reply |
|
end |
|
end. |
|
|
|
%% Called when a process dies. The server finds the associated frequency |
|
%% that was allocated for it, and removes it from the allocated list. |
|
|
|
exited({Free, Allocated}, Pid) -> |
|
case lists:keysearch(Pid, 2, Allocated) of |
|
{value, {Freq, Pid}} -> |
|
NewAllocated = lists:keydelete(Freq, 1, Allocated), |
|
{[Freq|Free], NewAllocated}; |
|
false -> |
|
{Free, Allocated} |
|
end. |
|
|
|
%% The Internal Help Functions used to allocate and |
|
%% deallocate frequencies. |
|
|
|
%% The main allocate function that checks if a process already has a |
|
%% frequency allocated for it. If it does, we do not do anything else, |
|
%% otherwise we make a new allocation, and link it with the server process |
|
%% so we can monitor when the process dies. |
|
allocate({[], Allocated}, _Pid) -> |
|
{{[], Allocated}, {error, no_frequency}}; |
|
allocate({[Freq|Free], Allocated}=Freqs, Pid) -> |
|
IsAllocated = lists:keymember(Pid, 2, Allocated), |
|
case IsAllocated of |
|
true -> |
|
{Freqs, {error, already_allocated}}; |
|
false -> |
|
link(Pid), |
|
{{Free, [{Freq, Pid}|Allocated]}, {ok, Freq}} |
|
end. |
|
|
|
%% Deallocates a frequency for a given process and unlinks it |
|
%% from the server process. |
|
deallocate({Free, Allocated}, Freq) -> |
|
case lists:keysearch(Freq,1,Allocated) of |
|
{value,{Freq,Pid}} -> |
|
unlink(Pid), |
|
NewAllocated=lists:keydelete(Freq, 1, Allocated), |
|
{[Freq|Free], NewAllocated}; |
|
false -> |
|
{Free, Allocated} |
|
end. |
|
|
|
%% This is the supervisor start function, similar to the server start/0. |
|
%% The 'sup' stands for supervisor. |
|
sup_start() -> |
|
% Registering io for sending messages back to the shell. |
|
register(io, self()), |
|
spawn(frequency, sup_init, []). |
|
|
|
sup_init() -> |
|
process_flag(trap_exit, true), |
|
register(frequency, |
|
spawn_link(frequency, init, [])), |
|
supervisor_loop(). |
|
|
|
%% This is the supervisor loop function. It waits for an exit message, |
|
%% which can only come from a server we spawned.in sup_init/0, and then |
|
%% restarts the process along with registering the 'frequency' atom. |
|
supervisor_loop() -> |
|
receive |
|
{'EXIT', _Pid, _Reason} -> |
|
io ! io:format("Server shutdown. Attempting restart..~n"), |
|
register(frequency, spawn_link(frequency, init, [])), |
|
supervisor_loop(); |
|
stop -> |
|
ok |
|
end. |
|
|
|
%% Simulation functions for acting like a client. |
|
|
|
%% Like the supervisor and server, we have an init function to catch exits, |
|
%% and a client loop that listens for an exit signal from a server. |
|
%% If there is no pending message, we continue randomizing activity |
|
%% and (ah ah ah) staying alive until the server is back up. |
|
sim_client_init() -> |
|
process_flag(trap_exit, true), |
|
sim_client_loop(no_freq_set). % We start without a frequency |
|
|
|
%% The `Freq` is the one we allocate which we hold on to for deallocation. |
|
sim_client_loop(Freq) -> |
|
receive |
|
{'EXIT', Pid, _Reason} -> |
|
io:format("Server ~w has shutdown~n", [Pid]), |
|
% I'm not sure if this eplicity call to unlink is required, but |
|
% it doesn't seem to cause issues otherwise. |
|
unlink(Pid), |
|
simulate_client(no_freq_set) |
|
after 0 -> |
|
simulate_client(Freq) |
|
end. |
|
|
|
%% Randomly decides to allocate, deallocate, and timeout along the way. |
|
%% Continues to do this forever until the process is killed. |
|
%% If allocating, we check if already allocated first. |
|
simulate_client(Freq) -> |
|
%% Using random to get either 1 to 3. |
|
case rand:uniform(3) of |
|
1 -> % We simulate allocation |
|
sim_client_allocate(Freq), |
|
timer:sleep(rand:uniform(5000)); |
|
2 -> % We sleep, simulating activity/randomness |
|
io ! io:format("~w zzz..~n", [self()]), |
|
timer:sleep(rand:uniform(5000)); |
|
3 -> % We simulate deallocation |
|
io ! io:format("~w attempt dealloc~n", [self()]), |
|
sim_client_deallocate(Freq), |
|
timer:sleep(rand:uniform(5000)) |
|
end, |
|
sim_client_loop(Freq). |
|
|
|
%% Simulates allocation requests and handles responses accordingly. |
|
sim_client_allocate(Freq) -> |
|
Reply = allocate(), |
|
case Reply of |
|
{ok, New} -> |
|
io ! io:format("Allocating ~w with freq ~w~n", [self(), New]), |
|
simulate_client(New); |
|
{error, Reason} -> |
|
io ! io:format("Errored: ~w~n", [Reason]), |
|
simulate_client(Freq) |
|
end. |
|
|
|
%% Simulates deallocation requests and handles responses accordingly. |
|
sim_client_deallocate(Freq) -> |
|
case Freq of |
|
no_freq_set -> % This means we haven't allocated a frequency yet. |
|
io ! io:format("no_freq_set ~w~n", [self()]), |
|
no_freq_set; |
|
_ -> |
|
io ! io:format("calling dealloc now ~w~n", [self()]), |
|
io ! io:format("dealloc resp: ~w~n", [deallocate(Freq)]) |
|
end. |
|
|
|
|
|
%% Helper function to let you spawn `N` number of clients at once. |
|
sim_clients(N) -> |
|
sim_clients(N, []). |
|
|
|
sim_clients(0, List) -> |
|
List; |
|
sim_clients(N, List) -> |
|
Pid = spawn(frequency, sim_client_init, []), |
|
sim_clients(N - 1, [Pid|List]). |