Concurrency patterns in DFC – Optimistic locking

I think everybody had faced with errors like DM_SYSOBJECT_E_VERSION_MISMATCH or DM_OBJ_MGR_E_VERSION_MISMATCH but there are only a few people who correctly understand what these errors mean. By default Documentum applies optimistic locking for all objects in repository: every object has i_vstamp attribute and content server increments it’s value automatically upon save:

API> create,c,dm_sysobject
...
0801d920805278c4
API> get,c,l,i_vstamp
...
0
API> save,c,l
...
OK
API> get,c,l,i_vstamp
...
0
API> save,c,l
...
OK
API> get,c,l,i_vstamp
...
1
API> save,c,l
...
OK
API> get,c,l,i_vstamp
...
2

When DFC fetches object from repository it stores object’s i_vstamp into memory and passes memorized i_vstamp to content server when save call is being performed:

....com.documentum.fc.client.DfPersistentObject@1e4905a.saveEx(false,"")
.....com.documentum.fc.client.DfSysObject@1e4905a.doSave(false,"",null)
......RPC: applyForInt("SysObjSave",DfId{0801d920805278c4},DynamicallyTypedData@1e6696c[
        readOnly=false, autoFill=true, fetchTimestamp=0, values=[
          _SESSION_ALIAS_SET_=, _KEEP_LOCK_=F, OBJECT_TYPE=dm_sysobject, IS_NEW_OBJECT=F,
          i_vstamp=3, TYPE_VERSION=0, CACHE_VSTAMP=17409]
        ],true,true)
......RPC: applyForInt ==> 1
.....com.documentum.fc.client.DfSysObject@1e4905a.doSave ==> <void>
....com.documentum.fc.client.DfPersistentObject@1e4905a.saveEx ==> <void>

How does the content server check whether the object has been changed in another transaction (session) or not (i.e. are changes being performed against actual object’s state or not)? It executes following SQL statement when trying to save changes in database:

UPDATE DM_SYSOBJECT_S dm_dbalias_B
  SET R_MODIFY_DATE =
    TO_DATE ('2013/12/28.11.30.38', 'YYYY/MM/DD.HH24.MI.SS'),
  R_MODIFIER = 'dmadmin',
............................
  I_VSTAMP = 4 -- incremented value
WHERE (    dm_dbalias_B.R_OBJECT_ID = '0801d920805278c4'
  AND dm_dbalias_B.I_VSTAMP = 3) -- memorized value

and checks how many rows were updated (database returns such information) – if no rows were updated that means database does not contain row with r_object_id and i_vstamp specified. Sometimes such implementation might be a root cause of wrong result because SQL mentioned above returns “no rows updated” not only when i_vstamp has been changed but also when there is no object in database:

session #1:

API> create,c,dmi_queue_item
...
1b01d92080026578
API> save,c,l
...
OK
API> begintran,c,
...
OK
API> destroy,c,1b01d92080026577
...
OK

session #2:

API> save,c,1b01d92080026577
...

session #1:

API> commit,c,
...
OK

session #2:

[DM_OBJ_MGR_E_VERSION_MISMATCH]error:  "save of object 1b01d92080026577 of type 
  dmi_queue_item failed because of version mismatch: old version was 0"
API> fetch,c,1b01d92080026577
...
[DM_API_E_EXIST]error:  "Document/object specified by 1b01d92080026577 does not exist."
[DM_SESSION_W_FETCH_FAILED]warning:  "Fetch of object with handle 1b01d92080026577 
   and type dmi_queue_item failed."
[DM_OBJ_MGR_FETCH_FAIL]error:  "attempt to fetch object with handle 1b01d92080026577 failed"

Content server also performs some kind of optimisation for sysobjects – before executing SQL update it checks whether i_vstamp specified is actual or not, so concurrent modification of sysobjects can throw either DM_SYSOBJECT_E_VERSION_MISMATCH or DM_OBJ_MGR_E_VERSION_MISMATCH errors:

API> create,c,dm_sysobject
...
0801d92080527a12
API> save,c,l
...
OK
API> connect,dwh_rt,dmadmin,dmadmin
...
s1
API> fetch,s1,0801d92080527a12
...
OK
API> connect,dwh_rt,dmadmin,dmadmin
...
s2
API> fetch,s2,0801d92080527a12
...
OK
API> save,s1,l
...
OK
API> save,s2,l
...
[DM_SYSOBJECT_E_CANT_SAVE]error:  "Cannot save 0801d92080527a12 sysobject."
[DM_SYSOBJECT_E_VERSION_MISMATCH]error:  "save of object failed because of 
   version mismatch: old version was 0"

The most patterns intended to catch VERSION_MISMATCH errors looks like:

int triesCount = 0;
while (true) {
    try {
        object.fetch(null);
        //some object modifications
        triesCount++;
        object.save();
        break;
    } catch (DfException ex) {
        if (triesCount >= 10) {
            object.revert();
            throw ex;
        }
        String messageId = ex.getMessageId();
        if (!"DM_SYSOBJECT_E_VERSION_MISMATCH".equals(messageId)
                && !"DM_OBJ_MGR_E_VERSION_MISMATCH".equals(messageId)) {
            throw ex;
        }
    }
}

Such pattern is good enough when you are dealing with single object but it does not work properly when you want to modify multiple objects: optimistic locking allows to save only one object consistently, if you want to modify multiple objects consistently you should perform changes in explicit transaction, but CS marks transaction invalid when any server error occurred:

API> fetch,s0,0801ffd780051c27
...
OK
API> begintran,s1
...
OK
API> fetch,s1,0801ffd780051c27
...
OK
API> save,s0,l
...
OK
API> save,s1,l
...
[DM_SYSOBJECT_E_CANT_SAVE]error:  "Cannot save 0801ffd780051c27 sysobject."
[DM_SYSOBJECT_E_VERSION_MISMATCH]error:  "save of object failed because of 
  version mismatch: old version was 0"
API> fetch,s1,l
...
OK
API> save,s1,l
...
[DM_SESSION_E_TRANSACTION_ERROR]error:  "Transaction invalid due to errors, 
  please abort transaction."

3 thoughts on “Concurrency patterns in DFC – Optimistic locking

  1. Pingback: Another way to implement “controlled” database lock | Documentum in a (nuts)HELL
  2. Pingback: JavaBlog.fr / Java.lu - Documentum : Concurrency patterns in DFC – Optimistic locking
  3. Pingback: When documentum had started dying | Documentum in a (nuts)HELL

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s