Skip to content

fix: close relation metadata cache races - #492

Merged
appflowy merged 1 commit into
mainfrom
fix/relation-metadata-review-races
Aug 21, 2026
Merged

fix: close relation metadata cache races#492
appflowy merged 1 commit into
mainfrom
fix/relation-metadata-review-races

Conversation

@appflowy

@appflowy appflowy commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • capture an off-outline database identity before share-change cache invalidation so access revocation purges both the view and backing database collab
  • retire and fence relation fallback metadata while a picker is disabled, including revoke/restore events missed while unsubscribed
  • restart the shared relation-catalog request when invalidation supersedes a request before its outline load completes

Validation

  • pnpm exec jest — 6 focused suites, 146 tests
  • pnpm lint
  • pnpm build
  • git diff --check

Follow-up to #491.

Summary by Sourcery

Harden workspace share invalidation and relation metadata revalidation to prevent stale or inaccessible relation data from resurfacing.

Bug Fixes:

  • Prevent share-change cache invalidation from losing the backing database identity needed to purge revoked database collaborations.
  • Prevent relation metadata from resurfacing after access changes occur while the relation picker is disabled.
  • Retry shared relation-catalog loading when invalidation supersedes an in-flight request.

Enhancements:

  • Harden relation metadata lifecycle and access revalidation across picker suspension and listener gaps.

Tests:

  • Add coverage for off-outline database eviction, superseded catalog requests, and missed revoke/restore events while relation metadata is disabled.

@sourcery-ai

sourcery-ai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adjusts workspace permission handling and relation metadata lifecycle to avoid races between share-change invalidation, database catalog loading, and relation fallback state while disabled, with tests covering the new behaviors.

Sequence diagram for relation catalog request restart on invalidation

sequenceDiagram
  actor User
  participant RelationCellMenu
  participant useRelationData
  participant DatabaseContext
  participant WorkspaceCatalog

  User->>RelationCellMenu: open relation menu
  RelationCellMenu->>useRelationData: useRelationData(fieldId, { enabled: true })
  useRelationData->>WorkspaceCatalog: getCachedWorkspaceDatabaseCatalog(workspaceId)
  WorkspaceCatalog-->>useRelationData: undefined
  useRelationData->>WorkspaceCatalog: getWorkspaceDatabaseCatalog(workspaceId) (initial)
  useRelationData->>DatabaseContext: loadViews()
  DatabaseContext-->>useRelationData: outline pending

  WorkspaceCatalog->>WorkspaceCatalog: publishMockWorkspaceCatalog(workspaceCatalog)
  WorkspaceCatalog-->>useRelationData: initial catalog snapshot

  WorkspaceCatalog->>WorkspaceCatalog: publishMockWorkspaceCatalog(undefined)
  WorkspaceCatalog-->>useRelationData: catalog invalidation while outline pending

  DatabaseContext-->>useRelationData: outline resolves []
  useRelationData->>WorkspaceCatalog: getWorkspaceDatabaseCatalog(workspaceId) (replacement)
  WorkspaceCatalog-->>useRelationData: replacement catalog

  useRelationData-->>RelationCellMenu: selectedView.name = Replacement Projects
Loading

Sequence diagram for relation fallback retirement while disabled and access revoke/restore

sequenceDiagram
  actor User
  participant RelationCellMenu
  participant useRelationData
  participant DatabaseContext
  participant EventEmitter

  User->>RelationCellMenu: open relation menu (enabled: true)
  RelationCellMenu->>useRelationData: useRelationData(fieldId, { enabled: true })
  useRelationData->>DatabaseContext: loadViewMeta(viewId)
  DatabaseContext-->>useRelationData: primaryView (Tasks)
  useRelationData-->>RelationCellMenu: selectedView = Tasks

  User->>RelationCellMenu: close relation menu (enabled: false)
  RelationCellMenu->>useRelationData: rerender { enabled: false }
  useRelationData->>useRelationData: [retire fallback metadata]
  useRelationData->>EventEmitter: unsubscribe VIEW_ACCESS_REVOKED and VIEW_ACCESS_RESTORED

  EventEmitter-->>useRelationData: VIEW_ACCESS_REVOKED (while disabled)
  useRelationData-->>EventEmitter: no listener, event missed

  User->>RelationCellMenu: reopen relation menu (enabled: true)
  RelationCellMenu->>useRelationData: rerender { enabled: true }
  useRelationData->>DatabaseContext: loadViewMeta(viewId)
  DatabaseContext-->>useRelationData: error Access revoked
  useRelationData-->>RelationCellMenu: selectedView remains undefined

  Note over useRelationData,EventEmitter: In a restore scenario, VIEW_ACCESS_RESTORED fires while disabled
  User->>RelationCellMenu: reopen after restore
  useRelationData->>DatabaseContext: loadViewMeta(viewId)
  DatabaseContext-->>useRelationData: primaryView (Tasks)
  useRelationData-->>RelationCellMenu: selectedView = Tasks
Loading

File-Level Changes

Change Details Files
Capture the database identity and adjust cache invalidation ordering on share events so revocation reliably evicts both the view and backing database collab.
  • Move workspace-wide permission-related cache invalidation to occur after computing access flags and reading the changed view metadata.
  • Capture the backing database_id from the changed view before memory cache invalidation runs so off-outline databases can be evicted even when navigation is removed.
  • Ensure permission-derived subtree eviction and metadata-outline distrust still run when access may affect the current user.
  • Extend tests to cover off-outline database revocation using memory-only navigation, including expectations on collab deletion and cache behavior.
src/components/app/hooks/useWorkspaceData.ts
src/components/app/hooks/__tests__/useWorkspaceData.sidebarRevalidation.test.tsx
Handle database catalog invalidation races in relation data loading by starting a replacement request when the catalog snapshot disappears while the outline is still loading.
  • Allow relation data loading to retry against a replacement database catalog request even when there is no cached snapshot present, instead of bailing out.
  • Add a test that simulates a catalog being published, invalidated to undefined during an in-flight outline load, and then replaced, asserting the relation selects from the replacement catalog.
  • Verify loading state and call counts for database catalog retrieval in the race scenario.
src/components/database/components/property/relation/useRelationData.ts
src/components/database/components/property/relation/useRelationData.test.tsx
Retire and revalidate relation fallback metadata when the relation menu is disabled so that missed revoke/restore events do not expose stale metadata upon reopening.
  • Add an effect that, when the relation is disabled, increments the fallback access generation, clears the authoritative fallback, and installs a retired fallback access state tied to the current related database.
  • Reset the fallback selected view to an empty retired state while disabled, ensuring it does not resurrect metadata after an unobserved revoke.
  • Keep fallback access/selected view state stable when it already matches the retired shape to avoid unnecessary updates.
  • Add tests for retiring fallback metadata on disable when a revoke occurs while unsubscribed and for revalidating after a missed restore event by refetching view metadata on re-enable.
src/components/database/components/property/relation/useRelationData.ts
src/components/database/components/property/relation/useRelationData.test.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In the new useEffect that retires fallback metadata when enabled is false, workspaceId is in the dependency array but not referenced in the body; consider removing it to avoid unnecessary re-runs when the workspace changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the new `useEffect` that retires fallback metadata when `enabled` is false, `workspaceId` is in the dependency array but not referenced in the body; consider removing it to avoid unnecessary re-runs when the workspace changes.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@appflowy
appflowy merged commit 09d2887 into main Aug 21, 2026
15 of 16 checks passed
@appflowy
appflowy deleted the fix/relation-metadata-review-races branch August 21, 2026 00:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant