GES233

GES233

Hi everyone,

I’m currently building QyCore, a lightweight open-source DAG task scheduler and execution engine written in Elixir. The origin goal is to create a backend for node-based editors similar to ComfyUI, as shown in this thread earlier.

I’ve hit a wall with a persistent Dialyzer error that I can’t seem to resolve, even after cleaning builds and explicitly defining specs.

The Context

I have a Scheduler.build/2 function that validates a Recipe struct and returns {:ok, context} or {:error, reason}. It works perfectly at runtime, all tests passed, but Dialyzer is convinced that the {:ok, …} path is impossible.

The Error

When checking Executor.Async and Executor.Serial(Scheduler.build/2’s downstream), Dialyzer complains:

lib/qy_core/executor/async.ex:17:13:pattern_match
The pattern can never match the type.

Pattern:
{:ok, _ctx}

Type:

  {:error,
   {:cyclic, [any()]} | {:missing_inputs, map()} | {:option_validation_failed, [any()]}}
________________________________________________________________________________
...
________________________________________________________________________________
lib/qy_core/executor/serial.ex:15:13:pattern_match
The pattern can never match the type.

Pattern:
{:ok, _ctx}

Type:

  {:error,
   {:cyclic, [any()]} | {:missing_inputs, map()} | {:option_validation_failed, [any()]}}


________________________________________________________________________________

This implies that my Scheduler.build/2 function (shown below) is inferred to only return {:error, …}.

The Code

defmodule QyCore.Scheduler do
  # ...

  @spec build(Recipe.t(), initial_params()) ::
          {:ok, Context.t()} | {:error, term()}
  def build(%Recipe{} = recipe, initial_params) do
    # ... (initial map preparation) ...
    initial_keys = Map.keys(initial_map)

    # Dialyzer seems to think this `with` block never reaches the `do` block
    with :ok <- validate_step_option(recipe.steps),
         :ok <- Recipe.Graph.validate(recipe.steps, initial_keys) do
      do_build(recipe, initial_map)
    else
      {:error, reason} -> {:error, reason}
    end
  end

  # I added an explicit spec here hoping to fix it, but it didn't work.
  @spec validate_step_option([Step.t()]) :: :ok | {:error, {:option_validation_failed, list()}}
  defp validate_step_option(steps) do
    errors =
      steps
      |> Enum.with_index()
      |> Enum.reduce([], fn {step, idx}, acc ->
         # ... validation logic (returns list of errors) ...
         # If valid, `acc` remains []
         # If validator not exist, no error appended
      end)

    case errors do
      [] -> :ok
      _ -> {:error, {:option_validation_failed, errors}}
    end
  end

  defp do_build(recipe, initial_map) do
    # ... logic ...
    {:ok, %Context{...}}
  end

  # ...
end

What I’ve tried

  1. mix do clean, compile multiple times.
  2. Added explicit @spec to the private function validate_step_option/1.
  3. Verified that Recipe.Graph.validate/2 (in another module) has a spec that includes :ok as a return type.

It feels like Dialyzer’s success typing analysis determines that validate_step_option (or Graph.validate) can never return :ok, thus marking the do_build call as unreachable code.

Has anyone encountered this specific behavior where Dialyzer ignores an explicit :ok path in a reducer?

Any insights would be appreciated! Also, if you are interested in DAG scheduling in Elixir, feel free to check out the repo structure.

Thanks!


P.S. Warings were ignored by configure .dialyzer_ignore.exs. Hovewer, It seems trigger warning within ALL downstream functions which invoked Scheduler.build/2.

As anticipated, I want to leverage the community’s resources after showcasing the demo to create a more production-ready, fault-tolerant and Elixit-like Executor. But it seems necessary to manually declare that alarms should be ignored in all relevant modules of the new application.

Is there a more-elegant way to solve?

Showing Posts 1 to 2

GES233

GES233 OP

Problem solved, by refactoring executor’s API.

Now executor will receive a Scheduler.Context struct and execute work flow.

defmodule Orchid.Executor do
  @moduledoc """
  Executor behavoir.
  """

  @type executor :: module()
  @type executor_opts :: keyword()

  @type response :: {:ok, [Orchid.Param.t()]} | {:error, term()}

  @callback execute(Orchid.Scheduler.Context.t(), executor_opts()) ::
              response()
end

I also change the application’s name from qy_core into orchid, because it’s project-agnostic.

It also released on hex.pm, and I translated some comments and documents into English. This is a lightweight library, and its library has only about ~1k lines of code(exclude comments and test).

Anyway, thank you for your viewing, especially the group member who helped me in the elixir chat group.

dimitarvp

dimitarvp

Thank you for translating the README! I have checked your project at the time of your original post and bounced off when I saw it was in Chinese. Now I’ll check it out.

— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New
thiagogsr
** (ArgumentError) expected :max_attempts to be a positive integer, got: {:@, [line: 10, column: 19], [{:max_attempts, [line: 10, column:...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
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

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews