authorimg

Transaction server with locking

Ileana

5.0

Here is Your Sample Download Sample 📩

Abstract

This work represents how a client-server based system can be used server-side shared objects in a synchronization manner. To demonstrate this, it is considered that server contains X number of shared objects with a common value Y in each object (Value of X and Y configurable). A transaction is defined as an atomic operation on the server-side objects which subtracts an amount K from the current balance of Yi stored in the Xi account and adds K in Yj stored in the Xj account. The system must support any number of such transactions in parallel if they are not conflicting with each other. While implementing this, the proposed solution tries to answer three problems:

  • How client and server will communicate with each other over a TCP/IP network backbone.
  • How to restrict race conditions when two transitions operate on a common object/account.
  • How to ensure there will be no deadlock due to the synchronization.

The subsequent part of the report demonstrates how this system provides an answer to these aforesaid problems.

Class Diagram

There are two parts of the project, one is the server-side and the other one is the client-side. UML based class diagram representation of these two modules is shown in the following. This formal design level representation will help to understand the solution in a better way.

N.B.: TransactionServerContract is a shared entity among client and server and it has been stored in a separate package.

MainServer_Stub: is a Proxy of the MainServer and it has been generated using the RMIC compiler.

Package structure of the project is shown below

Client-Server Communication

To implement a client to server communication over a TCP/IP network the low-level solution is the use of the Socket. A socket is represented by a quadruple server_ip, server_port, client_ip and client_port, which uses either TCP or UDP protocol for the communication. It basically set up a virtual pipe between client and server by which they can exchange data. To make the communication reliable TCP is required as the layer-4 protocol. Using stream-based read and write operation, two entities exchange data.

This type of code is not very suitable for the OOP based program like Java as pure socket-based code reduces the modularity of the code significantly. Due to this reason, Java-based distributed code or client-server code often uses the concept of the RMI (Remote Method Invocation). RMI helps to provide a level of abstraction over socket-based data transfer with the help of the remote method. This implementation also uses RMI to achieve a higher degree of modularity.

In RMI, a server program implements a contract derived from the ‘Remote’ interface. In this implementation, the ‘TransactionServerContrct’ is a specialized version of the ‘Remote’ contract. MainServer class implements the remote contract and it becomes eligible for the RMI services. When the server-side object bind in the RMI registry, the client can invoke the remote method on the server over a TCP/IP network. Whenever the remote method is invoked, it creates a separate thread automatically in the server-side. To manage the client-side socket level operation a Stub (proxy) is generated using the RMIC (RMI compiler). It is necessary to place the byte code of the stub on the client-side.  RMI implementation related important code fragments are given in the following

Remote contract

public interface TransactionServerContract extends Remote{

    public String showBranchTotal()throws RemoteException;

    public String doTransaction(int srcAccIndex, int destAccIndex, int amount)

            throws RemoteException;}

Server Binding

try {// create remote object

            TransactionServerContract obj=new MainServer();

            // create RMI registry at port 1099

            java.rmi.registry.LocateRegistry.createRegistry(1099);

            //get the registry

            Registry registry = LocateRegistry.getRegistry();

            //bind the registry

            Naming.rebind("//127.0.0.1:1099/TransactionObj", obj);

            System.out.println("Server is ready!!");

            MainServer.logger.logging("Server is ready!!");}

Client side remote object lookup

try {//download the stub dynamically

      Registry registry = LocateRegistry.getRegistry("127.0.0.1");

      tc = (TransactionServerContract) Naming.lookup ("//127.0.0.1:1099/TransactionObj");}

Any remote method can be invoked on tc.

This code indicates that the server RMI service present at IP 127.0.0.1 and port 1099. In real life scenario, instead of loopback IP, the public IP of the server must be used.

Synchronization

To avoid the race condition in the account state, a Lock Manager class has been defined. When any server thread tries to perform a transaction, it invokes acquireLock method on a shared (static) instance of the LockManager object. This class holds a set of Accounts for the synchronization purpose. The acquireLock function checks whether the source and the destination account is already in the set or not. If not it adds them in the set and if one of the accounts is already present in the set, it returns ‘False’. Based on the signal received from the lock manager, the transaction code gets executed. If the return value is false, the transaction code takes a sleep interval and retries the acquireLock method again. The lock manager manipulates the set from a synchronization block.

public boolean acquireLock(AccountManager src,AccountManager dst){

        // do locking is MainServer.lockFlag is true

        if(MainServer.lockFlag){

            synchronized(hash_Set){

                // if src and dst accounts are not in the set add them synchronizedly

                if(!hash_Set.contains(src) && !hash_Set.contains(dst)){

                    hash_Set.add(src);

                    hash_Set.add(dst);

                    return true;}

                // accounts are in use

                else{return false;}

        else{return true

Lock manager bypass this checking is run configuration parameter MainServer.lockFlag is set to false.

Deadlock Avoidance

This code ensures that the system will never enter in a deadlock situation. To avoid deadlock, it eliminates the hold-and-wait condition from the scenario. Each transaction will use two shared account objects for the execution of its critical section. The code will ensure that two accounts are not held by any other transaction. If the lock manager signals False, i.e. any one of the intended account objects is in the hold condition, no lock will be given for both the object. In this situation, the blocked transaction will take a sleep any retry this process again. This will ensure that there will be no deadlock in the system. The execution result already confirms this. Once the transaction is done, it will request LockManager to remove the Account instances from its internal Set.

while(true){

// if lock aquire from the lock manager on source and destination account

if(MainServer.lockManager.acquireLock(source, destination)){

        // when lock manager present no synchronized block required

        try{// add some dummy delay to check deadlock avoidence

               Thread.sleep(20);}

        catch(InterruptedException ie){ie.printStackTrace();}

        //deduct amount from source

        source.write(source.read()-amount);

        //add amount in the destination

        destination.write(destination.read()+amount);

        //remove the lock

        MainServer.lockManager.revokeLock(source, destination);

        break;}

N.B. To demonstrate that some threads are waiting due to the unavailability of the resource, some sleep amount is added in the transaction to make it lengthy.

Run Configuration

To run the code, it is required to execute the client and server separately. Both the class takes command line arguments. Details of the execution technique is given below:

Server:

ARSG[0]: 0(default): No lock/ No synchronization 1: with synchronization

 ARGS[1]: account count, default 10

 ARGS[2]: initial account balance, default 10

Client

ARSG[0]: Number of accounts. If not supplied or the wrong value is supplied it will take the default value 10

It is essential to run the server before executing the client.

The client will create (2* account count) the number of threads. Each account make transfers into two other randomly selected account. The transferred amount will also a positive number in between 0-10.

The final balance is 100 and it indicates that the no account becomes inconsistent due to the race condition. All the transactions were executed successfully, so there is no deadlock also. 

References

Hilderink, G., Broenink, J., & Bakkers, A. (1998). A new Java thread model for concurrent programming of real-time systems. Real-time magazine1, 30-35.

Juric, M. B., Kezmah, B., Hericko, M., Rozman, I., & Vezocnik, I. (2004). Java RMI, RMI tunneling and Web services comparison and performance analysis. ACM Sigplan Notices39(5), 58-65.

Naimi, M. (1993). Distributed algorithm for k-entries to critical section based on the directed graphs. ACM SIGOPS Operating Systems Review27(4), 67-75.