locked
Upredicable results using two-way sync with "Destination wins" on both side RRS feed

  • Question

  • Based on my test when "Destination wins" is used as constraint resolution policy on both local store and remote store the result is not predicable - well, the result is one of two possibilities. Allow me to explain using an example. At initial state both store A and store B contains a item with same key "1". Then sync starts:

    Initial state

    =======

    Store A: Item A1: Key=1, Value=”ABC”

    Store B: Item B1: Key=1, Value=”DEF”

     

    During upload, because “destination wins”, a tombstone is created for A1. Then during download, changes from store B are “B1 is created/updated, and A1 is marked to be deleted”.

     

    After upload

    =======

    Store A: Item A1: Key=1, Value=”ABC”

    Store B: Item B1: Key=1, Value=”DEF”

                 Item A1: Key=1, Value=”ABC”, Delete=true

     

    Note that the order of apply these two changes matters in this case. If B1 creation is applied first another constraint conflict occurs on store A, and because “destination wins”, B1 is marked to be deleted. Then “A1 is marked to be deleted” is applied. Store A ends up with no items.

     

    After download (Create first)

    ============

    Store A: Item A1: Key=1, Value=”ABC”, Delete=true

                 Item B1: Key=1, Value=”DEF”, Delete=true

    Store B: Item B1: Key=1, Value=”DEF”

                 Item A1: Key=1, Value=”ABC”, Delete=true

     

    On the other hand, if “A1 is marked to be deleted” first, A1 is deleted and B1 is added, and both stores end up in sync.

     

    After download (Delete first)

    ============

    Store A: Item A1: Key=1, Value=”ABC”, Delete=true

                 Item B1: Key=1, Value=”DEF”

    Store B: Item B1: Key=1, Value=”DEF”

                 Item A1: Key=1, Value=”ABC”, Delete=true

     

    I executed my test case many times and the order seemed random and I got one of the two results each time.

    Monday, May 10, 2010 11:57 PM

Answers

  • For the case when Create is enumerated first you should see these steps:

    • SaveItemChange() with changeAction == CREATE (item B1). This is where you should report the constraint conflict, and perform nothing else.
    • SaveConstraintConflict() with isTemporary == TRUE.
    • SaveItemChange() with changeAction == DELETE (item A1).
    • SaveItemChange() with changeAction == CREATE (item B1). This time there should be no constraint, and the item should be applied successfully.

    I've just debugged through this scenario (create comes first in second sync), and I ended up with the correct result (item A1 deleted, item B1 - alive).

    Are you using the same replica as in the other thread? From what you describe it looks like you would resolve the constraint yourself, and so ending up with both items deleted.

    When a constraint is reported, the engine "decides" whether the conflict should be resolved right away, or whether it should be logged temporary (in the hope that some change will make the constraint go away). This decision is based on the knowledge for the conflicting item. Say we have items A and B. Source knows only A, but destination knows both B (alive) and A(deleted as a loser of the constraint). When syncing back, B is enumerated first, and a constraint is reported. At this moment engine knows that there is an incoming change for item A (and this change might make the constraint dissapear), so B is temporarily logged (hence the isTemporary == TRUE). Next the delete for item A is applied (and constraint goes away). Lastly, item B is applied, and since A has already been deleted, there is no constraint, and it's applied successfully.

    Please confirm/infirm whether you see the steps I've described above in the sync session when CREATE Is enumerated first.

    Adrian

    Tuesday, May 11, 2010 5:43 PM

All replies

  • Friendly Dog II,

    is this the same issue which we resolved on the other thread or is it a different one ?

    Tuesday, May 11, 2010 3:16 AM
  • This is a different one.
    Tuesday, May 11, 2010 4:58 AM
  • For the case when Create is enumerated first you should see these steps:

    • SaveItemChange() with changeAction == CREATE (item B1). This is where you should report the constraint conflict, and perform nothing else.
    • SaveConstraintConflict() with isTemporary == TRUE.
    • SaveItemChange() with changeAction == DELETE (item A1).
    • SaveItemChange() with changeAction == CREATE (item B1). This time there should be no constraint, and the item should be applied successfully.

    I've just debugged through this scenario (create comes first in second sync), and I ended up with the correct result (item A1 deleted, item B1 - alive).

    Are you using the same replica as in the other thread? From what you describe it looks like you would resolve the constraint yourself, and so ending up with both items deleted.

    When a constraint is reported, the engine "decides" whether the conflict should be resolved right away, or whether it should be logged temporary (in the hope that some change will make the constraint go away). This decision is based on the knowledge for the conflicting item. Say we have items A and B. Source knows only A, but destination knows both B (alive) and A(deleted as a loser of the constraint). When syncing back, B is enumerated first, and a constraint is reported. At this moment engine knows that there is an incoming change for item A (and this change might make the constraint dissapear), so B is temporarily logged (hence the isTemporary == TRUE). Next the delete for item A is applied (and constraint goes away). Lastly, item B is applied, and since A has already been deleted, there is no constraint, and it's applied successfully.

    Please confirm/infirm whether you see the steps I've described above in the sync session when CREATE Is enumerated first.

    Adrian

    Tuesday, May 11, 2010 5:43 PM
  • When Create is enumerated first, I observed

    SaveItemChange() with changeAction == CREATE (item B1). This is where you should report the constraint conflict, and perform nothing else.
    SaveConstraintConflict() with isTemporary == TRUE.
    SaveItemChange() with changeAction == DELETE (item A1).

    But the last step

    SaveItemChange() with changeAction == CREATE (item B1).

    is missing.

    What are the possible causes? I didn't see any exceptions.

    Tuesday, May 11, 2010 6:14 PM
  • How does the call to ChangeApplier.ApplyChanges() look like?

    Are you passing the inMemoryConflictLog to this call?

    Tuesday, May 11, 2010 6:29 PM
  • I think the main reason you're hitting this weirdness is becuase you're keeping keys around for deleted items.  You generally don't want to be having constraint conflicts with tombstones, since that's not particularly meaningful.  Typically, you want to store your tombstones on the side, where you only keep the sync id and not the keys.

    Aaron


    SDE, Microsoft Sync Framework
    Tuesday, May 11, 2010 6:31 PM
    Answerer
  • Thanks for Adrian's hint, the problem is seemingly solved. In my ProcessChangeBatch method I was calling ApplyChanges without passing in the conflict log:
    changeApplier.ApplyChanges(resolutionPolicy,
     sourceChanges,
     (IChangeDataRetriever)changeDataRetriever,
     destVersions,
     mMetaManager.Metadata.GetKnowledge(),
     mMetaManager.Metadata.GetForgottenKnowledge(),
     this,          
     mSessionContext,
     syncCallbacks);

    I changed it to:

    changeApplier.ApplyChanges(resolutionPolicy,
     CollisionConflictResolutionPolicy.ApplicationDefined,
     sourceChanges,
     (IChangeDataRetriever)changeDataRetriever,
     destVersions,
     mMetaManager.Metadata.GetKnowledge(),
     mMetaManager.Metadata.GetForgottenKnowledge(),
     this,
     mConflictLog,
     mSessionContext,
     syncCallbacks);

    And it worked fine!

    And to Aaron: what you said absolutely makes sense - do I need to write my own metadata store in order to save the tombstones separately?

    Tuesday, May 11, 2010 6:50 PM
  • If you're not using the metadata store that we provide, you'd have to find somewhere to store them.   I could conceive if a scenario where you store it with your data, just clearing out all the fields except the sync id.  It totally depends on how you're storing the rest of your metadata (your item versions and replica knowledge).

    Aaron


    SDE, Microsoft Sync Framework
    Tuesday, May 11, 2010 7:51 PM
    Answerer