{@thread.name}

zachdaniel
2023-04-28

zachdaniel:

I’ve added Ash.Query.lock , which will attach a lock to the query. This currently is only supported by the postgres data layer. There are two kinds of locks, builtin locks and data layer locks. The only builtin lock currently is :for_update . The rest will be passed to the data layer.

For example:

query
|> Ash.Query.lock(:for_update)

or

query
|> Ash.Query.lock( "FOR SHARE NOWAIT")

There is also a builtin change for refetching and locking a record before updating it. This can help with ensuring that all update actions to a given record are serialized. For example:

changes do
  change get_and_lock_for_update(), on: [:update]
end

or in individual actions

update :update do
  change get_and_lock_for_update()
end

jharton:

this is awesome work my dude!

lejoko_annatel:

Hey! Isn’t that exactly what I needed in my “update of related resource” post ? That’s great!

zachdaniel:

Yep! This should help with your thing 🙂

hanrelan:

Am I right that this can be used in place of atomics (until those are ready) for incrementing for example?

zachdaniel:

Yes, the get_and_lock_for_update change can be used to achieve the same result

hanrelan:

Been struggling with this for a while but it doesn’t seem to work:

    update :increment_completed_sections do
      accept []

      change get_and_lock_for_update()

      change fn changeset, _context ->
        Ash.Changeset.before_action(changeset, fn changeset ->
          old_completed_sections = Ash.Changeset.get_attribute(changeset, :completed_sections)

          Ash.Changeset.change_attribute(
            changeset,
            :completed_sections,
            old_completed_sections + 1
          )
        end)
      end

The update call just doesn’t update completed_sections (only updates updated_at )

hanrelan:

If I comment out the change get_and_lock_for_update() then it does the right thing

hanrelan:

Ok I think I figured it out - get_and_lock_for_update() need to come after my action because before_action hooks are executed in reverse order.

This is a pretty subtle issue so it might be worth calling out explicitly in the docs

zachdaniel:

Yeah, good point. I’ve been thinking of potentially adding another phase, called setup_action that runs before any before_action hooks and happen in the order that they are added.