schnittchen

schnittchen

Hi,

I’m trying to track down why my testing experience is such a pain because there’s always many files being compiled… I’ve used the inotify trick from https://milhouse.dev/2016/08/11/understanding-elixir-recompilation/ to see which files these are (is there a simpler way?) and tried to understand from mix xref why those files were compiled.

So there’s runtime, compile time and exports dependencies, I learned from mix help xref. So when I do a trivial change like adding a newline in a source file, only compile time dependencies should trigger a recompile.
If I change file A, all files having a (transitive) compile time dependency on A would need to be recompiled.

This lead me to use mix xref graph --label compile --only-direct because my reasoning is that this would show me all non-transitive compile time dependencies, and I should be able to traverse from the changed file to every compiled file via a graph node connection in the output of this command.

Here’s a snippet from the output, the email.ex file has only one dependency shown.

lib/my_app/email.ex
└── lib/my_app/views/mailer_view.ex (compile)

The email file was recompiled when I made a trivial change in a file which, according to mix xref (with the invocation above), was not connected at all to the email file.

I must have some misconception here.

Showing Posts 1 to 10

NobbZ

NobbZ

If A depends on B at compiletime, then any transitive runtime dependency of B is a compiletime dependency of A. As As compiletime is Bs runtime.

axelson

axelson

Scenic Core Team

If you are using Bamboo (guessing based off the email view), it was a pretty bad offender in that regard. Luckily the latest release improves the situation significantly:
https://github.com/thoughtbot/bamboo/pull/559

Now all of your email-related modules won’t recompile all the time (or maybe it’s vice versa).

schnittchen

schnittchen OP

Thanks, that helps a bit.

schnittchen

schnittchen OP

Thank you, that makes sense, I had not thought of that.

schnittchen

schnittchen OP

There’s a pattern when writing macros to write as little code as possible inside the macro itself, but use functions as much as possible. The simplest such scenario is calling include to import functions into the calling module. This idea has lead to code like this:

defmodule Fancy do
  defmacro __using__(_) do
    quote do
      # do some stuff...
      import unquote(__MODULE__)
    end
  end

  def utility_function do
    # calls other module
    M.other_fun()
  end
end

When A uses Fancy, we have a compile time dependency A => Fancy
Due to the function inside the Fancy module, there is also a runtime dependency Fancy -> M

So when M changes, A needs to be recompiled.

In my case, A had a transitive runtime dep to the Phoenix router (for calling helpers), which created a huuuuuge recompilation loop.

The easiest solution here is to break the utility functions in Fancy out into a separate module (and file).

schnittchen

schnittchen OP

I still have the problem that when M is changed, my router gets recompiled, but the only relevant dependencies seem to be these runtime dependencies:

Router -> Controller -> M -> Router

I know a cycle might be bad, but since there is no compile time dependency involved I don’t get why the Router is recompiled.

NobbZ

NobbZ

Is the project public, or can you reproduce it in a fresh project that you can make public?

schnittchen

schnittchen OP

I’m afraid it’s not public. I think trying to reproduce it from bottom up is the right approach. Don’t know when I can get to that…

schnittchen

schnittchen OP

Thanks for the advice, I quickly found the culprit:

I had compile time dependencies from the router to two controllers. Because those controllers had a transitive run time dependency back to the router (redirects using route helpers) and from there to every other controller, changing any controller required the router to be recompiled.

The fix:

# change this:
get "/", MyApp.FooController, :index
# to this:
scope alias: MyApp do
  get "/", FooController, :show
end

simply because now the fully qualified module name of the controller it no longer present in the router.

Two other things I did/tried:

elixirc_options: [verbose: true]

in the project/0 callback of the MixFile, which made the effective dependency chain more obvious.

I also tried setting

config :phoenix, :plug_init_mode, :runtime

not only in dev mode, but in test mode as well, assuming that plug initialization would otherwise happen at compile time and that that would also be the case for the controllers being routed to (since they are plugs as well), but I was wrong here, the default :compile does not make all controllers compile time dependencies of the router.

schnittchen

schnittchen OP

I got tired of analyzing the output of mix xref so I wrote a thing… If you run into this problem please check out GitHub - schnittchen/why_did_recompile · GitHub and help me improve it

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
webofbits
Aludel - LLM Evaluation Workbench Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews