oban-bg / oban

💎 Robust job processing in Elixir, backed by modern PostgreSQL and SQLite3
https://oban.pro
Apache License 2.0
3.37k stars 313 forks source link

Oban Web UI throws 500s for some structured job results #1157

Closed connorjacobsen closed 1 month ago

connorjacobsen commented 1 month ago

Environment

Current Behavior

When using structured jobs, I am sometimes running into certain jobs that cause the Oban Web UI to throw Internal Server Errors when going to the job detail view. The return values of these jobs are all of the form {:ok, map()} (the data itself is just map()). Unfortunately I cannot post the raw objects for security reasons (but happy to include map shape and keys if helpful), but it's basically just a result JSON blob from an API call that I am storing. No functions or anything I would imagine to be sketchy or unsafe.

Stacktrace:

ArgumentError: errors were found at the given arguments:

  * 1st argument: invalid or unsafe external representation of a term

  Module "erlang", in :erlang.binary_to_term/2
  File "lib/oban/web/resolver.ex", line 378, in Oban.Web.Resolver.decode_recorded/2
  File "lib/oban/web/resolver.ex", line 390, in Oban.Web.Resolver.format_recorded/2
  File "lib/oban/web/live/jobs/detail_component.ex", line 188, in anonymous fn/2 in Oban.Web.Jobs.DetailComponent.render/1
  File "lib/phoenix_live_view/diff.ex", line 391, in Phoenix.LiveView.Diff.traverse/7
  File "lib/phoenix_live_view/diff.ex", line 532, in anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
  File "lib/enum.ex", line 2531, in Enum."-reduce/3-lists^foldl/2-0-"/3
  File "lib/phoenix_live_view/diff.ex", line 389, in Phoenix.LiveView.Diff.traverse/7

If I go find the job in the oban_jobs DB table and grab the return key from the meta column and then run the Oban.Web.Resolver.decode_recorded function on the data, it returns an object just fine. Always possible it's running that function on some different piece of data, maybe? What's even weirder is sometimes it renders fine at first but then eventually stops - not sure if there's a pattern or what it is, though.

This happens both for jobs in both the completed and retryable state.

Obviously, happy to provide more info if there's something you think would be helpful!

Expected Behavior

I expect the UI to render and show the job detail / result.

sorentwo commented 1 month ago

The decoder defaults to using the :safe option for decoding. That means it won't create unknown atoms, among other safeguards. What's likely happening is the atom in your map doesn't currently exist in the runtime, perhaps because it ran on a separate node.

You can change the decoding behavior in your application using the format_recorded/2 callback in a custom resolver. Here's an example that omits the [:safe] flag:

def format_recorded(recorded, _job) do
  recorded
  |> decode_recorded([])
  |> inspect(pretty: true)
end

Check the resolver docs for more on usage and the other available callbacks.

connorjacobsen commented 1 month ago

Thank you! Sorry for not seeing that in the docs.

sorentwo commented 1 month ago

No problem at all! It's not especially obvious from the error message and you'd have no way to link that back to Web docs anyhow 🙂