Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elixir - Deadlock between library dependencies

The context

I'm building my own library of code for dealing with ElasticSearch, using the Tirexs package. This is my first time heading deep into macros, dependencies, use, import and several other of the most advanced features Elixir offers.

For that I have define a Document struct which looks roughly like this, (/lib/data/document.ex)

defmodule Document do
  use Document.ORM
  import Document.Builder, only: [build_document: 2]
  import Document.Builder.Validations, only: [is_document_valid?: 1, collect_errors: 1]

  defstruct [valid?: false, errors: nil, fields: %{}]

  def something do
    # uses imported functions from Document.Builder and Documen.Builder.Validations
  end
end

The Document module then uses several other functions from the Document.ORM module which don't seem to be the cause of the error.

The problem

My error is the following

Compilation failed because of a deadlock between files.
dataobj_1          | The following files depended on the following modules:
dataobj_1          | 
dataobj_1          |   web/controllers/document_controller.ex => Document
dataobj_1          |             lib/data/document/builder.ex => Document.Builder.AuxiliarBuilder
dataobj_1          |                     lib/data/document.ex => Document.Builder
dataobj_1          |      lib/data/document/orm/bulk/utils.ex => Document
dataobj_1          |    lib/data/document/builder/auxiliar.ex => Document

There's a deadlock which I don't know how to approach, I'm sure I'm doing something wrong.

The first dependency, the document_controller one occurs (I think) because it both refer to the module Document and the %Document struct in different places:

defmodule Data.DocumentController do
  use Data.Web, :controller

  def create(conn, %{"document" => document_params}) do
      {:ok, doc} = document_params 
        |> Document.new
      case Document.save(doc) do
        {:ok, record} ->
          conn
          |> put_status(201)
          |> render(Data.DocumentView, "document.json", payload: Document.find(record._id))
        {:error, map_of_errors} ->
          conn
          |> put_status(422)
          |> render(Data.ErrorView, "422.json", errors: map_of_errors)
      end
  end
  def update(conn, %{"id" => id, "document" => document}) do
    case Document.update(id, document) do
      %Document{valid?: false, errors: errors} ->
        conn
        |> put_status(422)
        |> render(Data.ErrorView, "422.json", errors: errors)
      docset ->
        conn
        |> put_status(200)
        |> render(Data.DocumentView, "update.json", payload: docset)
    end
  end

The other dependencies also refer to both the module and the struct, so I'm thinking the deadlock has to do with this. But I'm lost about what to do.

If there's necessary I can share more code, but to start the question I think it is enough.

Thanks in advance!

like image 444
Sebastialonso Avatar asked Dec 14 '25 10:12

Sebastialonso


2 Answers

You have circular dependencies.

Document -> Document.Builder -> Document.AuxiliarBuilder -> Document

Generally speaking, you should avoid child modules (Document.Builder, Document.AuxiliarBuilder) from calling their parent (Document). It sounds like you need to extract those functions out of Document and put them elsewhere.

Note: Elixir does not actually have a concept of parent and child modules. I am just using the words here because that is how I think about it.

like image 152
Justin Wood Avatar answered Dec 16 '25 00:12

Justin Wood


Apparently the circular dependency was happening because of the definition of the struct, namely %Document, and confusion with the module name.

Moving the struct definition into its own separate file (first couple of lines here were the hint), and changing the module name from Document to Data.Document (%Data.Document) was enough to solve all circular dependencies.

like image 24
Sebastialonso Avatar answered Dec 16 '25 01:12

Sebastialonso