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.
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
}
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:
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
}
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
}
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
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.
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
}
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