Skip to content

fix: ignore stale observer map array notifications#7629

Open
pradeep-ramola wants to merge 3 commits into
microsoft:mainfrom
pradeep-ramola:pradeep-ramola/observer-map-array-registration-owners
Open

fix: ignore stale observer map array notifications#7629
pradeep-ramola wants to merge 3 commits into
microsoft:mainfrom
pradeep-ramola:pradeep-ramola/observer-map-array-registration-owners

Conversation

@pradeep-ramola

Copy link
Copy Markdown

Pull Request

📖 Description

Fixes stale observer-map array notifications after arrays are replaced.

Previously, observer-map tracked observed arrays with a single WeakSet, so the first owner/root context for an array could keep receiving notifications even after that array was no longer reachable from the current data. This changes array observation to use owner-specific registrations and guards notifications by checking whether the changed array is still reachable from the registered owner’s current root property.

This is a fix and is not a breaking change.

🎫 Issues

Fixes #7579

👩‍💻 Reviewer Notes

The main area to review is the new array registration/reachability logic in observer-map-utilities.ts.

A useful smoke test is the observer-map deep-merge fixture. It covers stale root arrays, stale nested arrays, direct assignment replacement, proxy assignment replacement, and shared arrays where only the owner that still references the array should be notified.

📑 Test Plan

  • npm run build:tsc -w @microsoft/fast-element
  • npx biome check packages/fast-element/src/declarative/observer-map-utilities.ts packages/fast-element/test/declarative/fixtures/extensions/observer-map-deep-merge/main.ts packages/fast-element/test/declarative/fixtures/extensions/observer-map-deep-merge/observer-map-deep-merge.spec.ts change/@microsoft-fast-element-ab7cde20-cff1-47a9-8e95-5895dd961321.json
  • npx beachball check --verbose
  • npx playwright test --config=packages/fast-element/playwright.declarative.config.ts --project=chromium fixtures/extensions/observer-map-deep-merge/observer-map-deep-merge.spec.ts

✅ Checklist

General

  • I have included a change request file using $ npm run change
  • I have added tests for my changes.
  • I have tested my changes.
  • I have updated the project documentation to reflect my changes.
  • I have read the CONTRIBUTING documentation and followed the standards for this project.

Agents

  • I have linked to an existing issue in this project that this change addresses
  • I have read the skills
  • I have read the DESIGN.md file(s) in packages relevent to my changes
  • I have updated the DESIGN.md file(s) in packages relevent to my changes

⏭ Next Steps

None.

@janechu janechu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the contribution @pradeep-ramola! I've added a few comments but overall this looks like a good fix.

target: any,
rootProperty: string,
): void {
let registrations = observedArraysMap.get(data);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

registrations are never cleaned up, a subscriber may have an old notifier attached, can you ensure these get cleaned up?

};
}

public async mutateStaleNestedItemsAfterDeepMerge() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This test does not actually fail on the issue, consider this change:

public async mutateStaleNestedItemsAfterSecondDeepMerge() {
    deepMerge(this.users[0], {
        orders: [this.createOrder(901, "First replacement")],
    });
    await Updates.next();

    const oldItems = this.users[0].orders[0].items;

    deepMerge(this.users[0], {
        orders: [this.createOrder(902, "Second replacement")],
    });
    await Updates.next();

    let notifications = 0;
    const notifier = Observable.getNotifier(this);
    const subscriber = {
        handleChange() {
            notifications++;
        },
    };

    notifier.subscribe(subscriber, "users");

    oldItems.push(this.createProduct(9000, "Stale item"));
    await Updates.next();

    notifier.unsubscribe(subscriber, "users");

    return {
        notifications,
        currentItemCount: this.users[0].orders[0].items.length,
        staleItemCount: oldItems.length,
    };
}

Pair this with the following test in the spec file:

test("should ignore stale nested array mutations after replacing arrays with deepMerge", async ({
    page,
}) => {
    const hydrationCompleted = page.waitForFunction(
        () => (window as any).hydrationCompleted === true,
    );
    await page.goto("/fixtures/extensions/observer-map-deep-merge/");
    await hydrationCompleted;

    const result = await page
        .locator("deep-merge-test-element")
        .evaluate((element: any) =>
            element.mutateStaleNestedItemsAfterSecondDeepMerge(),
        );

    expect(result.notifications).toBe(0);
    expect(result.currentItemCount).toBe(1);
    expect(result.staleItemCount).toBe(2);
});

This pattern fails without the fix because  oldItems.push(...)  incorrectly notifies the element’s users root property after the second replacement.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, updated in 8937b49.

  • stale array registrations are now removed and their array notifier subscriber is unsubscribed
  • the nested stale-array regression now uses the second deepMerge repro case from your comment

The focused Playwright fixture test, tsc build, Biome check, and Beachball check all pass locally.

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.

fix: prevent stale observerMap array notifications after replacement in fast-html

2 participants