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

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
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
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
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews