In
this project, you are to implement a simple Resource Manager (RM) that supports
concurrent transactions with the ACID properties. Specifically, the RM stores
data about flights, rental cars, hotel rooms, and customers. Multiple clients
access this RM concurrently to query and update the data through a
transactional interface. It is the responsibility of the RM to ensure that
these concurrent transactions are executed correctly, i.e., in accordance with
the ACID requirements.
Conceptually, the RM stores the following tables:
FLIGHTS(String flightNum, int price, int numSeats, numAvail)
HOTELS(String location, int price, int numRooms, int numAvail)
CARS(String location, int price, int numCars, int numAvail)
CUSTOMERS(String custName)
RESERVATIONS(String custName, int resvType, String resvKey)
To keep things simple, we'll make some assumptions:
flightNum is a primary key for FLIGHTS.location is a primary key for HOTELS.location is a primary key for CARS.custName is a primary key for CUSTOMERS.RESERVATIONS table contains an entry corresponding to each reservation made by a customer for a flight, car, or hotel, as follows: resvType indicates the type of reservation (1 for a flight, 2 for a hotel room, and 3 for a car), and resvKey is the primary key of the corresponding reserved item.FLIGHTS table, numAvail is the number of seats available to be booked on a given flight. For a given flightNum, one of the database consistency conditions is that the total number of reservations for a flight (in the RESERVATIONS table) plus the number of available seats must add up to the total number of seats in the flight. Similar conditions hold for the CARS and HOTELS tables. We provide the standard interface for the ResourceManager
(ResourceManager API). Among other things, the interface allows rows to be added, deleted, and modified for each of the tables the RM stores. Your
job is to write the implementation for the interface we provide.
The RM supports transactions. A transaction calls the start() method of ResourceManager to get a unique transaction id. All subsequent calls to the RM include the transaction id. Finally, the transaction calls commit() or abort(). A typical client transaction might look something like the following: (Of course in reality you should be checking for return values and catch exceptions). Also look at Client.java for another simple example.
int xid = rm.start();
// If it's cheap enough and there're seats left
if (rm.queryFlightPrice(xid, "223") < 300 && rm.queryFlight(xid, "223") > 0) {
rm.reserveFlight(xid, "John", "223");
}
rm.commit(xid);
Concurrency control is done through two-phase locking. In particular, when a client requests to query or update information, your RM implementation is responsible for locking things appropriately, and releasing all locks only when a transaction commits or aborts. We have provided a lock manager to help with this task. The lock manager supports the following API (this is pseudo-code, the actual LockManager API is here).
- lock(xid, thingBeingLocked, read|write) throws DeadlockException
- unlockAll(xid)
As before, xid is the unique transaction identifier. The lock manager handles deadlocks by a timeout mechanism (currently set at 10 sec). A failed (deadlocked) lock operation throws a DeadlockException. The lock manager also handles lock conversion (also known as lock upgrade). Lock upgrade happens when a transaction that has a read lock on an item needs a write lock on the same item.
lock(xid, foo, read); // read foo
lock(xid, foo, write); // write foo
Other transactions may have read locks on, so deadlock is possible when you upgrade locks.
Your code is expected to work on any Java-compatible environment with a Java 2 SDK. You are free to work on any machine, including your own PC, where you can find a Java environment.
The supplied codebase (here) has two packages: lockmgr and transaction. The lockmgr package implements the class LockManager (API). You won't need to modify the contents of this package. The transaction package has some basic classes to get you started with your implementation. It contains the interface ResourceManager (API). Note that you are NOT to modify this file. Your implementation of the RM goes into the class called ResourceManagerImpl (plus additional new classes that you define as necessary), which implements the ResourceManager interface. The current ResourceManagerImpl.java contains a toy implementation which you will replace.
The transaction directory also contains a simple client in Client.java. During your implementation, you may want to write more sophisticated clients to test against your RM, but you're not required to turn these in. In fact, for grading, we will link your code with our own version of Client.java and see if you RM handles all cases correctly.
The client communicates with the RM via Java RMI (Remote Method Invocation). You can find a tutorial on RMI here. However, our toy ResourceManagerImpl and Client already have all the necessary RMI code for this part. The only thing you have to do is to modify the first line in transaction/Makefile to give yourself a unique port number for the RMI registry, so that you don't conflict with other CS223 students who happen to work on the same machine as you (see later).
You will turn in all your source files plus a README file for submission. Grading will be based largely on correct functionality. The readme file should meet the following requirements: (1) Briefly explain your design and implementation. It should include enough details for the grading. (2) Write the file in about 2-3 pages. In addition, you should make sure that your code should work following the instructions. Don't send me your own instructions to test the code.
We have provided test cases to help you design and test your program. Use the following steps to get started.
~/cs223 cd ~/cs223 gunzip project1.tar.gz tar xvf project1.tar cd project/lockmgr make ./submissions cd ../transaction cd ../test.part1 cp -r ../../submissions/* ../transaction rm -r ../transaction/Client.java cp -f Client.java ../transaction/ cd ../transaction make clean make server make client rmiregistry -J-classpath -J.. 2100 & cd ../test.part1 setenv CLASSPATH .:gnujaxp.jar /usr/bin/javac RunTests.java java -DrmiPort=2100 RunTests MASTER.xml Make sure the "java" command and the "rm" command in RunTests.java and Client.java are properly set
based on your shell environment. The results will be put in ./results/grades.txt. If you want, you
can modify the file project/test.part1/MASTER.xml to change the
scripts you want to test. You are STRONGLY suggested to run the scripts ONE BY
ONE.
Structure: (1) RunTest.java parses the file MASTER.xml. For each line, it activates "Client.java" by passing the script name under the "scripts" directory. (2) Client.java starts the necessary RMI modules. Then it reads and parses the script file, and interpret each line to take the corresponding action.
Here is a suggested approach to implementing the required
functionality in an iterative manner (note that the steps below may not all
require the same programming effort; some steps are harder than others):
The RESERVATIONS table doesn't have a primary key, but it's mostly looked up by the custName; so one possible implementation is to combine it with the CUSTOMERS table. That is, have a hashtable indexed by custName, with each hashtable entry containing a list of (resvType, resvKey) pairs.
Note that in our simple data model, you can have only one price per location, so the net effect of
addCars(T1, "Irvine", 4, $52);
addCars(T2, "Irvine", 7, $54);
should leave 11 cars at $54, not 7 cars at
$54 and 4 cars at $52.
In the current system, the only failure to handle is an abort. Since the memory image is lost when the process terminates, there doesn't need to be any recover() method at this stage.
Testing
We will test the Simple RM using multiple clients and
a single resource manager. The Technical Interface methods in ResourceManager are defined to make it easier to test for
faults. The shutDown()
method implies that the RM should shut down gracefully. In this case, that
means waiting for all running transactions to terminate, and cleaning up its
files, so that the next time it starts up, it does not attempt to recover its
state. The dieNow()
method makes the RM call System.exit() immediately, to simulate a system
failure, leaving the database in its current state. When the RM next starts up,
it will have to recover to a consistent state. Instead of killing the RM
immediately, the dieBeforePointerSwitch() and dieAfterPointerSwitch() methods set flags so that the RM
will call System.exit() in the middle of the next commit
operation. See the API for details.
Make sure:
Steps to submit: