Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Anomalous behavior occurs when repeating removing and inserting between LiveLists #1371

Open
enk0de opened this issue Jan 3, 2024 · 10 comments
Labels
bug Something isn't working Custom App

Comments

@enk0de
Copy link

enk0de commented Jan 3, 2024

Describe the bug
Anomalous behavior occurs when removing an item from a LiveList named A, inserting it back into a LiveList named B, and then reversing the action.

A and B LiveLists are properties that belong to some LiveMap.

Here's what happens

  1. you remove an item from a LiveList named A and insert it into a LiveList named B. (When you take a snapshot, it looks like the item is deleted from A and added to B.)

  2. remove the item you added to B from the LiveList named B again and add it to the LiveList named A. (When you take the snapshot, it should look like the item was deleted from B and added to A.)

  3. Repeat this behavior very quickly

  4. After a few iterations, you should see an error situation. An error is observed, such as "The item that was removed from B is still there. We say "we observe an error such as" because the error doesn't look the same every time.

To Reproduce

Steps to reproduce the behavior:

  1. you remove an item from a LiveList named A and insert it into a LiveList named B. (When you take a snapshot, it looks like the item is deleted from A and added to B.)

  2. remove the item you added to B from the LiveList named B again and add it to the LiveList named A. (When you take the snapshot, it should look like the item was deleted from B and added to A.)

Expected behavior

Illustrations

2024-01-03.11.19.07.mov

Environment (please complete the following information):

  • [...]
    "@liveblocks/client": "1.8.1",
    "@liveblocks/core": "1.8.1",
    "@liveblocks/node": "1.8.1",
    "@liveblocks/react": "1.8.1",

Additional context

Add any other context about the problem here.

@enk0de enk0de added bug Something isn't working triage needed The issue needs to be reviewed by the team labels Jan 3, 2024
@enk0de
Copy link
Author

enk0de commented Jan 3, 2024

image

@nvie
Copy link
Collaborator

nvie commented Jan 4, 2024

Hi @enk0de – thanks for taking the time to write up this issue report. I've got a few follow-up questions to help investigate this issue.

  • From your dependency list, it looks like you're using the @liveblocks/react package to interact with Storage primarily, can you confirm this?
  • What does the (relevant) shape of your Storage tree look like? (Could you for example share the Storage type definition from your liveblocks.config.ts file if you have any?)
  • Can you share the relevant mutations you are performing in your mouse drag event? How exactly are you mutating storage?
  • Can you share the relevant room subscriptions you have set up that may trigger re-rendering?
  • Which React version are you on?

Thank you!

@enk0de
Copy link
Author

enk0de commented Jan 4, 2024

  1. Yes, we are using @liveblocks/react to use createRoomContext, shallow!
  2. As security issue, we cannot reveal exact interface. but i can share simillar structure.
interface Storage {
  tree: LiveObject<{
    rootNodeID: string; // Tree entry
    nodes: LiveMap<string, LiveObject<{ id: string, x: string, y: string, parentNodeID?: string, children?: LiveList<string> }>>;
  }>;
};
  1. Yes, sure.
  • when you drag the BlockNode, Node's x, y position is updated
  • when you drag the BlockNode to ContainerNode(which can take other Node in children), instantly below operations occurs.
    • remove BlockNode's id item from original parent's children LiveList.
    • replace BlockNode's parentNodeID value(original parent node id) with new parent(ContainerNode)'s id.
    • insert BlockNode's id item into new parent(ContainerNode)'s children LiveList.
  1. Yes, sure
    useStorage((root) => root.tree.nodes, shallow);
  2. We are using react@18.2.0

@nvie
Copy link
Collaborator

nvie commented Jan 4, 2024

Amazing, thanks @enk0de! I assume you're making these three writes in a single batch(), so they're always sent over the wire as an atomic batch of ops? If you're using useMutation, that's already the case.

Could you maybe share what happens in one of your observed error states?

For example, do only 2 of the three updates take place? Or more than those 3? Or is there a duplicate entry somewhere? Basically: what does your erroreneous state look like in the situation like your recorded video?

@enk0de
Copy link
Author

enk0de commented Jan 5, 2024

I assume you're making these three writes in a single batch()

Yes, We are doing those operations in one batch callback!
Sometimes the execution of a batch() occurs in another batch(), or after a history.pause().

Could you maybe share what happens in one of your observed error states?

In recorded video, There are two spaces there. One is inside the white box shown in the video, and the other is outside the white box. The blue rectangle in the recorded video can be dragged to become a child in either of those two spaces.

Therefore, the blue square should only exist in one of the two spaces.

At the beginning of the video, you can see that moving the blue rectangle slowly works fine, but when you move it very quickly, it suddenly crashes.

an error situation occurs where the blue square exists in both spaces. This is not a React rendering issue, there are actually two of them in the data from Liveblocks.

@enk0de
Copy link
Author

enk0de commented Jan 5, 2024

hi, @nvie. I found a solution to resolve the issue.
the issue occurs when remove dragging node ID from original parent's children LiveList.

here is my original code

  private deleteNodeFromParent(id: string) {
    const parentNode = this.getParentNode(id);
    if (parentNode === undefined) {
      return;
    }

    const children = this.getLiveNode(parentNode.id)?.get('children');
    if (children === undefined) {
      throw new Error('Node children not exists');
    }

    const index = children.indexOf(id);
    children.delete(index);
}

The code below fixes the problem.

  private deleteNodeFromParent(id: string) {
    const parentNode = this.getParentNode(id);
    if (parentNode === undefined) {
      return;
    }

    const parentLiveNode = this.getLiveNode(parentNode.id);
    const originalChildren = parentLiveNode?.get('children');

    if (parentLiveNode === undefined) {
      throw new Error('Node not exists');
    }

    if (originalChildren === undefined) {
      throw new Error('Node children not exists');
    }

    parentLiveNode.set(
      'children',
      new LiveList(originalChildren.filter((childID) => childID !== id))
    );
  }

i just re-create children LiveList and replace it with originalChildren LiveList.
could you find and let me know why this problem happens? Thanks!

@nvie
Copy link
Collaborator

nvie commented Jan 5, 2024

Ah, I see! Thanks for sharing that code example, @enk0de. This makes the problem a bit clearer to understand.

To summarize, the issue you were facing before is that under some conditions the children.delete(index) call sometimes does not remove the node from the LiveList, and as a result you are seeing the drag node end up in more than one children lists at the same time, causing an invalid state?

If the above is correct, I have an idea of what might explain this issue.

@enk0de
Copy link
Author

enk0de commented Jan 8, 2024

To be more precise, the node is not deleted when you do children.delete(index), but rather the nodeID is deleted in children LiveList and then immediately restored to the list.

@nvie
Copy link
Collaborator

nvie commented Jan 8, 2024

Thanks for sharing that extra info @enk0de. Can you maybe also share if it's only the client that's affected, or does the same bug happen in a different client/window as well when it happens? Trying to bisect if this bug is scoped to the client or to the server. Thanks!

@enk0de
Copy link
Author

enk0de commented Mar 12, 2024

Hi. nvie. I am still suffering the problem. have you found out why this occurs?

@adigau adigau added the public repo label Apr 15, 2024 — with Linear
@adigau adigau removed triage needed The issue needs to be reviewed by the team public repo labels Apr 15, 2024
@adigau adigau added the 🎪 Room label Apr 24, 2024 — with Linear
@adigau adigau added the Engineering label May 9, 2024 — with Linear
@adigau adigau removed the Engineering label May 9, 2024
@adigau adigau removed the 🎪 Room label May 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Custom App
Projects
None yet
Development

No branches or pull requests

3 participants