Optimistic Locking

In optimistic locking system no locks are used to prevent collision: any user can read an object into the memory and work on it at any time. However, before the client can save its modifications back to the database, a check should take place verifying that the item did not change since the time of initial read (no collision occurred). If a collision is detected it should be resolved according to your application logic. Typical solutions are:

Let's look at an example realization.

We will use a db4o database containing objects of Pilot class and a separate thread to create a client connection to the database, retrieve and modify objects.

OptimisticThread.cs: Run
01public void Run() 02 { 03 try { 04 IObjectSet result = _db.Get(typeof(Pilot)); 05 while (result.HasNext()){ 06 Pilot pilot = (Pilot)result.Next(); 07 long objVersion = _db.Ext().GetObjectInfo(pilot).GetVersion(); 08 09 /* save object version into _idVersions collection 10 * This will be needed to make sure that the version 11 * originally retrieved is the same in the database 12 * at the time of modification 13 */ 14 long id = _db.Ext().GetID(pilot); 15 _idVersions.Add(id, objVersion); 16 17 Console.WriteLine(Name + "Updating pilot: " + pilot+ " version: "+objVersion); 18 pilot.AddPoints(1); 19 _updateSuccess = false; 20 RandomWait(); 21 if (!_db.Ext().SetSemaphore("LOCK_"+_db.Ext().GetID(pilot), 3000)){ 22 Console.WriteLine("Error. The object is locked"); 23 continue; 24 } 25 _db.Set(pilot); 26 /* The changes should be committed to be 27 * visible to the other clients 28 */ 29 _db.Commit(); 30 _db.Ext().ReleaseSemaphore("LOCK_"+_db.Ext().GetID(pilot)); 31 if (_updateSuccess){ 32 Console.WriteLine(Name + "Updated pilot: " + pilot); 33 } 34 Console.WriteLine(); 35 /* The object version is not valid after commit 36 * - should be removed 37 */ 38 _idVersions.Remove(id); 39 } 40 41 } finally { 42 _db.Close(); 43 } 44 }

OptimisticThread.vb: Run
01Public Sub Run() 02 Try 03 Dim result As IObjectSet = _db.Get(GetType(Pilot)) 04 While result.HasNext 05 Dim pilot As Pilot = CType(result.Next, Pilot) 06 Dim objVersion As Long = _db.Ext.GetObjectInfo(pilot).GetVersion 07 ' save object version into _idVersions collection 08 ' This will be needed to make sure that the version 09 ' originally retrieved is the same in the database 10 ' at the time of modification 11 Dim id As Long = _db.Ext.GetID(pilot) 12 _idVersions.Add(id, objVersion) 13 Console.WriteLine(Name + "Updating pilot: " + pilot.ToString() + " version: " + objVersion.ToString()) 14 pilot.AddPoints(1) 15 _updateSuccess = False 16 RandomWait() 17 If Not _db.Ext.SetSemaphore("LOCK_" + _db.Ext.GetID(pilot).ToString(), 3000) Then 18 Console.WriteLine("Error. The object is locked") 19 ' continue 20 End If 21 _db.Set(pilot) 22 ' The changes should be committed to be 23 ' visible to the other clients 24 _db.Commit() 25 _db.Ext.ReleaseSemaphore("LOCK_" + _db.Ext.GetID(pilot).ToString()) 26 If _updateSuccess Then 27 Console.WriteLine(Name + "Updated pilot: " + pilot.ToString()) 28 End If 29 Console.WriteLine() 30 ' The object version is not valid after commit 31 ' - should be removed 32 _idVersions.Remove(id) 33 End While 34 Finally 35 _db.Close() 36 End Try 37 End Sub

A semaphore is used for locking the object before saving and the lock is released after commit when the changes become visible to the other clients. The semaphore is assigned a name based on object ID to make sure that only the modified object will be locked and the other clients can work with the other objects of the same class simultaneously.

Locking the object for the update only ensures that no changes will be made to the object from the other clients during update. However the object might be already changed since the time when the current thread retrieved it. In order to check this we will need to implement an event handler for the updating event:

OptimisticThread.cs: RegisterCallbacks
1public void RegisterCallbacks() 2 { 3 IEventRegistry registry = EventRegistryFactory.ForObjectContainer(_db); 4 // register an event handler to check collisions on update 5 registry.Updating += new CancellableObjectEventHandler(OnUpdating); 6 }
OptimisticThread.cs: OnUpdating
01private void OnUpdating(object sender, CancellableObjectEventArgs args) 02 { 03 Object obj = args.Object; 04 // retrieve the object version from the database 05 long currentVersion = _db.Ext().GetObjectInfo(obj).GetVersion(); 06 long id = _db.Ext().GetID(obj); 07 // get the version saved at the object retrieval 08 IEnumerator i = _idVersions.GetEnumerator(); 09 10 long initialVersion = (long)_idVersions[id]; 11 if (initialVersion != currentVersion) 12 { 13 Console.WriteLine(Name + "Collision: "); 14 Console.WriteLine(Name + "Stored object: version: " + currentVersion); 15 Console.WriteLine(Name + "New object: " + obj + " version: " + initialVersion); 16 args.Cancel(); 17 } 18 else 19 { 20 _updateSuccess = true; 21 } 22 }

OptimisticThread.vb: RegisterCallbacks
1Public Sub RegisterCallbacks() 2 Dim registry As IEventRegistry = EventRegistryFactory.ForObjectContainer(_db) 3 ' register an event handler to check collisions on update 4 AddHandler registry.Updating, AddressOf OnUpdating 5 End Sub
OptimisticThread.vb: OnUpdating
01Private Sub OnUpdating(ByVal sender As Object, ByVal args As CancellableObjectEventArgs) 02 Dim obj As Object = args.Object 03 ' retrieve the object version from the database 04 Dim currentVersion As Long = _db.Ext.GetObjectInfo(obj).GetVersion 05 Dim id As Long = _db.Ext.GetID(obj) 06 ' get the version saved at the object retrieval 07 Dim i As IEnumerator = _idVersions.GetEnumerator 08 Dim initialVersion As Long = CType(_idVersions(id), Long) 09 If Not (initialVersion = currentVersion) Then 10 Console.WriteLine(Name + "Collision: ") 11 Console.WriteLine(Name + "Stored object: version: " + currentVersion.ToString()) 12 Console.WriteLine(Name + "New object: " + obj.ToString() + " version: " + initialVersion.ToString()) 13 args.Cancel() 14 Else 15 _updateSuccess = True 16 End If 17 End Sub

In the above case the changes are discarded and a message is sent to the user if the object is already modified from another thread. You can replace it with your own strategy of collision handling.

Note: the supplied example has random delays to make the collision happen. You can experiment with the delay values to see different behavior.

OptimisticThread.cs: RandomWait
01private void RandomWait() 02 { 03 try 04 { 05 Random r = new Random(); 06 Thread.Sleep((int)(5000 * r.Next(1))); 07 } 08 catch (Exception e) 09 { 10 Console.WriteLine("Interrupted!"); 11 } 12 }

OptimisticThread.vb: RandomWait
1Private Sub RandomWait() 2 Try 3 Dim r As Random = New Random 4 Dim sleepTime As Integer = 5000 * r.Next(1) 5 Thread.Sleep(sleepTime) 6 Catch e As Exception 7 Console.WriteLine("Interrupted!") 8 End Try 9 End Sub