I have a worker that is initialised as follows:
defmodule GenServerDB.Worker do
use GenServer
def start_link(name) do
GenServer.start_link(__MODULE__, :ok, [name: {:global, {:name, name}}])
end
def init(:ok) do
{:ok, %{}}
end
end
I can then create workers that I expect to be named using the :global module:
iex(3)> {:ok, pid} = Supervisor.start_link([Supervisor.Spec.worker(GenServerDB.Worker, [], [])], strategy: :simple_one_for_one)
{:ok, #PID<0.126.0>}
iex(4)> Supervisor.start_child(pid, [[1]])
Supervisor.start_child(pid, [[1]])
{:ok, #PID<0.128.0>}
iex(6)> Supervisor.start_child(pid, [[2]])
Supervisor.start_child(pid, [[2]])
{:ok, #PID<0.131.0>}
iex(7)> Supervisor.start_child(pid, [[3]])
Supervisor.start_child(pid, [[3]])
{:ok, #PID<0.133.0>}
iex(8)> Supervisor.which_children(pid)
Supervisor.which_children(pid)
[{:undefined, #PID<0.128.0>, :worker, [GenServerDB.Worker]},
{:undefined, #PID<0.131.0>, :worker, [GenServerDB.Worker]},
{:undefined, #PID<0.133.0>, :worker, [GenServerDB.Worker]}]
However when I try and get the pid for a given name, I get the following:
iex(9)> :global.whereis_name({:global, {:name, 1}})
:global.whereis_name({:global, {:name, 1}})
:undefined
Am I missing something here? It looks like I haven't named the process properly.
You're using the argument in the :global.whereis_name/1 call. You don't need the {:global here and the name of the process is actually {:name, [1]}, so you need to call :global.whereis_name({:name, [1]}).
defmodule GenServerDB.Worker do
use GenServer
def start_link(name) do
GenServer.start_link(__MODULE__, :ok, [name: {:global, {:name, name}}])
end
def init(:ok) do
{:ok, %{}}
end
end
{:ok, pid} = Supervisor.start_link([Supervisor.Spec.worker(GenServerDB.Worker, [], [])], strategy: :simple_one_for_one)
Supervisor.start_child(pid, [[1]])
Supervisor.start_child(pid, [[2]])
Supervisor.start_child(pid, [[3]])
IO.inspect Supervisor.which_children(pid)
IO.inspect :global.whereis_name({:name, [1]})
Output:
[{:undefined, #PID<0.77.0>, :worker, [GenServerDB.Worker]},
{:undefined, #PID<0.78.0>, :worker, [GenServerDB.Worker]},
{:undefined, #PID<0.79.0>, :worker, [GenServerDB.Worker]}]
#PID<0.77.0>
Related
I'm trying to connect to a RabbitMQ instance using the ampq package on Elixir, but at times the RabbitMQ instance won't be available at the time that the Elixir server is running. I was wondering how I might be able to implement a simple retry mechanism. There's one strategy here but that seems more involved than I feel necessary especially since there's a mention of it on the README about more information being found on the official docs. I unfortunately couldn't find anything.
Edit: This will crash the application on start and exit.
My code for the module is as follows:
Server.Gen.Rabbit (child)
defmodule Server.Gen.Rabbit do
use GenServer
use AMQP
defmodule State do
#type t :: %{
id: String.t(),
chan: map()
}
defstruct id: "", chan: nil
end
def start_link(%{id: id}) do
GenServer.start_link(
__MODULE__,
%State{
id: id
},
name: :"#{id}:rabbit"
)
end
def init(opts) do
host = "amqp://guest:guest#localhost"
case Connection.open(host) do
{:ok, conn} ->
{:ok, chan} = Channel.open(conn)
setup_queue(opts.id, chan)
:ok = Basic.qos(chan, prefetch_count: 1)
queue_to_consume = #online_receive_queue <> opts.id
IO.puts("queue_to_consume_online: " <> queue_to_consume)
{:ok, _consumer_tag} = Basic.consume(chan, queue_to_consume, nil, no_ack: true)
{:ok, %State{chan: chan, id: opts.id}}
{:error, _} ->
IO.puts("[Rabbit] error on connecting to server: #{host}")
{:backoff, 5_000}
end
end
Server (parent)
defmodule Server do
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
{
GenRegistry,
worker_module: Server.Gen.Rabbit
},
Plug.Cowboy.child_spec(
scheme: :http,
plug: Server.Router,
options: [
port: String.to_integer(System.get_env("PORT") || "3000"),
dispatch: dispatch(),
protocol_options: [idle_timeout: :infinity]
]
)
]
opts = [strategy: :one_for_one, name: Server.Supervisor]
Supervisor.start_link(children, opts)
end
end
I am having an issue with Process.monitor/1. My initial use case was to monitor Phoenix Channel and do some cleanup after it dies. However, I didn't manage to set it up in Phoenix and decided to test it out with pure GenServers.
So, I have a simple GenServer and I want to track when it dies:
defmodule Temp.Server do
use GenServer
def start_link(_), do: GenServer.start_link(__MODULE__, %{})
def init(args) do
Temp.Monitor.monitor(self())
{:ok, args}
end
end
And another GenServer that monitors:
defmodule Temp.Monitor do
use GenServer
require Logger
def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def monitor(pid) do
Process.monitor(pid)
end
def handle_info({:DOWN, ref, :process, _, _}, state) do
Logger.info("DOWN")
{:noreply, state}
end
end
So, if I understand correctly, Process.monitor will start monitoring the Temp.Server process, and should call the handle_info matching :DOWN when Server process dies. If I try it in iex:
iex> {_, pid} = Temp.Server.start_link([])
{:ok, #PID<0.23068.3>}
iex> Process.exit(pid, :kill)
true
I expect handle_info being called from Monitor module and logging "DOWN", however that doesn't happen. What am I doing wrong? I assume it doesn't work because I call to monitor from Server process Temp.Monitor.monitor(self()), but I just can't figure out how else should I do it.
When you call the Temp.Monitor.monitor/1 method, it's still running in Temp.Server's own process, not Temp.Monitor's. That means the :DOWN message is sent to Temp.Server when Temp.Server dies, which is redundant.
What you want to do is, pass the pid of your server process to Temp.Monitor and have it call the Process.Monitor method from it's own process so it can monitor it. That can only happen from one of the GenServer callbacks.
You can do that by moving your implementation in to handle_call/3 or handle_cast/3:
defmodule Temp.Monitor do
use GenServer
require Logger
def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def monitor(pid) do
GenServer.cast(__MODULE__, {:monitor, pid})
end
def handle_cast({:monitor, pid}, state) do
Process.monitor(pid)
{:noreply, state}
end
def handle_info({:DOWN, ref, :process, _, _}, state) do
Logger.info("DOWN")
{:noreply, state}
end
end
I’m trying to use Openmaize for user authentication, and having trouble getting phoenix pass a token when a user logs in. It appears that no token is assigned and passed to the client, and therefore Phoenix.Token.verify fails.
IO.inspect(socket) in UserSocket.connect returns this.
Phoenix.Socket{assigns: %{}, channel: nil, channel_pid: nil,
endpoint: SeatSaver.Endpoint, handler: SeatSaver.UserSocket, id: nil,
joined: false, pubsub_server: SeatSaver.PubSub, ref: nil,
serializer: Phoenix.Transports.WebSocketSerializer, topic: nil,
transport: Phoenix.Transports.WebSocket, transport_name: :websocket,
transport_pid: #PID<0.2098.0>}
I defined set_current_user(user, conn) function in authenticate.ex controller that looks like
defp set_current_user(user, conn) do
token = Phoenix.Token.sign(conn, "user socket", user.id)
conn
|> assign(:current_user, user)
|> assign(:user_token, token)
In the app.html.eex, the following has been added.
<script> window.userToken = “<%= assigns[:user_token] %>” </script>
<script src = “<%= static_path(#conn, “/js/app.js”) %>”></script>
in the app.js,
let socket = new Socket(”/socket”, {
params: {token: window.userToken},
…
})
and finally, user_socket.ex has
def connect(%{"token" => token}, socket) do
case Phoenix.Token.verify(socket, "user socket",
token, max_age: #max_age) do
{:ok, user_id} ->
IO.inspect(user_id)
{:ok, assign(socket, :user_id, user_id)}
{:error, _reason} ->
:error # this errors out because token is nil
end
end
First you need to add secret_key_base in config/config.exs.
secret_key_base: xxxxx
I'd like to find out the current user from the server side. The user_controller module shows how current_user can be returned in response to the client request. But I'm having trouble finding out how to do it from another module on the server.
Below is what I tried but failed with the following error. Basically I copied over the Guardian.Plug code block from the user_controller.ex.
What's the correct way to do it?
(CompileError) web/GraphQLSession.ex:9: undefined function put_status/2
(stdlib) lists.erl:1337: :lists.foreach/2
(stdlib) erl_eval.erl:669: :erl_eval.do_apply/6
.
defmodule App.GraphQLSession do
use Guardian.Hooks
def root_eval(_conn) do
current_user =
case Guardian.Plug.current_resource(_conn) do
nil ->
_conn
|> put_status(:not_found)
|> render(App.V1.SessionView, "error.json", error: "User not found")
user ->
_conn
|> put_status(:ok)
|> render("show.json", user: user)
end
%{author: current_user}
end
end
<<<<< router.ex >>>>
defmodule App.Router do
use App.Web, :router
...
pipeline :api do
plug :accepts, ["json"]
plug Guardian.Plug.VerifyHeader
plug Guardian.Plug.LoadResource
end
scope "/graphql" do
pipe_through :api
forward "/", GraphQL.Plug, [schema: {App.PublicSchema, :schema}, root_value: {App.GraphQLSession, :root_eval} ]
end
<<<< user_controller.ex >> .... This shows how client can retrieve current_user via controller.
defmodule App.V1.UserController do
use App.Web, :controller
alias App.User
plug Guardian.Plug.EnsureAuthenticated, on_failure: { App.V1.SessionController, :unauthenticated_api }
plug :scrub_params, "user" when action in [:create, :update]
def current_user(conn, %{"jwt" => jwt}) do
case Guardian.Plug.current_resource(conn) do
nil ->
conn
|> put_status(:not_found)
|> render(App.V1.SessionView, "error.json", error: "User not found")
user ->
conn
|> put_status(:ok)
|> render("show.json", user: user)
end
UPDATE: After importing Plug.Conn and changing _conn to conn, per David Sulc's advice, the next error I get is as follows:
[error] #PID<0.1026.0> running App.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /graphql
** (exit) an exception was raised:
** (Protocol.UndefinedError) protocol Enumerable not implemented for %Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{error: "User not found", guardian_default_resource: nil, layout:
false}, before_send: [#Function<1.34093945/1 in Plug.Logger.call/2>, #Function<0.30221299/1 in Phoenix.LiveReloader.before_send_inject_reloader/1>], body_params: %{"query" => "mutation CreateMutation(
$input_0:CreateInput!){createQ(input:$input_0){clientMutationId,...F3}} fragment F0 on ...}
}}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "localhost", method: "POST", owner: #PID<0.1026.0>, params: %{"query" => "mutation ....}}}, path_info: [], peer: {{1
27, 0, 0, 1}, 50944}, port: 4000, private: %{App.Router => {[], %{GraphQL.Plug => []}}, :phoenix_endpoint => App.Endpoint, :phoenix_format => "json", :phoenix_pipelines => [:api], :phoenix_route => #Funct
ion<0.58758354/1 in App.Router.match_route/4>, :phoenix_router => App.Router, :phoenix_template => "error.json", :phoenix_view => App.V1.SessionView, :plug_session_fetch => #Function<0.29336444/1 in Plug.
Session.fetch_session/1>}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [{"host", "localhost:4000"}, {"connection", "ke
ep-alive"}, {"content-length", "632"}, {"accept", "*/*"}, {"origin", "http://localhost:4000"}, {"user-agent", "}, {"content-type", "application/json"}, {"referer", "http://localhost:4000/"}, {"accept-encoding", "gzip, deflate"}, {"accept-language", "en-US,en;q=0.8,ko;q=0.6,zh-CN;q=0.4"
}, {"alexatoolbar-alx_ns_ph", "AlexaToolbar/alx-4.0"}], request_path: "/graphql", resp_body: nil, resp_cookies: %{}, resp_headers: [{"content-type", "application/json; charset=utf-8"}, {"cache-control", "
max-age=0, private, must-revalidate"}, {"x-request-id", "ikh03v5kqightov3npgl8bv0do5rv77d"}, {"access-control-allow-origin", "*"}, {"access-control-expose-headers", ""}, {"access-control-allow-credentials
", "true"}], scheme: :http, script_name: ["graphql"], secret_key_base: "x4K=====-00-----lksMUX", state: :sent, status: 404}
(elixir) lib/enum.ex:1: Enumerable.impl_for!/1
(elixir) lib/enum.ex:116: Enumerable.reduce/3
(elixir) lib/enum.ex:1477: Enum.reduce/3
(elixir) lib/enum.ex:1092: Enum.map/2
(rethinkdb) lib/rethinkdb/query/macros.ex:93: RethinkDB.Query.Macros.wrap/1
(rethinkdb) lib/rethinkdb/query/macros.ex:94: anonymous fn/1 in RethinkDB.Query.Macros.wrap/1
(elixir) lib/enum.ex:1092: anonymous fn/3 in Enum.map/2
(stdlib) lists.erl:1262: :lists.foldl/3
I'm learning Elixir myself, but hopefully this will help you along...
Elixir is complaining because the controller imports Plug modules for you (via use App.Web, :controller). This isn't your case in the module you defined, so you need to import it yourself (see line 2).
Also, note that the naming convention of preceding a variable with an underscore if for variables that are to be ignored (and therefore not used). Since your code uses the conn param, it shouldn't be matched as _conn.
defmodule App.GraphQLSession do
import Plug.Conn, only: [put_status: 2]
def root_eval(conn) do
current_user =
case Guardian.Plug.current_resource(conn) do
nil ->
conn
|> put_status(:not_found)
|> render(App.V1.SessionView, "error.json", error: "User not found")
user ->
conn
|> put_status(:ok)
|> render("show.json", user: user)
end
%{author: current_user}
end
end
To answer your updated question there are a few things that you should notice:
Your main error is that you're trying to implement the Enumerable protocol for something that doesn't support it. See the line: ** (Protocol.UndefinedError) protocol Enumerable not implemented. Check any code that is using the Enum.* module. Make sure anything you're passing into the function is enumerable.
Also, Guardian can't find the user you want to retrieve. In the stack trace you can see this: assigns: %{error: "User not found", guardian_default_resource: nil. Try using the api_sign_in function provided by Guardian. You can find more information here. Then you should be able to use Guardian.Plug.current_resource(conn)
I'm brand spanking new to Phoenix and Elixir. So far, it's awesome, but there is a learning curve to be had. Right now, I'm getting this error while following a tut and I can't seem to see what is wrong. (SyntaxError) web/controllers/registration_controller.ex:16: syntax error before: '->'
Registration_controller:
defmodule Restore.RegistrationController do
use Restore.Web, :controller
alias Restore.User
def new(conn, _params) do
changeset = User.changeset(%User{})
render conn, changeset: changeset
end
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
case Restore.Registration.create(changeset, Restore.Repo) do
{:ok, changeset} ->
# sign in the user
{:error, changeset} ->
# show error message
end
end
end
Error:
Compiling 2 files (.ex)
== Compilation error on file web/controllers/registration_controller.ex ==
** (SyntaxError) web/controllers/registration_controller.ex:16: syntax error before: '->'
(elixir) lib/kernel/parallel_compiler.ex:116: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1
Sorry if this is a bad question, but for the life of me I can't seem to get it right. Any help would be great thank you!
You need to complete your registration process (add code instead of the comment), there's nothing you're doing with each case, this is an example from an app that uses JWT authentication:
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
case Repo.insert(changeset) do
{:ok, user} ->
{:ok, jwt, _full_claims} = Guardian.encode_and_sign(user, :token)
conn
|> put_status(:created)
|> render(Restore.SessionView, "show.json", jwt: jwt, user: user)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(Restore.RegistrationView, "error.json", changeset: changeset)
end