anotherpit
Nested grafts + Oban.Pro.Workflow.status = Infinite recursion
A graft inside another graft causes Oban.Pro.Workflow.status to recurse forever. oban_pro 1.7.5
defmodule App.NestedGraftRepro.Test do
# Minimal, self-contained reproduction of an Oban.Pro nested-graft bug (oban_pro 1.7.5).
#
# A graft that executes while its own job's `workflow_id` is already "grafted_"-prefixed
# (i.e. a graft nested inside a grafted workflow) produces a sub-job whose
# `workflow_id == sup_workflow_id`. `Oban.Pro.Workflow.status/1` walks sub-workflows via
# `sup_workflow_id -> workflow_id` with no visited-set, so that self-edge makes
# `expand_status` recurse forever (here: child process killed at max_heap).
#
# Topology:
# - W -> add_graft(:outer);
# - outer grafts a workflow containing add_graft(:inner);
# - inner grafts a leaf -> leaf.workflow_id == leaf.sup_workflow_id.
use ExUnit.Case, async: false
# `prefix: "sunrise"` matches this app's Oban schema; upstream you'd drop it (default "public").
use Oban.Pro.Testing, repo: App.Repo
import Ecto.Query
setup do
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(App.Repo, shared: true)
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
:ok
end
defmodule Leaf do
use Oban.Pro.Worker, queue: :general, max_attempts: 1
@impl Oban.Pro.Worker
def process(%Oban.Job{}), do: :ok
end
defmodule InnerGrafter do
use Oban.Pro.Worker, queue: :general, max_attempts: 1
@impl Oban.Pro.Worker
def process(%Oban.Job{}) do
Oban.Pro.Workflow.new()
|> Oban.Pro.Workflow.add(:leaf, Leaf.new(%{}))
|> Oban.Pro.Workflow.apply_graft()
|> Oban.insert_all()
:ok
end
end
defmodule OuterGrafter do
use Oban.Pro.Worker, queue: :general, max_attempts: 1
@impl Oban.Pro.Worker
def process(%Oban.Job{}) do
# Graft a workflow that itself contains a graft point -> nesting.
Oban.Pro.Workflow.new()
|> Oban.Pro.Workflow.add_graft(:inner, InnerGrafter.new(%{}))
|> Oban.Pro.Workflow.apply_graft()
|> Oban.insert_all()
:ok
end
end
test "nested graft produces workflow_id == sup_workflow_id, and status/1 never terminates" do
Oban.Testing.with_testing_mode(:manual, fn ->
Oban.Pro.Workflow.new()
|> Oban.Pro.Workflow.add_graft(:outer, OuterGrafter.new(%{}))
|> Oban.insert_all()
# Recursive drain (default) runs the dynamically-grafted jobs as they're inserted.
drain_jobs(queue: :general, with_safety: true)
jobs =
from(j in Oban.Job,
select: %{
name: fragment("?->>'name'", j.meta),
wid: fragment("?->>'workflow_id'", j.meta),
sup: fragment("?->>'sup_workflow_id'", j.meta)
}
)
|> App.Repo.all()
for j <- jobs, do: IO.puts("JOB #{j.name} wid=#{j.wid} sup=#{j.sup}")
# 1. The broken state: a job whose workflow_id == its own sup_workflow_id.
selfloop = Enum.find(jobs, fn j -> not is_nil(j.sup) and j.wid == j.sup end)
assert selfloop, "expected a workflow_id == sup_workflow_id self-loop job, found none"
IO.puts("SELF-LOOP NODE: name=#{selfloop.name} wid=#{selfloop.wid} sup=#{selfloop.sup}")
# 2. status/1 on that workflow never returns (infinite expand_status recursion).
# Run it in a heap-capped child so the test process survives.
parent = self()
{pid, ref} =
:erlang.spawn_opt(
fn ->
result = Oban.Pro.Workflow.status(selfloop.wid)
send(parent, {:returned, result})
end,
[:monitor, {:max_heap_size, %{size: 2_000_000, kill: true, error_logger: false}}]
)
outcome =
receive do
{:returned, _} -> :returned
{:DOWN, ^ref, :process, ^pid, reason} -> {:died, reason}
after
8_000 ->
Process.exit(pid, :kill)
:timeout
end
IO.puts("status/1 outcome: #{inspect(outcome)}")
refute outcome == :returned,
"status/1 returned — infinite recursion NOT reproduced (bug may be fixed)"
end)
end
end
Most Liked
sorenone
Oban Core Team
Welcome
@anotherpit,
Thanks for catching a strange edge case and giving us the report. It’s fixed and ready for the next patch release.
2
Popular in Troubleshooting
When I compile hex package io_ansi_plus, I get error “Codepoint failed”. Why? and why are the “Got:” and “Hint:” codepoints identical? It...
New
Currently reading and experimenting through, which is in 1.6 Chapter 7: Sign up | Phoenix Tutorial (Phoenix 1.6) | Softcover.io
In regar...
New
Summary
A downstream job that deps on a graft correctly waits for the grafted jobs at one graft level, but stops waiting when the workflo...
New
Environment
Elixir 1.18.3-otp-27 / OTP 27.3.4
Oban 2.20.2
Phoenix 1.7.x
db_connection 2.8.1 / Postgrex 0.21.1
Infrastructure: Google Cl...
New
Having read the book, I’m keen to explore Ash a bit more, but I’m falling at the first hurdle - the creation of a new project.
Here’s my...
New
Hello, I am currently using Elixir 1.18.4 and OTP 27 with Linux Mint 22.3 - Cinnamon 64-bit as my operating system. I came across this er...
New
We recently started running Oban with Oban Web on PostgreSQL (AWS RDS gp3) and noticed our oban_jobs buffer cache hit ratio sitting at 65...
New
For hex package islands_score the source @spec for function format/2 is as such:
@spec format(t, keyword) :: :ok
Why does it show on 4 ...
New
A graft inside another graft causes Oban.Pro.Workflow.status to recurse forever. oban_pro 1.7.5
defmodule App.NestedGraftRepro.Test do
...
New
Hi, I’m on Linux Mint 22.3 - Cinnamon 64-bit, and I encountered an error when trying to build an elixir phoenix container
577.1 Reading...
New
Other popular topics
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode.
The solution seems to be, in a hyphena...
New
Hello everyone,
I try to use an Javascript Event Handler in my root.html.leex file.
Therefore I created a function in the app.js file: ...
New
If I have a post route which an argument:
post /my_post_route/:my_param1, MyController.my_post_handler
How would get the post params ...
New
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
Good day to you all.
I have been struggling to get a query involving like and ilike to work.
Can anyone assist me on this, please?
pro...
New
i’m a new one to elixir
which editor can i use
vs code? or atom?
Thanks! :smiley:
New
I have VueJS GUIs with the project generated using Webpack.
I have Elixir modules that will need to be used by the VueJS GUIs.
I forese...
New
Update:
How to use the Blogs & Podcasts section
You can post links to your blog posts or podcasts either in one of the Official Blog...
New
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
Hi everyone!
I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










