Calling functions on a Phoenix worker

All examples of using a gen_server worker process show iex where we pass pid (we so start_link). I have no idea how I can get to my process registered in applicaiton.ex as:

worker(MyApp.Stats, [])

say it has a function “foo” I want to call from a different module. How to do that? Since “API” functions expect pid, how can I know pid then?

1 Like

You can name your GenServer when creating it:

GenServer.start_link(__MODULE__, [init_args], name: __MODULE__)

and then you can use this name instead of pid.

In your example you can have something like that:

defmodule MyApp.Stats do
  use GenServer
  
  def start_link() do
    GenServer.start_link(__MODULE__, [], name: Foo)
  end
end

and then you can call it with Foo.

1 Like

There are various ways to do so. The different ways are introduced here: https://hexdocs.pm/elixir/GenServer.html#module-name-registration

1 Like

I know I can refer to the module by Foo, but “API” functions have pid (so does call/cast). What should I pass as “pid”? If I don’t of course I am getting “GenServer.call/1 is undefined or private.”. That is a Phoenix app, so I don’t call start_link myself.

You can give it Foo or a PID as the first argument:

GenServer.call(Foo, :some_call)

There is no GenServer.call/1, but only GenServer.call/3, you can call it as GenServer.call(Foo, :bar) (The first argument has to be a GenServer.server/0, which is either a pid, a {atom, node}, or a GenServer.name/0, which again is either atom, {:global, term}, or {:via, module, term})

And I do not understand what phoenix has to do with it. Supervisor do usually call init/0, from where you do call GenServer.start_link/3.

If you still struggle, please show some example code (do as a repository if necessary) that shows your problem (and only that)

For named processes you can simply replace the pid of the process with the name you’ve given it. And if the backdraws of named processes are not a problem for your design you can simply delete the pid from your genserver’s public api and hardcode the name within the function body.

While technically that is true, it’s far from obvious for people new to this stuff that the list of children given to the supervisor will result in a call to start_link for those. You don’t explicitly call start_link there.

Ah, wait, missed a bit here. The Supervisor calls according to the childspec… The childspec again will contain an MFA, defaulting to the equivalent of __MODULE__.start_link/1, so in your genserver implementation you can forward to GenServer.start_link/3 as you want.

This is explained in GenServers documentation:

https://hexdocs.pm/elixir/GenServer.html#module-use-genserver-and-callbacks

Thank you all. It works now. The thing was I used GenServer.start_link/2 and not /3 so name was technically the second arg :slight_smile: That silently didn’t work.

In terms of what does it have to do with Phoenix - simple, I don’t call start_link myself and grab the pid, but add a line to an .ex file as “worker” where such workers should be “mounted”. The call and management is done by some other process.

That file is still just a supervisor, which is nothing specific to phoenix.
https://hexdocs.pm/elixir/Supervisor.html
https://hexdocs.pm/elixir/Supervisor.Spec.html

It looks like you guys try hard to look smart. I believe you are. I meant “I’m on Phoenix” where I have to put a line to supervisors in the file vs “I can just do {:ok, pid} = GenServer.start_link(…)”. This DOES NOT mean it is Phoenix’s fault nor does it mean it doesn’t work that way elsewhere. I just meant THAT pattern vs the other. Are we on the same page now ? :slight_smile:

No, we aren’t. Let me ask again for code, OK?

At least the part where you need to add your GenServer and the GenServer itself.

LOL. It works. You helped me. But… since you are insisting, here you are:

file: application.ex

defmodule Foo.Application do
use Application

See https://hexdocs.pm/elixir/Application.html

for more information on OTP Applications

def start(_type, _args) do
import Supervisor.Spec

# Define workers and child supervisors to be supervised
children = [
  # Start the Ecto repository
  supervisor(Foo.Repo, []),
  # Start the endpoint when the application starts
  supervisor(FooWeb.Endpoint, []),
  # Start your own worker by calling: Foo.Worker.start_link(arg1, arg2, arg3)
  # worker(Foo.Worker, [arg1, arg2, arg3]),
   worker(Foo.Stats, []) ## <-- THIS IS THE LINE
]

# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Foo.Supervisor]
Supervisor.start_link(children, opts)

end

Not really. With lot’s of people getting into elixir by using phoenix I just try to make the distinction clear about what’s phoenix and what’s just plain elixir and therefore how things work in other places as well.

I don’t disagree. Probably there are no things used in Phoenix code that don’t exist in Elixir do they ? :wink: In other words one would not be permitted to mention Phoenix as no function/module/pattern is “just Phoenix”. Again, thanks for help guys!

For completeness, you can get the PID of the Foo.Stats worker by calling:

Supervisor.which_children(Profiledb.Supervisor)

and traversing it, searching for Foo.Stats:

def server_pid(module) do
  Supervisor.which_children(Profiledb.Supervisor)
  |> Enum.find_value(&match_module_in_children(&1, module))
end

def match_module_in_children({module, pid, :worker, _modules}, module) do
  pid
end

# def match_module_in_children({module, pid, :supervisor, _modules}, module) do
#   pid
# end

def match_module_in_children({module, pid, _, _modules}, _module) do
  nil
end

This would allow you to do this:
GenServer.call(Profiledb.Supervisor.server_pid(Foo.Stats), :some_call)

This will call Foo.Stats.start_link/1 with an empty list as argument, as we have already pointed out. There is a default implementation made by use GenServer to just forward it into GenServer.start_link/3 without default options. We also pointed out, that you can create your own version of start_link/1 which alters the default options for a GenServer.

All these things are also mentioned and explained with examples in the docs we linked here. Also we didn’t touch Phoenix documentation, everything was plain elixir stdlib.

Nobbz - I got VALUABLE hints from LostKobrakai and ryh, essentially “use the given name in call’s arg#1”. I am not sure what you pointed out, but even if it was in some docs, what does it change? Probably every function is documented somewhere, but we talk on forums as it is more productive. Also it is no suprise start_link is called. Did I said it wasn’t? Nor did I say gen_server is a Phoenix concept. All that feels like I am being lectured :slight_smile:

You insisted on not beeing able to “call start_link” on your own because “phoenix generated code does it somewhere else”. Thats why I told you again, that phoenix has nothing to do with the process in general.

Also, posting some code may help you now to get it fixed in no time, but providing and talking about the documentation might help you even more (at least thats my opinion) because you will be able to use it later on.

If though you had shown that you already read the docs by asking about a certain phrase, or showing that you already tried various things and nothing worked, maybe I just had fixed your failing code with a short explanation.

But for now, I do insist on my opinion that the ability to read the documentation properly and to know the borders of used libraries is much more important than code written by someone else that runs for now until you run into the next problem.

Especially all those OTP-callback-and-supervision-flow need to be understood, not just used