0

I am having a hard time wrapping my head around the correct way to make calls against a gen_server instance dynamically created by a supervisor with a simple_one_for_one child strategy. I am attempting to create data access controls as gen_servers. Each entity will have its own supervisor, and that supervisor will create gen_server instances as needed to actually perform CRUD operations on the database. I understand the process for defining the child processes, as well as the process for creating them as needed.

Initially, my plan was to abstract the child creation process into custom functions in the gen_server module that created a child, fired off the requested operation (e.g. find, store, delete) on that child using gen_server:call(), and then returning the operation results back to the calling process. Unless I am mistaken, though, that will block any other processes attempting to use those functions until the call returns. That is definitely not what I have in mind.

I may be stuck in OO mode (my background is Java), but it seems like there should be a clean way of allowing a function in one module to obtain a reference to a child process and then make calls against that process without leaking the internals of that child. In other words, I do not want to have to call the create_child() method on an entity supervisor and then have my application code make gen_server:calls against that child PID (i.e. gen_sever:call(Pid, {find_by_id, Id})). I would instead like to be able to call a function more like Child:find_by_id(Id).

2
  • It is not clear for me why you want to have a dedicated intermediate process to access the database. What is wrong if you simply executes the interface function (eg Child:find_by_id(Id)) in the client process? of course in this case Child is not the appropriate module name... Commented Jun 4, 2014 at 21:08
  • The basic idea is to spin off a process for any operation that might take some time (such as a database call). AFAIK, having client code call user_info:find_by_id(User) synchronously, where user_info is a named module, would create a bottleneck for the app, particularly if the database call blocked for any period of time. Encapsulating database access operations in a gen_server process seems like a reasonable approach to managing concurrent access. That being said, I am new to Erlang, so I might be completely off base. Commented Jun 4, 2014 at 22:44

3 Answers 3

1

A full answer is highly dependent on your application — for example, one gen_server might suffice, or you might really need a pool of database connections instead. But one thing you should be aware of is that a gen_server can return from a handle_call callback before it actually has a reply ready for the client by returning {noreply, NewState} and then later, once it has a client reply ready, calling gen_server:reply/2 to send it back to the client. This allows the gen_server to service calls from other clients without blocking on the first call. Note though that this requires that the gen_server has a way of sending a request into the database without having to block waiting for a reply; this is often achieved by having the database send a reply that arrives in the gen_server:handle_info/2 callback, passing enough info back that the gen_server can associate the database reply with the correct client request. Note also that gen_server:call/2,3 has a default timeout of 5 seconds, so you'll need to deal with that if you expect the duration of database calls to exceed the default.

Sign up to request clarification or add additional context in comments.

Comments

0

when you create, modify or delete a record, you don't need to wait for an answer. You can use a gen_server:cast for this, but you don't need a gen_server for this, as I said in my first comment, a simple call to an interface function executed in the client process will save time.

If you want to read, 2 cases:

  • you can do something else while waiting the answer, then a gen_server call is ok, but a simple spawned process waiting for the answer and sending it back to the client will provide the same service.

  • you cannot do anything before getting the answer, then there is no blocking issue, and I think that it is really preferable to use as less code as possible so again a simple function call will be enough.

gen_server is meant to be persistent and react to messages. I don't see in your example the need to be persistent.

-module(access). -export([add/2,get/1]). -record(foo, {bar, baz}). add(A,B) -> F = fun() -> mnesia:write(#foo{bar=A,baz=B}) end, spawn(mnesia,activity,[transaction, F]). %% the function return immediately, %% but you will not know if the transaction failed get(Bar) -> F = fun() -> case mnesia:read({foo, Bar}) of [#foo{baz=Baz}] -> Baz; [] -> undefined end end, Pid = self(), Ref = make_ref(), Get = fun() -> R = mnesia:activity(transaction, F), Pid ! {Ref,baz,R} end, spawn(Get), Ref. %% the function return immediately a ref, and will send later the message {Ref,baz,Baz}. 

1 Comment

That makes perfect sense. OTP myopia, I guess. Thank you very much!
0

If the problem you see is that you are leaking that the internal implementation of your db-process is a gen_server, you could implement the api such that it takes the pid as argument as well.

-module(user). -behaviour(gen_server). -export([find_by_id/2]). find_by_id(Pid, Id) -> gen_server:call(Pid, {find_by_id, Id}). %% Lots of code omitted handle_call({find_by_id, Id}, From, State) -> ok. %% Lots more code omitted. 

This way you don't tell clients that the implementation is in fact a gen_server (although someone could use gen_server:call as well).

2 Comments

But won't that still block the user module until the call to find_by_id returns? I actually implemented this pattern exactly until I started thinking about how it would react under load. I am not yet to the point of doing volume testing, and really don't want to implement dozens of entities incorrectly. :-(
cannot enter my comments in this format, see my answer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.