I was thinking of using a list to store elements(files)
How do i change the code below so that it would lock one process(user) from accessing one file(element in list), while allowing another process(user) to access the file.
-module(bank).
-export([account/1, start/0, stop/0, deposit/1, get_bal/0, set_bal/1]).
account(Balance) ->
receive
{set, NewBalance} ->
account(NewBalance);
{get, From} ->
From ! {balance, Balance},
account(Balance);
stop -> ok
end.
start() ->
Account_PID = spawn(bank, account, [0]),
register(account_process, Account_PID).
stop() ->
account_process ! stop,
unregister(account_process).
set_bal(B) ->
account_process ! {set, B}.
get_bal() ->
account_process ! {get, self()},
receive
{balance, B} -> B
end.
deposit(Amount) ->
OldBalance = get_bal(),
NewBalance = OldBalance + Amount,
set_bal(NewBalance).
and
-module(banktest).
-export([start/0, client/1]).
start() ->
bank:start(),
mutex:start(),
register(tester_process, self()),
loop(10, 20, 100),
unregister(tester_process),
mutex:stop(),
bank:stop().
loop(_, _, 0) ->
true;
loop(Amount1, Amount2, N) ->
bank:set_bal(0),
spawn(banktest, client, [Amount1]),
spawn(banktest, client, [Amount2]),
receive
done -> true
end,
receive
done -> true
end,
io:format("Expected balance = ~B, actual balance = ~B~n~n",
[Amount1 + Amount2, bank:get_bal()]),
loop(Amount1, Amount2, N-1).
client(Amount) ->
bank:deposit(Amount),
tester_process ! done.
If you want to serialize write calls but allow read ones, you can change the code as follow:
account(Balance, WriteLocker) ->
receive
free_lock ->
account(Balance, undefined),
{set, NewBalance, Caller} when WriteLocker =:= undefined ->
Caller ! done,
account(NewBalance, Caller),
{get, From} ->
From ! {balance, Balance},
account(Balance, WriteLocker);
stop -> ok
end.
wait() ->
receive
{done, WriteLock} -> WriteLock ! free_lock.
end.
set_bal(B) ->
account_process ! {set, B, self()},
wait().
If you want to lock reader too, add the guard to {get, From} message too.
OK, still not quite understand the question but I will try to clarify and then propose a solution based on that:
You want to manage access to a set of files rather than a bank account
You want one user to have a write access to a file while other uses still have a read access to that file
If that's what you want then you can simply treat each file similarly to a bank account. It means that you should start one process (analogous to the code in the bank module) per file. In other words:
for 1 bank account you need 1 process
for 3 bank accounts you need 3 processes
for 10 files you need 10 processes
Implementation details would depend on how you plan to manage the files on the server, but in the simplistic scenario the process could work analogously to the existing code:
file(FileName) ->
receive
{write, From, Data} ->
ok = file:write_file(FileName, Data),
From ! ok,
file(FileName);
{read, From} ->
{ok, Data} = file:read_file(FileName),
From ! Data,
file(FileName);
stop -> ok
end.
start() ->
File_PID = spawn(file, file, ["somefile.txt"]),
register(file_process, File_PID).
Related
I ran into a strange problem. I'm testing the code found in the docs that creates several processes to accept incoming connections from a listen socket:
-module(servertest).
%% API
-export([start/2, server/1]).
start(Num,LPort) ->
case gen_tcp:listen(LPort,[{active, false},{packet,0}]) of
{ok, ListenSock} ->
start_servers(Num,ListenSock),
{ok, Port} = inet:port(ListenSock),
Port;
{error,Reason} ->
{error,Reason}
end.
start_servers(0,_) ->
ok;
start_servers(Num,LS) ->
spawn(?MODULE,server,[LS]),
start_servers(Num-1,LS).
server(LS) ->
io:format("server started~n", []),
case gen_tcp:accept(LS) of
{ok,S} ->
loop(S),
server(LS);
Other ->
io:format("accept returned ~w - goodbye!~n",[Other]),
ok
end.
loop(S) ->
inet:setopts(S,[{active,once}]),
receive
{tcp,S,Data} ->
Answer = Data, % Not implemented in this example
gen_tcp:send(S,Answer),
loop(S);
{tcp_closed,S} ->
io:format("Socket ~w closed [~w]~n",[S,self()]),
ok
end.
This works perfectly fine:
servertest:start(1, 1241).
server started
1241
The problem starts when I add another layer of spawn and the code that creates the listen socket starts the accept servers has already been spawned:
-module(servertest2).
%% API
-export([start/2, server/1, start_listening/2]).
start(Num,LPort) ->
erlang:spawn_link(?MODULE, start_listening, [Num,LPort]).
start_listening(Num,LPort) ->
case gen_tcp:listen(LPort,[{active, false},{packet,0}]) of
{ok, ListenSock} ->
start_servers(Num,ListenSock),
{ok, Port} = inet:port(ListenSock),
Port;
{error,Reason} ->
{error,Reason}
end.
start_servers(0,_) ->
ok;
start_servers(Num,LS) ->
spawn(?MODULE,server,[LS]),
start_servers(Num-1,LS).
server(LS) ->
io:format("server started~n", []),
case gen_tcp:accept(LS) of
{ok,S} ->
loop(S),
server(LS);
Other ->
io:format("accept returned ~w - goodbye!~n",[Other]),
ok
end.
loop(S) ->
inet:setopts(S,[{active,once}]),
receive
{tcp,S,Data} ->
Answer = Data, % Not implemented in this example
gen_tcp:send(S,Answer),
loop(S);
{tcp_closed,S} ->
io:format("Socket ~w closed [~w]~n",[S,self()]),
ok
end.
When it's started like this, the accept immediately (without any incoming connection) returns {error,closed}:
servertest2:start(1, 1242).
server started
<0.13452.0>
accept returned {error,closed} - goodbye!
For clarify here's a diff between the two versions:
--- servertest.erl 2021-11-25 00:04:32.000000000 +0100
+++ servertest2.erl 2021-11-25 00:04:01.000000000 +0100
## -1,9 +1,12 ##
--module(servertest).
+-module(servertest2).
%% API
--export([start/2, server/1]).
+-export([start/2, server/1, start_listening/2]).
start(Num,LPort) ->
+ erlang:spawn_link(?MODULE, start_listening, [Num,LPort]).
+
+start_listening(Num,LPort) ->
case gen_tcp:listen(LPort,[{active, false},{packet,0}]) of
{ok, ListenSock} ->
start_servers(Num,ListenSock),
Thank you!
Without testing it, I'd say that in the second case the process that's the owner of the listen socket dies naturally after spawning the acceptors, thus closing the listen socket.
Add some timer:sleep or receive after calling start_servers to verify it.
Ive been trying to write a simple concurrency exercise in Erlang that invlolves communicating across different terminals/shells.
However this error appears everytime I run init_chat() and entering my name.
=ERROR REPORT==== 15-Nov-2021::08:13:11.849169 === Error in process <0.91.0> on node kei#osboxes with exit value:
{undef,[{chat,chat_handle,"Jeano",[]}]}
I have no idea what I'm doing wrong.
The whole program:
-module(chat).
-compile(export_all).
init_chat() ->
In = io:get_line("Name please: "),
Name = string:trim(In),
register(chat_handle, spawn(chat, chat_handle, [Name])).
chat_handle(Name) ->
spawn(chat, message_handle, [Name]),
receive
{message, Sender, Message} ->
io:format("~p: ~p", [Sender, Message]),
chat_handle(Name);
{dc, Sender} ->
io:format("~p Has disconnected. ~n", [Sender]),
chat_handle(Name);
quit ->
io:format("Disconnecting... ~n"),
erlang:halt()
end.
message_handle(Name) ->
Message = io:get_line("You: "),
if
Message == "bye/n" ->
disconnect(nodes(), Name);
true ->
send_message(nodes(), Name, Message)
end.
send_message([Head | Tail], Name, Message) ->
{chat_handle, Head} ! {message, Name, Message},
send_message(Tail, Name, Message);
send_message([], Name, Message) ->
message_handle(Name).
disconnect([Head | Tail], Name) ->
{chat_handle, Head} ! {dc, Name},
disconnect(Tail, Name);
disconnect([], Name) ->
{chat_handle, node()} ! quit.
The error message says {chat,chat_handle,"Jeano", ... (which is missing list brackets around the string) but your code actually says spawn(chat, chat_handle, [Name]), which looks correct, so I think you just haven't reloaded the recompiled version of the module.
I am currently trying to get the occupants of a room in a custom Ejabberd module within the muc_filter_message hook, but all attempts to get the room state are timing out.
error:
2016-07-25 10:43:04.802 [error] <0.13909.0>#ejabberd_hooks:run_fold1:368 {timeout,{gen_fsm,sync_send_all_state_event,[<0.13909.0>,get_state]}}
imports + hook function (cut down)
-behaviour(gen_mod).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("mod_muc_room.hrl").
-include("mod_muc.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("ejabberd_commands.hrl").
muc_message_sent(Stanza, MUCState, RoomJID, FromJID, FromNick) ->
{_, RoomName, Host, _, _, _, _} = RoomJID,
OccuList = get_room_occupants(RoomName, Host),
?INFO_MSG("muc_message_sent OccuList ~p~n", [OccuList]),
Stanza.
Code for room occupants retrieval:
get_room_occupants(Room, Host) ->
case get_room_pid(Room, Host) of
room_not_found ->
?INFO_MSG("muc_message_sent get_room_occ ~p~n", [room]);
Pid -> get_room_occupants(Pid)
end.
get_room_occupants(Pid) ->
?INFO_MSG("muc_message_sent get_room_pstate ~p~n", [Pid]),
S = get_room_state(Pid),
?INFO_MSG("muc_message_sent get_room_state S ~p~n", [S]),
lists:map(
fun({_LJID, Info}) ->
{jid:to_string(Info#user.jid),
Info#user.nick,
atom_to_list(Info#user.role)}
end,
dict:to_list(S#state.users)).
%% #doc Get the Pid of an existing MUC room, or 'room_not_found'.
get_room_pid(Name, Service) ->
case mnesia:dirty_read(muc_online_room, {Name, Service}) of
[] ->
?INFO_MSG("muc_message_sent get_room_pid ~p~n", [failed]),
room_not_found;
[Room] ->
?INFO_MSG("muc_message_sent get_room_pid ~p~n", [pid]),
Room#muc_online_room.pid
end.
get_room_state(Room_pid) ->
{ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_state),
R.
The get_room_occupants code is pulled directly from mod_muc_admin where it works fine (but I couldn't find a way to directly use the functions within that module). Been stuck on this for a while now so any ideas appreciated.
I have a header file with some functionality in it.
It's meant to be for expandibility ;)
In this header file I use -spec for my functions.
Because my header file is included in more than one erl file and has some functionality which are not used by all of the erl files, I get warnings of functions not being used (which are being used but in another erl file).
For example:
I get for this example a warning for module a that function headerExecute is not being used and for module c that function headerCount is not being used.
%% Shell %%
BPid = b:create(),
APid = a:create(BPid),
CPid = c:create(BPid).
-module(a).
-export([create/1, count/2, stop/1]).
-export([loop/1]).
-include("header.hrl").
create(Id) ->
spawn(?MODULE, loop, [Id]).
count(Pid, Counter) ->
Pid!{count, Ref = make_ref(), self(), Counter},
receive
{Ref, Result} -> Result
end.
stop(Pid) ->
Pid!{stop}.
loop(Id) ->
receive
{count, Ref, From, Counter}
Result = headerCount(Id, Counter),
From!{Ref, Result},
?MODULE:loop(Id);
{stop} ->
ok;
_ ->
?MODULE:loop(Id)
end.
-define(header, header).
-spec headerCount(pid(), integer()) -> integer().
-spec headerExecute(pid(), atom()) -> no_return().
headerCount(Pid, Counter) ->
Pid!{count, Ref = make_ref(), self(), Counter},
receive
{Ref, Result} -> Result
end.
headerExecute(Pid, Cmd) ->
Pid!{execute, Cmd}.
-module(b).
-export([create/0, stop/1]).
-export([loop/0]).
create() ->
spawn(?MODULE, loop, []).
stop(Pid) ->
Pid!{stop}.
loop() ->
receive
{count, Ref, From, Counter} ->
From!{Ref, Counter + 1},
?MODULE:loop();
{execute, Cmd} ->
%% Execute something with the command Cmd %%
?MODULE:loop();
{stop} ->
ok;
_ ->
?MODULE:loop()
end.
-module(c).
-export([create/1, execution/2, stop/1]).
-export([loop/1]).
-include(header).
create(Id) ->
spawn(?MODULE, loop, [Id]).
execution(Pid, Cmd) ->
Pid!{execution, Cmd}.
stop(Pid) ->
Pid!{stop}.
loop(Id) ->
receive
{execution, Cmd} ->
headerExecute(Id, Cmd),
?MODULE:loop(Id);
{stop} ->
ok;
_ ->
?MODULE:loop(Id)
end.
For scalability it's not an option to export these in the erl files and exporting these functions in a header file result in errors.
How can I discard or get rid off these warnings?
EDIT: I put this functionality in the headers so that new erl modules can easily be added to the code and adjust the existing erl modules without touching the other erl modules.
Thanks in advance.
Use nowarn_unused_function compiler parameter in your header file.
-compile({nowarn_unused_function, [headerCount/2, headerExecute/2]})
But it doesn't have anything with scalability. You are using this word wrong.
So when a user sends a request to register an account, they send their username, password, email, and other info. The registration function must verify all of their data. An example would be:
verify email not in use
verify username not in use
verify username is alphanumeric
verify all fields are above X characters long
verify all fields are less than Y characters long
Now I don't want to have a 5 level deep if or case statement, but what other options do I have? Splitting it into separate functions sounds like a good idea, but then I just have to check the return value of the functions in some sort of conditional and it's back to the original problem.
I could separate them into functions and then call an if statement with all of the conditionals OR'd together, but that wouldn't give me what I want because I need to be able to tell the user the specific error if there was one.
How does one handle this kind of situation in erlang? Is there an equivalent of a return statement, or does it have to be the last executable line in a function to be a return value?
One of Joe Armstrong's suggestion: program success case code separated from error handling. You can make it in this way
create_user(Email, UserName, Password) ->
try
ok = new_email(Email),
ok = valid_user_name(UserName),
ok = new_user(UserName),
ok = strong_password(Password),
...
_create_user(Email, UserName, Password)
catch
error:{badmatch, email_in_use} -> do_something();
error:{badmatch, invalid_user_name} -> do_something();
error:{badmatch, user_exists} -> do_something();
error:{badmatch, weak_password} -> do_something();
...
end.
note that you can do all errors catches out of create_user function which is better.
create_user(Email, UserName, Password) ->
ok = new_email(Email),
ok = valid_user_name(UserName),
ok = new_user(UserName),
ok = strong_password(Password),
...
_create_user(Email, UserName, Password).
main() ->
try
...
some_function_where_create_user_is_called(),
...
catch
...
error:{badmatch, email_in_use} -> do_something();
error:{badmatch, invalid_user_name} -> do_something();
error:{badmatch, user_exists} -> do_something();
error:{badmatch, weak_password} -> do_something();
...
end.
Pattern match is one of coolest things in Erlang. Note that you can involve your tag to badmatch error
{my_tag, ok} = {my_tag, my_call(X)}
and custom data too
{my_tag, ok, X} = {my_tag, my_call(X), X}
If exception is fast enough for you depends of your expectations. Speed on my 2.2GHz Core2 Duo Intel:
about 2 millions exceptions in one second (0.47us) compared to 6 millions success (external) function calls (0.146us) - one can guess that exception handling takes about 0.32us.
In native code it is 6.8 vs 47 millions per second and handling can take about 0.125us. There can be some additional cost for try-catch construct which is about 5-10% to success function call in both native and byte-code.
User = get_user(),
Check_email=fun(User) -> not is_valid_email(User#user.email) end,
Check_username=fun(User) -> is_invalid_username(User#user.name) end,
case lists:any(fun(Checking_function) -> Checking_function(User) end,
[Check_email, Check_username, ... ]) of
true -> % we have problem in some field
do_panic();
false -> % every check was fine
do_action()
end
So it isn't 5 level deep any more. For real program i guess you should use lists:foldl for accumulate error message from every checking function. Because for now it simple says 'all fine' or 'some problem'.
Note that in this way add or remove checking condition isn't a big deal
And for "Is there an equivalent of a return statement..." - look at try catch throw statement, throw acts like return in this case.
Building up on #JLarky's answer, here's something that i came up with. It also draws some inspiration from Haskell's monads.
-record(user,
{name :: binary(),
email :: binary(),
password :: binary()}
).
-type user() :: #user{}.
-type bind_res() :: {ok, term()} | {error, term()} | term().
-type bind_fun() :: fun((term()) -> bind_res()).
-spec validate(term(), [bind_fun()]) -> bind_res().
validate(Init, Functions) ->
lists:foldl(fun '|>'/2, Init, Functions).
-spec '|>'(bind_fun(), bind_res())-> bind_res().
'|>'(F, {ok, Result}) -> F(Result);
'|>'(F, {error, What} = Error) -> Error;
'|>'(F, Result) -> F(Result).
-spec validate_email(user()) -> {ok, user()} | {error, term()}.
validate_email(#user{email = Email}) ->
...
-spec validate_username(user()) -> {ok, user()} | {error, term()}.
validate_username(#user{name = Name}) ->
...
-spec validate_password(user()) -> {ok, user()} | {error, term()}.
validate_password(#user{password = Password}) ->
...
validate(#user{...}, [
fun validate_email/1,
fun validate_username/1,
fun validate_password/1
]).
Maybe you will need using of
receive
message1 -> code1;
message2 -> code2;
...
end.
But, of course, there will be spawn() methods.