mikebridge commented on PR #39977:
URL: https://github.com/apache/superset/pull/39977#issuecomment-4502609584

   @aminghadersohi thanks for the post-approval flags — both are worth 
addressing. **F-002 fixed in `8b0495aacb`; F-001 deferred to a focused 
follow-up PR.**
   
   ### F-002 — session bypass cleanup. Fixed.
   
   You're right that relying on session teardown left a within-request 
widened-visibility window. The fix uses a variation on your option B/C: the 
filter records classes it added in `g._deleted_state_added_classes` 
(request-scoped, auto-cleans), and `SoftDeleteApiMixin.pre_get_list` removes 
those entries from `session.info[SKIP_VISIBILITY_FILTER_CLASSES]` in a 
`try/finally` immediately after augmenting the response.
   
   ```python
   # BaseDeletedStateFilter._add_session_bypass — track for release
   bypass = query.session.info.setdefault(SKIP_VISIBILITY_FILTER_CLASSES, set())
   bypass.add(self.model)
   added = getattr(g, DELETED_STATE_ADDED_CLASSES, set()) | {self.model}
   setattr(g, DELETED_STATE_ADDED_CLASSES, added)
   
   # SoftDeleteApiMixin.pre_get_list — release after augmentation
   try:
       self._inject_deleted_at(data)
   finally:
       self._release_session_bypass()
   
   @staticmethod
   def _release_session_bypass() -> None:
       added = getattr(g, DELETED_STATE_ADDED_CLASSES, set())
       if not added:
           return
       bypass = db.session.info.get(SKIP_VISIBILITY_FILTER_CLASSES, set())
       bypass -= added
       setattr(g, DELETED_STATE_ADDED_CLASSES, set())
   ```
   
   Key property: the release is scoped to entries *the filter* added. A 
programmatic caller using the `skip_visibility_filter` context manager or a DAO 
`skip_visibility_filter=True` call earlier in the same request is unaffected — 
those bypasses manage their own lifecycles via try/finally or DAO call scope. 
Pinned by `test_mixin_release_does_not_touch_unrelated_bypass_entries`.
   
   Sequence within a list response:
   
   1. `BaseDeletedStateFilter.apply` adds `self.model` to both 
`session.info[SKIP_VISIBILITY_FILTER_CLASSES]` and 
`g._deleted_state_added_classes`.
   2. FAB issues count + inner + outer + relationship loads — all see the 
bypass.
   3. `pre_get_list` runs *after* the list query: consumes the augmentation 
flag, injects `deleted_at`, then releases the filter's bypass.
   4. Any code that runs later in the same request (audit hooks, 
`after_request` handlers, dependent serialisation) sees the normal filtered 
view.
   
   Four new tests:
   
   - `test_filter_records_added_classes_on_g` — pins the tracker
   - `test_mixin_releases_bypass_after_inject` — pins the release fires after 
`pre_get_list` returns
   - `test_mixin_release_does_not_touch_unrelated_bypass_entries` — pins that 
programmatic bypasses are preserved
   - `test_mixin_release_is_noop_when_filter_was_not_invoked` — pins the no-op 
path for requests that didn't use `deleted_state`
   
   44 unit tests pass on the infrastructure branch (was 40).
   
   ### F-001 — TOCTOU on restore. Deferred.
   
   You're right that the `validate()` → `model.restore()` → commit window has a 
stale-authorization race. The fix isn't a one-liner, though, and the design 
choice between the two options has real trade-offs:
   
   - **`SELECT ... FOR UPDATE`** on the re-query needs either a new 
`for_update` kwarg on `BaseDAO.find_by_id` (widening a load-bearing DAO method) 
or bypassing the DAO for restore (losing the abstraction). Plus 
dialect-specific behaviour: SQLite silently no-ops, PostgreSQL/MySQL differ on 
timeouts and `nowait` / `skip_locked` policy.
   - **Atomic conditional update** — `UPDATE … WHERE uuid = ? AND deleted_at IS 
NOT NULL` — closes the window precisely but **bypasses ORM `before_update` / 
`after_update` event listeners**, including `AuditMixinNullable`'s `changed_on` 
/ `changed_by_fk` stamping. Losing audit attribution on restore is a real cost; 
we'd have to re-add stamping manually.
   
   Proper test coverage means an actual concurrent-session test rig (two 
sessions, observe lock contention or rowcount=0), not just a unit-level mock — 
a different kind of work than what's been in this PR.
   
   So F-001 lands separately, where the locking-vs-atomic-update design can get 
focused review and the concurrency tests can be built deliberately. Tracked in 
the spec at `sc-103157-soft-deletes/spec.md` under "Follow-up Items from 
Review."
   
   ---
   
   Infrastructure tip is now `8b0495aacb`. Entity branches rebased: `sc-106189` 
→ `9c6d1584a2`, `sc-106190` → `5e26ebc084`, `sc-106191` → `ac38056b41`. CI 
re-running across all four.
   


-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to