ash icon indicating copy to clipboard operation
ash copied to clipboard

Possible bug in `Ash.Actions.Update.Bulk.do_run/3`

Open nallwhy opened this issue 10 months ago • 3 comments

Describe the bug

When using ash_archival, Ash.Actions.Update.Bulk.do_run/8 calls Ash.Actions.Read.Stream.stream_strategy/3. If the strategy returned is :keyset, do_atomic_batches/6 is executed, which only applies the :atomic strategy. However, the action does not support :atomic, causing a failure.

https://github.com/ash-project/ash/blob/c675101316222205b3e3f2a16602763d89ec48e1/lib/ash/actions/update/bulk.ex#L1087

It seems that when Ash.Actions.Read.Stream.stream_strategy/3 returns :keyset, the :stream strategy should be applied instead.

To Reproduce

I failed to reproduce it in my playground project.

Expected behavior

When Ash.Actions.Read.Stream.stream_strategy/3 returns :keyset, the :stream strategy should be applied instead of enforcing :atomic, ensuring that the requested operation does not fail.

Runtime

  • Elixir version: 1.18.1
  • Erlang version: 27
  • OS: mac
  • Ash version: 3.4.60
  • any related extension versions
    • ash_archival: 1.1.1

Additional context

nallwhy avatar Feb 20 '25 01:02 nallwhy

@nallwhy its hard to reason about what we might do about this w/o a reproduction, or at least some information about how or why it failed. "it fails" is not enough information.

zachdaniel avatar Feb 20 '25 22:02 zachdaniel

This is an error message example. I don’t think there’s any reason for only atomic to remain in strategies.

I understand that it might not be easy to spot the bug just from this information. However, looking at the code, I think it is strange to enforce atomic batch when a stream strategy is available.

** (Ash.Error.Invalid) 
Bread Crumbs:
  > Returned from bulk update: Bard.Resources.UserOrg.destroy
  > Exception raised in: Bard.Resources.User.destroy

Invalid Error

* Bard.Resources.UserOrg.destroy had no matching bulk strategy that could be used.

Requested strategies: [:atomic]

Could not use `:stream`: Not in requested strategies
Could not use `:atomic_batches`: Not in requested strategies
Could not use `:atomic`: cannot atomically update a query if it has `before_action` hooks



  (ash 3.4.64) lib/ash/error/invalid/no_matching_bulk_strategy.ex:5: Ash.Error.Invalid.NoMatchingBulkStrategy.exception/1
  (ash 3.4.64) lib/ash/actions/update/bulk.ex:1135: Ash.Actions.Update.Bulk.do_run/8
  (ash 3.4.64) lib/ash/actions/update/bulk.ex:479: Ash.Actions.Update.Bulk.run/6
  (ash 3.4.64) lib/ash/actions/update/bulk.ex:159: Ash.Actions.Update.Bulk.run/6
  (ash 3.4.64) lib/ash/actions/update/bulk.ex:1186: anonymous fn/8 in Ash.Actions.Update.Bulk.do_atomic_batches/6
  (elixir 1.18.1) lib/stream.ex:626: anonymous fn/4 in Stream.map/2
  (elixir 1.18.1) lib/stream.ex:1761: anonymous fn/3 in Enumerable.Stream.reduce/3
  (elixir 1.18.1) lib/stream.ex:302: Stream.after_chunk_while/2
  (elixir 1.18.1) lib/stream.ex:1790: Enumerable.Stream.do_done/2
  (elixir 1.18.1) lib/stream.ex:1773: Enumerable.Stream.do_each/4
  (elixir 1.18.1) lib/stream.ex:703: Stream.run/1
  (ash 3.4.64) lib/ash/actions/update/bulk.ex:1329: Ash.Actions.Update.Bulk.run_batches/5
  (ash 3.4.64) lib/ash/actions/update/bulk.ex:479: Ash.Actions.Update.Bulk.run/6
  (ash 3.4.64) lib/ash/actions/update/bulk.ex:159: Ash.Actions.Update.Bulk.run/6
  (ash 3.4.64) lib/ash.ex:2569: Ash.bulk_destroy!/4
  (elixir 1.18.1) lib/enum.ex:987: Enum."-each/2-lists^foreach/1-0-"/2
  (ash_archival 1.1.1) lib/ash_archival/resource/changes/archive_related.ex:8: anonymous fn/3 in AshArchival.Resource.Changes.ArchiveRelated.change/3
  (ash 3.4.64) lib/ash/changeset/changeset.ex:4079: anonymous fn/2 in Ash.Changeset.run_after_actions/3
  (elixir 1.18.1) lib/enum.ex:4964: Enumerable.List.reduce/3
  (elixir 1.18.1) lib/enum.ex:2600: Enum.reduce_while/3
  (ash 3.4.64) lib/ash/changeset/changeset.ex:3557: anonymous fn/3 in Ash.Changeset.with_hooks/3
  (ecto_sql 3.12.1) lib/ecto/adapters/sql.ex:1400: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
  (db_connection 2.7.0) lib/db_connection.ex:1756: DBConnection.run_transaction/4
  (ash 3.4.64) lib/ash/changeset/changeset.ex:3555: anonymous fn/3 in Ash.Changeset.with_hooks/3
  (ash 3.4.64) lib/ash/changeset/changeset.ex:3699: anonymous fn/2 in Ash.Changeset.transaction_hooks/2
  (ash 3.4.64) lib/ash/changeset/changeset.ex:3536: Ash.Changeset.with_hooks/3
  (ash 3.4.64) lib/ash/actions/update/update.ex:423: Ash.Actions.Update.commit/3
  (ash 3.4.64) lib/ash/actions/update/update.ex:303: Ash.Actions.Update.do_run/4
  (ash 3.4.64) lib/ash/actions/update/update.ex:247: Ash.Actions.Update.run/4
  (ash 3.4.64) lib/ash.ex:2828: Ash.destroy/2
  (elixir 1.18.1) src/elixir.erl:386: :elixir.eval_external_handler/3
  (stdlib 6.1) erl_eval.erl:904: :erl_eval.do_apply/7
  (elixir 1.18.1) src/elixir.erl:364: :elixir.eval_forms/4
  (elixir 1.18.1) lib/module/parallel_checker.ex:120: Module.ParallelChecker.verify/1
  (iex 1.18.1) lib/iex/evaluator.ex:336: IEx.Evaluator.eval_and_inspect/3
  (iex 1.18.1) lib/iex/evaluator.ex:310: IEx.Evaluator.eval_and_inspect_parsed/3
  (iex 1.18.1) lib/iex/evaluator.ex:299: IEx.Evaluator.parse_eval_inspect/4
  (iex 1.18.1) lib/iex/evaluator.ex:189: IEx.Evaluator.loop/1
  (iex 1.18.1) lib/iex/evaluator.ex:34: IEx.Evaluator.init/5
  (stdlib 6.1) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
    (ash 3.4.64) lib/ash.ex:2589: Ash.bulk_destroy!/4
    (elixir 1.18.1) lib/enum.ex:987: Enum."-each/2-lists^foreach/1-0-"/2
    (ash_archival 1.1.1) lib/ash_archival/resource/changes/archive_related.ex:8: anonymous fn/3 in AshArchival.Resource.Changes.ArchiveRelated.change/3
    (ash 3.4.64) lib/ash/changeset/changeset.ex:4079: anonymous fn/2 in Ash.Changeset.run_after_actions/3
    (elixir 1.18.1) lib/enum.ex:4964: Enumerable.List.reduce/3
    (elixir 1.18.1) lib/enum.ex:2600: Enum.reduce_while/3
    (ash 3.4.64) lib/ash/changeset/changeset.ex:3557: anonymous fn/3 in Ash.Changeset.with_hooks/3
    (ecto_sql 3.12.1) lib/ecto/adapters/sql.ex:1400: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
    (db_connection 2.7.0) lib/db_connection.ex:1756: DBConnection.run_transaction/4
    (ash 3.4.64) lib/ash/changeset/changeset.ex:3555: anonymous fn/3 in Ash.Changeset.with_hooks/3
    (ash 3.4.64) lib/ash/changeset/changeset.ex:3699: anonymous fn/2 in Ash.Changeset.transaction_hooks/2
    (ash 3.4.64) lib/ash/changeset/changeset.ex:3536: Ash.Changeset.with_hooks/3
    (ash 3.4.64) lib/ash/actions/update/update.ex:423: Ash.Actions.Update.commit/3
    (ash 3.4.64) lib/ash/actions/update/update.ex:303: Ash.Actions.Update.do_run/4
    (ash 3.4.64) lib/ash/actions/update/update.ex:247: Ash.Actions.Update.run/4
    (ash 3.4.64) lib/ash.ex:2828: Ash.destroy/2
    iex:5: (file)

nallwhy avatar Feb 21 '25 05:02 nallwhy

Something definitely seems strange there. From what I can tell, ash_archival allows all strategies, and I can't find a reason we'd be resetting it or altering it. You're using ash_postgres right?

I've spent about an hour trying to piece it together. Ultimate w/o a reproduction I think I'm dead in the water. Sorry 😓

zachdaniel avatar Feb 21 '25 14:02 zachdaniel