jedcunningham commented on code in PR #71936:
URL: https://github.com/apache/airflow/pull/71936#discussion_r3857228581


##########
airflow-core/src/airflow/dag_processing/collection.py:
##########
@@ -923,8 +941,21 @@ def activate_assets_if_possible(self, models: 
Iterable[AssetModel], *, session:
             from sqlalchemy.dialects.sqlite import insert as sqlite_insert
 
             stmt = sqlite_insert(AssetActive).on_conflict_do_nothing()
-        if values := [{"name": m.name, "uri": m.uri} for m in models]:
-            session.execute(stmt, values)
+        # Of two assets sharing a name or a uri only the one offered first is 
activated. Choosing
+        # that here rather than leaving it to the insert means the rows can 
then be sorted, which
+        # they must be: ``asset_active`` is unique on both columns, so two 
writers inserting them
+        # in opposite orders deadlock on the index.
+        claimed_names: set[str] = set()
+        claimed_uris: set[str] = set()
+        values = []
+        for model in models:
+            if model.name in claimed_names or model.uri in claimed_uris:
+                continue

Review Comment:
   These two sets only know about the current batch, so an asset that's going 
to get rejected by the insert anyway can still grab the claim and knock out one 
that would've made it in.
   
   Say asset_active already has `("n9", "s3://u1")`, and this batch offers 
`("n1", "s3://u1")` then `("n1", "s3://u2")`. Before, the second one went in as 
its name and uri were both free. Now the first grabs n1, the second gets 
skipped, and then the first's insert conflicts anyway, so we end up with 
neither.
   
   The scheduler's `_activate_referenced_assets` does it the other way round - 
it looks at what's already active and skips before recording the claim. Seeding 
these two sets from the existing asset_active rows would match that, and the 
sorted insert still works.



##########
airflow-core/src/airflow/dag_processing/collection.py:
##########
@@ -850,6 +855,10 @@ def collect(cls, dags: dict[str, LazyDeserializedDAG]) -> 
Self:
                 dag_id: list(_get_dag_assets(dag, SerializedAsset, 
inlets=False, outlets=True))
                 for dag_id, dag in dags.items()
             },
+            # Left in the order the Dags define them: that order decides which 
of two assets

Review Comment:
   The read-back in sync_assets isn't ordered, so this only holds when all the 
assets are new. Anything already in asset from a previous parse comes back in 
DB order and gets offered first. Same as before this PR, so not a regression, 
but the comment and the new test both read as though collection order always 
wins, and the test clears assets each time so it only ever hits the all-new 
case.
   
   If you want the claim to actually hold, rebuilding the dict in self.assets 
order at the end would do it (`return {key: orm_assets[key] for key in 
self.assets if key in orm_assets}`), and then the test could cover the 
existing-asset half too. Happy to leave it if you'd rather keep the scope 
tight, in that case maybe just narrow the comment.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to