cjbottaro/faktory_worker_ex

Phoenix/Docker setup questions

pendletons opened this issue · 7 comments

I'm trying to get Faktory working within a Phoenix umbrella app that also uses Docker.

I've setup docker-compose.yml as follows:

app:
  links:
    - faktory

faktory:
  image: contribsys/faktory:latest
  ports:
    - "7419:7419"
    - "7420:7420"

Docker starts up, but I'm struggling with getting everything else to recognise the faktory service as running. If I don't add any configuration for Faktory in config.exs then it tries to talk to localhost:7419 when it should talk to faktory:7419 (as it's a different Docker container).

If I add this to config.exs:

config :faktory_worker_ex, MyApp.FaktoryClient,
  adapter: Faktory.Configuration.Client,
  host: System.get_env("FAKTORY_HOST") || "faktory",
  port: System.get_env("FAKTORY_PORT") || "7419",
  pool: System.get_env("FAKTORY_POOL") || 10

and this to lib/my_app/faktory_client.ex:

defmodule MyApp.FaktoryClient do
   use Faktory.Configuration.Client
end

and this to lib/my_app/application.ex:

def start(_type, _args) do
  # Define workers and child supervisors to be supervised
  children = [
    # Start the endpoint when the application starts
    supervisor(MyApp.Endpoint, []),
    supervisor(MyApp.FaktoryClient, [])
  ]
end

then it complains that it cannot start MyApp.FaktoryClient

app_1       | ** (Mix) Could not start application faktory_worker_ex: Faktory.Application.start(:normal, []) returned an error: shutdown: failed to start child: Faktory.Supervi
sor.Clients
app_1       |     ** (EXIT) shutdown: failed to start child: MyApp.FaktoryClient
app_1       |         ** (EXIT) an exception was raised:
app_1       |             ** (MatchError) no match of right hand side value: {:error, {:function_clause, [{:inet_tcp, :getserv, ["tcp://172.17.0.2:7419"], [file: 'inet_tcp.erl'
, line: 55]}, {:gen_tcp, :connect1, 4, [file: 'gen_tcp.erl', line: 158]}, {:gen_tcp, :connect, 4, [file: 'gen_tcp.erl', line: 145]}, {Faktory.Tcp, :connect, 1, [file: 'lib/fakt
ory/tcp.ex', line: 30]}, {Faktory.Connection, :do_connect, 1, [file: 'lib/faktory/connection.ex', line: 91]}, {Connection, :init_it, 6, [file: 'lib/connection.ex', line: 424]},
 {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 247]}]}}
app_1       |                 (poolboy) /usr/src/app/deps/poolboy/src/poolboy.erl:275: :poolboy.new_worker/1
app_1       |                 (poolboy) /usr/src/app/deps/poolboy/src/poolboy.erl:296: :poolboy.prepopulate/3
app_1       |                 (poolboy) /usr/src/app/deps/poolboy/src/poolboy.erl:145: :poolboy.init/3
app_1       |                 (stdlib) gen_server.erl:365: :gen_server.init_it/2
app_1       |                 (stdlib) gen_server.erl:333: :gen_server.init_it/6
app_1       |                 (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
app_1       | [error] GenServer #PID<0.780.0> terminating

Any thoughts? I'm sure it's something super basic but I've read the hexdocs a few times and don't see anything else to try.

Thanks in advance. :)

acj commented

Hi @pendletons. Thanks for the clear and specific bug report. Please try using an integer for your FAKTORY_PORT fallback value instead of a string.

We should do basic validation of those config variables and show a more helpful error in such cases, especially since they may be gathered from environment variables.

Thanks for the tip, that's definitely moved things along.

Interestingly, changing this

config :faktory_worker_ex, MyApp.FaktoryClient,
  adapter: Faktory.Configuration.Client,
  host: System.get_env("FAKTORY_HOST") || "faktory",
  port: System.get_env("FAKTORY_PORT") || "7419",
  pool: System.get_env("FAKTORY_POOL") || 10

to this

config :faktory_worker_ex, MyApp.FaktoryClient,
  adapter: Faktory.Configuration.Client,
  host: System.get_env("FAKTORY_HOST") || "faktory",
  port: System.get_env("FAKTORY_PORT") || 7419,
  pool: System.get_env("FAKTORY_POOL") || 10

and removing FAKTORY_PORT env setting from the Docker setup (so that it should just be 7419) produces this within the Docker container:

root@926dd305f823:/usr/src/app# echo $FAKTORY_HOST
faktory
root@926dd305f823:/usr/src/app# echo $FAKTORY_PORT
tcp://172.17.0.3:7419

and the same error as before (except the ["tcp://172.17.0.2:7419"] is now ["tcp://172.17.0.3:7419"])

So I removed the port from the config (though I think I will need it for remote servers):

config :faktory_worker_ex, MyApp.FaktoryClient,
  adapter: Faktory.Configuration.Client,
  host: System.get_env("FAKTORY_HOST") || "faktory",
  pool: System.get_env("FAKTORY_POOL") || 10

which gets me this error:

app_1       | [info] Running MyApp.Endpoint with Cowboy using http://0.0.0.0:4001
app_1       | [info] Application my_app exited: MyApp.Application.start(:normal, []) returned an error: shutdown: failed to start child: MyApp.FaktoryClient
app_1       |     ** (EXIT) an exception was raised:
app_1       |         ** (UndefinedFunctionError) function MyApp.FaktoryClient.start_link/0 is undefined or private
app_1       |             (rating_engine_web) MyApp.FaktoryClient.start_link()
app_1       |             (stdlib) supervisor.erl:365: :supervisor.do_start_child/2
app_1       |             (stdlib) supervisor.erl:348: :supervisor.start_children/3
app_1       |             (stdlib) supervisor.erl:314: :supervisor.init_children/2
app_1       |             (stdlib) gen_server.erl:365: :gen_server.init_it/2
app_1       |             (stdlib) gen_server.erl:333: :gen_server.init_it/6
app_1       |             (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

I'm not sure if any of that is helpful? I can throw together a sample app if that would help.

acj commented

It looks like you're almost there. Make sure that you're starting Faktory.Supervisor in your application's supervision tree and not the the client module. The supervisor should use the correct client module since it's mentioned in your config. (That's another thing that we should clarify in the docs.)

EDIT: On second thought, you shouldn't need to explicitly start the supervisor if it's a dependency of your app. If you do, though, I recommend using our Application module as a rough guide.

The Docker behavior that you're seeing is indeed odd. Is FAKTORY_PORT being injected into your environment from somewhere else? Maybe try building the image with --no-cache?

Hm, no change with any of that.

--no-cache for Docker still produces the FAKTORY_PORT variable

Adding :faktory_worker_ex to the list of additional applications in mix.exs had no effect, so I tried adding

    Faktory.Configuration.init()
    Faktory.Supervisor.start_link()

to the application.ex, but same result as before.

I'll try putting together a sample app tomorrow to see if that provides any more clarity without all the other things in this app. (Which aren't very many, but may as well rule out any obvious culprits.)

@pendletons Hi!

def start(_type, _args) do
  # Define workers and child supervisors to be supervised
  children = [
    # Start the endpoint when the application starts
    supervisor(MyApp.Endpoint, []),
    supervisor(MyApp.FaktoryClient, [])
  ]
end

You don't need to start it. The supervisor and pools and everything are automatically started; all you have to do is define clients and/or workers. Relevant code here if interested:
https://github.com/cjbottaro/faktory_worker_ex/blob/master/lib/faktory/supervisor.ex
(and that's started by the faktory_worker_ex application)

config :faktory_worker_ex, MyApp.FaktoryClient,
  adapter: Faktory.Configuration.Client,
  host: System.get_env("FAKTORY_HOST") || "faktory",
  pool: System.get_env("FAKTORY_POOL") || 10

That's an anti-pattern and probably not what you want. Elixir is a compiled language and those env vars are read at compile time only, not runtime. If you want env vars to be used at runtime, try this syntax:

config :faktory_worker_ex, MyApp.FaktoryClient,
  adapter: Faktory.Configuration.Client,
  host: {:system, "FAKTORY_HOST", "faktory"},
  pool: {:system, "FAKTORY_POOL", 10}

See here for more info on runtime config:
https://hexdocs.pm/faktory_worker_ex/Faktory.Configuration.html#module-runtime-configuration

All that being said, there is a bug when the port is specified from an env var; it needs to be converted to an integer to work with Erlang's socket lib.

I fixed the bug on master, can you update your mix.exs to use faktory_worker_ex from Github and see if it fixes your problem?

And sorry it took so for me to see this; there is still something wrong with my Github notifications (I'm not getting emails when a new issue is opened). If you @ me on comments, I will for sure get an email though.

I may be wrong about env vars and compile time. It may only be an issue when using some kind of release packager (like exrm). If you're running your app via mix, then using System.get_env right in the mix config files might work at runtime.

Still, just to be sure, better to use the tuple syntax. More info here:
http://blog.plataformatec.com.br/2016/05/how-to-config-environment-variables-with-elixir-and-exrm/
(just skip to the heading "ENV vars need to be present during compile time")

Going to close this since it's been a while since the last reply.