Expand my Community achievements bar.

Learn about Edge Delivery Services in upcoming GEM session
SOLVED

Synchronisation problem on shape update ? [whiteboard]

Avatar

Level 3

Hi all,

We had a problem with our own board and tried on AFCS youtube whiteboard sample who also seem to have it.

The recipe:

Get two users A and B on the board

  • Draw a square with A
  • User A resize the height of the square but to not do the 'mouse up action' which actualy publish to AFCS.
  • User B resize the width of the square but to not do the 'mouse up action' which actualy publish to AFCS.
  • At the same time A and B release the mouse up.

What you will have is two different states of the square shape a vertical rectangle on B and  horizontal rectangle on A.

There are not seing the same thing and that is a problem (isn'it ?)

What i think is happening behind the scene is the following:

AFCS receives an update from A on shape id #n,

It publishes it back to user A and B, A ignores it because it's an aciton coming from himself, and B updates its state with the message from A.

On the other side B publishes his state to AFCS, and A updates it.

(don't know how clear is this)

What we will try to fix this problem, is to update the shape even when the published message was from ourself.

What do you think ?

Thanks !

Greg

1 Accepted Solution

Avatar

Correct answer by
Former Community Member

Hi Greg,

Yes using a Timer should ideally solve you case with a large number of messages sent in quick succession. That's what I meant by trying something similar to updateDisplayList.

If you don't use Timer , then let's say you have the second keystroke but onItemReceive you have the first keystroke when you move using keyboard in quick succession. That will look bit weird in display bcoz your local UI has already moved for 2nd keystroke and you update again for the first keystroke. So, timer can solve this problem and I am not sure if it's the ideal way.

Thanks

Hironmay Basu

View solution in original post

13 Replies

Avatar

Former Community Member

Hi Greg,

If you publish the shape even when the published message was from yourself can let you run into UI latency when you are moving shapes and publishing many messages in quick succession.

This is a very typical race condition and We have been able to reproduce it on our side and thinking about it. You can as a suggestion, try out updating the shape (like we use updateDisplayList in Flex) after some time syncing from the model. We will try to figure on our side what works best.

Thanks

Hironmay Basu

Avatar

Employee

Hi Greg,

Updating from the model will definitely fix the scenario you mentioned (as there are no keyboard events). You will hit the latency issue that basu mentioned , when the local changes you make are from the keyboard. (say you are moving the shape using arrow keys )

If you are sure that users will not be modifying WhiteBoard shapes using keyboard , then I assume your approach would work.

Thanks

Avatar

Level 3

Hi Hironmay,

We publish the shape after our UI update, then wait for the publish back from AFCS, and then reupdate the UI on our side.

What do you mean by " syncing from the model" a message received from AFCS ?

Thanks

Avatar

Level 3

So if i understand well when it is from keyboard lets user the following scenaria

User A and B manipulates Text Shape T with content "zzz"

User A adds 'a' and user B adds 'b' at the same time (i.e before B receives sync from A and A the sync from B)

Then A is going to see zzzb and B zzza.

However if B or A also update the UI from their own update message sent by AFCS (publisherid) it should be fine no ?

If there are a lot of key events, we use a timer and buffer for Text to publish every 300ms so we may avoid the problem.

I am not sure i understand why the race condition is unavoidable.

Avatar

Correct answer by
Former Community Member

Hi Greg,

Yes using a Timer should ideally solve you case with a large number of messages sent in quick succession. That's what I meant by trying something similar to updateDisplayList.

If you don't use Timer , then let's say you have the second keystroke but onItemReceive you have the first keystroke when you move using keyboard in quick succession. That will look bit weird in display bcoz your local UI has already moved for 2nd keystroke and you update again for the first keystroke. So, timer can solve this problem and I am not sure if it's the ideal way.

Thanks

Hironmay Basu

Avatar

Level 3

Ah ok now i get it.

Not sure either for now :/

Thanks !

Avatar

Level 3

My plan was wrong.

If after 300ms A publishes 'aaa' but then A types more and later receives the message that he sent to AFCS there are two choices.

If he does not discard it, it will erase what he just added like "aaabb" and replace it with "aaa". Or maybe thats is where i should do a merge

When it comes from myself if it hasnt been updated by someone else in the meantime ? (getting complicated).

If he discards it then if a B user upates the same Text at a very close moment they may see different strings.

Indeed i dont see how to solve this for our multi line text area.

If user A types on Line 1 and B on Line2 at the same time we start loosing some pieces of what we type.

Scratching my head...

Avatar

Former Community Member

Hi Coulix,

I think there are a couple things here :

1. the whiteboard could be more sophisticated - we ignore shape modification when it comes from the local user, but to be honest we've hit a few small issues with this approach, and a few larger ones (such as the "out-of-sync" issue here).

2. at some points, you are correct - someone has to "lose" when it comes to a race condition. But there are ways to minimize this problem as well.

For 1), as Basu and Arun pointed out, the reason we ignore changes coming from the local user is largely because if you make several modifications to a shape in a row (such as hitting the left or right arrow very quickly over and over), your "local" state of the shape is a few messages ahead of the "shared" state. If you don't ignore the updates to the shared state, the shape needs to move back to where it was when you sent a previous message. And yes, we "batch" message updates to try to minimize the number of "old" messages , which can reduce the occurrence of this problem.

A better approach for 1) would be to buffer my "latest submitted item". Essentially, if I've hit the arrow key 10 times really fast, keep track of the last message I send to the service. When I receive a message, if it comes from me, only respond to it if it corresponds to my "last submitted item" for that message. In this way, old messages from myself wouldn't affect my current local state. This would also solve the original problem in the thread -

:: A and B both submit items for changing a shape at "the same time". Let's say that A's hits the server first, and B's second. A has "lost" this contention, as the message that will be stored on the service is B's, and the correct "shared" state of the shape is B's. (This is point 2, above, which I'll get into more in a bit).

:: Both A and B receive A's message first. B responds by changing his representation of the shape. A figures out that this message is its "last submitted item" for that shape, and responds the same way.

:: Both A and B receive B's message second. As above, both respond by changing their representation of the shape to the "official" shared state, B's.

For 2), to a certain degree, this is a fact of life in all multi-user applications. The choice you need to make as a developer comes down to "how many aspects of an object do I want users to independently manipulate at the same time?". For each one of these aspects, you need a separate line of communication, either as a separate item or completely different node. For example, notice that our whiteboard has 3 different nodes relating to shape : this is actually done so that one user can be changing the color of a shape, for example, while another moves it at the same time. Fine-tuning the "granularity of updates" is the best strategy available for solving this.

For your multi-text, I think you need to think about what's most useful to the user - should 2 users be editing at the same time? Would a baton make more sense? If they should be editing at the same time, it's probably the case that you need batons per paragraph or sentence.

Anyhow, this got pretty long =). We'll look at fixing the whiteboard issue on our end - let us know if we can help clarify further.

thanks

nigel

Avatar

Level 3

Wow that was a nice explanation !

Ok i understant the root of our problems now, we only use one shape definition for creation and for properties which includes colors and position.

Since we do not listen for our own published message we get out of sync on creation abd on updates. But now i have a theorical fix for both of them.

A better approach for 1) would be to buffer my "latest submitted item". Essentially, if I've hit the arrow key 10 times really fast, keep track of the last message I send to the service. When I receive a message, if it comes from me, only respond to it if it corresponds to my "last submitted item" for that message. In this way, old messages from myself wouldn't affect my current local state. This would also solve the original problem in the thread -

The upper method solves many problems but maybe not the TextShape:

In our case we use a 300 ms timer buffer on Text shape,

So A starts typing and the message get sent, i add it to  my "last submitted item" for this shape.

I add some more chars in less than the next 300ms and receives a message which comes from me.

It will corresponds to my last submitted item for this textshape, but not what is shown on the UI.

Without batton or using several communication channels in a per line fashiont TexShape we wont be able to solve this :/

Thanks !

Avatar

Level 3

A last question about Whiteboard:

To handle Shape creation the WBmodel uses:

NodeConfiguration.STORAGE_SCHEME_MANUAL

Letting the clients set the shape ID.

Race condition which is actually handled well by the whiteboard is when user A creates a Shape with id #4 and B also creates a shape with the same id at the same time. Since they are using an internal increment counter to handle shape id this case may happen (at least it does in our whiteboard).

My questions are:

  1. Why not use NodeConfiguration.STORAGE_SCHEME_QUEUE to handle unique ids set by the server ?
  2. How do you manage to avoid this race condition ?

After that, i am set to fix our broken whiteboard

Thanks !

Avatar

Former Community Member

Hi Greg,

Our createsShape function in SharedWhiteBoardModel doesn't set the shape ID by default and it gets automatically set and incremented by server even though the scheme is MANUAL. Only if you manually set the same ID there is a problem. Are you manually setting it ? If so, is there a specific need for that ?

Thanks

Hironmay Basu

Avatar

Former Community Member

Hi Coulix,

Actually, it doesn't really matter which storage scheme you use - as long as you submit an item with no itemID, the service will generate one for you. In all honesty, storage schemes don't do very much.

For #2, we handle this by having 2 separate steps to shape creation - "create" and "add". Create just submits the item to the service, with no itemID. When it comes back with an itemID, then we "add" it to the canvas. A simpler way to handle this would have been to use a GUID utility (mx has one) to generate random itemIDs from the client.

nigel

Avatar

Level 3

I saw the light !

I didnt know/forgot about the unique id from item.

Finally no more sync bugs,

19 of August will be AFCS's day at Edoboard

Greg

The following has evaluated to null or missing: ==> liqladmin("SELECT id, value FROM metrics WHERE id = 'net_accepted_solutions' and user.id = '${acceptedAnswer.author.id}'").data.items [in template "analytics-container" at line 83, column 41] ---- Tip: It's the step after the last dot that caused this error, not those before it. ---- Tip: If the failing expression is known to be legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)?? ---- ---- FTL stack trace ("~" means nesting-related): - Failed at: #assign answerAuthorNetSolutions = li... [in template "analytics-container" at line 83, column 5] ----