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 = _container.Get(typeof(Pilot)); 05 while (result.HasNext()){ 06 Pilot pilot = (Pilot)result.Next(); 07 /* We will need to set a lock to make sure that the 08 * object version corresponds to the object retrieved. 09 * (Prevent other client committing changes 10 * at the time between object retrieval and version 11 * retrieval ) 12 */ 13 if (!_container.Ext().SetSemaphore("LOCK_" + _container.Ext().GetID(pilot), 3000)) 14 { 15 Console.WriteLine("Error. The object is locked"); 16 continue; 17 } 18 _container.Ext().Refresh(pilot, Int32.MaxValue); 19 long objVersion = _container.Ext().GetObjectInfo(pilot).GetVersion(); 20 _container.Ext().ReleaseSemaphore("LOCK_" + _container.Ext().GetID(pilot)); 21 /* save object version into _idVersions collection 22 * This will be needed to make sure that the version 23 * originally retrieved is the same in the database 24 * at the time of modification 25 */ 26 long id = _container.Ext().GetID(pilot); 27 _idVersions.Add(id, objVersion); 28 29 Console.WriteLine(Name + "Updating pilot: " + pilot+ " version: "+objVersion); 30 pilot.AddPoints(1); 31 _updateSuccess = false; 32 RandomWait(); 33 if (!_container.Ext().SetSemaphore("LOCK_"+_container.Ext().GetID(pilot), 3000)){ 34 Console.WriteLine("Error. The object is locked"); 35 continue; 36 } 37 _container.Set(pilot); 38 /* The changes should be committed to be 39 * visible to the other clients 40 */ 41 _container.Commit(); 42 _container.Ext().ReleaseSemaphore("LOCK_"+_container.Ext().GetID(pilot)); 43 if (_updateSuccess){ 44 Console.WriteLine(Name + "Updated pilot: " + pilot); 45 } 46 Console.WriteLine(); 47 /* The object version is not valid after commit 48 * - should be removed 49 */ 50 _idVersions.Remove(id); 51 } 52 53 } finally { 54 _container.Close(); 55 } 56 }

OptimisticThread.vb: Run
01Public Sub Run() 02 Try 03 Dim result As IObjectSet = _container.Get(GetType(Pilot)) 04 While result.HasNext 05 Dim pilot As Pilot = CType(result.Next, Pilot) 06 ' We will need to set a lock to make sure that the 07 ' object version corresponds to the object retrieved. 08 ' (Prevent other client committing changes 09 ' at the time between object retrieval and version 10 ' retrieval ) 11 If Not _container.Ext.SetSemaphore("LOCK_" + _container.Ext.GetID(pilot).ToString(), 3000) Then 12 Console.WriteLine("Error. The object is locked") 13 Continue While 14 End If 15 Dim objVersion As Long = _container.Ext.GetObjectInfo(pilot).GetVersion 16 _container.Ext.Refresh(pilot, Int32.MaxValue) 17 _container.Ext.ReleaseSemaphore("LOCK_" + _container.Ext.GetID(pilot).ToString()) 18 ' save object version into _idVersions collection 19 ' This will be needed to make sure that the version 20 ' originally retrieved is the same in the database 21 ' at the time of modification 22 Dim id As Long = _container.Ext.GetID(pilot) 23 _idVersions.Add(id, objVersion) 24 Console.WriteLine(Name + "Updating pilot: " + pilot.ToString() + " version: " + objVersion.ToString()) 25 pilot.AddPoints(1) 26 _updateSuccess = False 27 RandomWait() 28 If Not _container.Ext.SetSemaphore("LOCK_" + _container.Ext.GetID(pilot).ToString(), 3000) Then 29 Console.WriteLine("Error. The object is locked") 30 Continue While 31 End If 32 _container.Set(pilot) 33 ' The changes should be committed to be 34 ' visible to the other clients 35 _container.Commit() 36 _container.Ext.ReleaseSemaphore("LOCK_" + _container.Ext.GetID(pilot).ToString()) 37 If _updateSuccess Then 38 Console.WriteLine(Name + "Updated pilot: " + pilot.ToString()) 39 End If 40 Console.WriteLine() 41 ' The object version is not valid after commit 42 ' - should be removed 43 _idVersions.Remove(id) 44 End While 45 Finally 46 _container.Close() 47 End Try 48 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(_container); 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 = _container.Ext().GetObjectInfo(obj).GetVersion(); 06 long id = _container.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
1Private Sub RegisterCallbacks() 2 Dim registry As IEventRegistry = EventRegistryFactory.ForObjectContainer(_container) 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 = _container.Ext.GetObjectInfo(obj).GetVersion 05 Dim id As Long = _container.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