4
votes

Occasionally, when I make a typo in my Elixir code and a CompileError happens, Phoenix refuses to live-reload even once I've fixed everything, and keeps printing App.Endpoint.path/1 is undefined (App is my app). Of course, the Endpoint is defined perfectly fine and works again as soon as I restart mix phoenix.server.

As far as I know, I'm on the latest version of everything. Since I can't find anything about this error online, I suspect I've misconfigured something, and I'm grateful for your suggestions on how to trace down the root of this.

[info] Sent 200 in 200ms
iex[debug] Live reload: web/resolver/reviewer.ex
[debug] Live reload: web/resolver/reviewer.ex
Compiling 20 files (.ex)

== Compilation error on file web/resolver/reviewer.ex ==
** (Ecto.Query.CompileError) unbound variable `f` in query
    (ecto) expanding macro: Ecto.Query.preload/3
    (app) web/resolver/reviewer.ex:519: App.Resolver.Reviewer.sc_data/1
    (elixir) expanding macro: Kernel.|>/2
    (app) web/resolver/reviewer.ex:520: App.Resolver.Reviewer.sc_data/1
    (elixir) lib/kernel/parallel_compiler.ex:116: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1

[error] #PID<0.2279.0> running App.Endpoint terminated
Server: localhost:4000 (http)
Request: GET /reviewers/2016/1234
** (exit) an exception was raised:
    ** (UndefinedFunctionError) function App.Endpoint.path/1 is undefined (module App.Endpoint is not available)
        (app) App.Endpoint.path("/phoenix/live_reload/frame")
        (phoenix_live_reload) lib/phoenix_live_reload/live_reloader.ex:115: Phoenix.LiveReloader.reload_assets_tag/1
        (phoenix_live_reload) lib/phoenix_live_reload/live_reloader.ex:97: anonymous fn/1 in Phoenix.LiveReloader.before_send_inject_reloader/1
        (elixir) lib/enum.ex:1623: Enum."-reduce/3-lists^foldl/2-0-"/3
        (plug) lib/plug/conn.ex:909: Plug.Conn.run_before_send/2
        (plug) lib/plug/conn.ex:355: Plug.Conn.send_resp/1
        (phoenix) lib/phoenix/code_reloader.ex:50: Phoenix.CodeReloader.call/2
        (app) lib/app/endpoint.ex:1: App.Endpoint.phoenix_pipeline/1
        (app) lib/plug/debugger.ex:93: App.Endpoint."call (overridable 3)"/2
        (app) lib/app/endpoint.ex:1: App.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
[error] Ranch protocol #PID<0.2281.0> (:cowboy_protocol) of listener App.Endpoint.HTTP terminated
** (exit) killed
Compiling 20 files (.ex)

== Compilation error on file web/resolver/reviewer.ex ==
** (Ecto.Query.CompileError) unbound variable `f` in query
    (ecto) expanding macro: Ecto.Query.preload/3
    (app) web/resolver/reviewer.ex:519: App.Resolver.Reviewer.sc_data/1
    (elixir) expanding macro: Kernel.|>/2
    (app) web/resolver/reviewer.ex:520: App.Resolver.Reviewer.sc_data/1
    (elixir) lib/kernel/parallel_compiler.ex:116: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1

^C
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
1
Maybe to be completely sure, that your endpoint is fine, just attach it.PatNowak
Okay, so the endpoint seems to be actually dead. I occasionally get ** (exit) an exception was raised: ** (UndefinedFunctionError) function App.Endpoint.call/2 is undefined (module App.Endpoint is not available) as well. How can an endpoint just die and not get restarted by the supervisor?sk29910
Is your endpoint uses Phoenix.endpoint or did you change the name of your app recently and didn't change in endpoint as well?PatNowak

1 Answers

2
votes

It appears that Phoenix's live reload functionality is part of the Endpoint, so once the latter crashes, code reload doesn't work anymore.

It is possible to crash the Endpoint by inserting errors in code that falls under the Endpoint, e.g. by adding plug :some_imaginary_plug or by causing syntax errors in router.ex, since the router is also plugged into the Endpoint.

Now, it turns out that I was using the Absinthe package by forward-ing all /graphql requests to the Absinthe plug directly. As a result, any GraphQL type definition and related resolver functions (which happen to make up most of my app) are fragile: any inconspicuous CompileError due to them indirectly (but inevitably) leads the Endpoint to crash.

The best workaround I have found – until the Absinthe devs manage to make the resolvers independent from the type defs, which they have suggested they will – is to start the server with iex -S mix phoenix.server, and to manually run recompile whenever the Endpoint crashes. While this doesn't fix the underlying problem of the CodeReloader being a part of the Endpoint instead of vice versa, it keeps me from having to re-run webpack everytime, which is the biggest pain point.