Engineering Tripos Part IIA THIRD YEAR Paper 3F: Software Engineering and Systems Distributed Systems Design Solutions to Examples Paper 3 CORBA 1. (a) A typical IDL file for this application is as follows: typedef long Money; // amount in pence interface StockBroker Money get_price(in string sharecode); bool buy(in string sharecode, in long numshares); bool sell(in string sharecode, in long numshares); Money get_balance(); The Money typedef defines an alias for a long, which just makes the interface definition more readable, and helps avoid confusion. Alternative solutions could define a Money structure storing pounds and pence, or use pounds as a float. buy and sell need to indicate whether or not they were successful, hence in this answer they return a bool. It would be even better to have them raise exceptions (which are available CORBA, but not covered in the context of CORBA in this course). (b) Naively, these extensions could be implemented by adding two new functions to the existing interface. However, this could break existing software that uses the old interface, so the safest way to provide the extra facilities is to create a derived interface. interface ExtendedStockBroker : StockBroker Money get_avgprice(in string sharecode, in long numdays); float get_pe_ratio(in string sharecode); (c) i. The server has to know the account balance for each customer the number of each type of share held In practice, the server should also probably know something about the identity of each customer and a password or similar to allow customers to verify their identity. 1
only one Broker_Factory in the whole system one StockBroker object per client Broker_Factory +get_broker(): StockBroker 1 << instantiate >> StockBroker +get_price(): money +buy(sharecode:string,numshares:int) +sell(sharecode:string,numshares:int) +get_balance(): money 1 Server Client * Trading_Client 1 To begin trading, a Trading_Client first asks the Broker_Factory for a StockBroker object The Broker_Factory creates a StockBroker object and passes a CORBA pointer to this object back to the Trading_Client The Trading_Client can then use this StockBroker object for trading This way, each Trading_Client has its own personal StockBroker object in the server Figure 1: (Question 1(c)) The stock broker application showing the factory ii. CORBA factories are objects which create objects of some other type. In this case, a BrokerFactory could be used to create StockBroker objects (or ExtendedStockBroker objects) for each customer. interface BrokerFactory StockBroker get_broker(in string userid); This IDL allows each user to obtain their own StockBroker from the BrokerFactory. This means that each StockBroker object can store the per customer information for the individual customer that it serves. The get_broker function is interesting because instead of returning a data value (e.g. a long), it returns a pointer to another CORBA object. The client can use this return value to access the newly created StockBroker object and call the methods provided in its interface. A UML class diagram for the system is shown in Figure 1. 2. (a) The idl file has to support downloading of four types of object. Each of the media objects are identified by a code number, stored in the associated hotpoint. interface TourismServer Model get_model(); Image get_image(in long imageno); Movie get_movie(in long movieno); 2
SoundClip get_soundclip(in long clipno); (b) The simplest way to do this is just to supply an argument to the get_model method to identify the zone to be downloaded, which becomes Model get_zone(in long zone); For an alternative (and more general) approach see part (c). (c) An alternative approach to answering the above question and a method which can be used to host zones on other servers is to create a Zone object, one per zone. Each Zone object is then responsible for handling the downloading of its model, images, movies and soundclips. The client could keep going back to the TourismServer for each new Zone pointer, but a better solution would be to embed the Zone CORBA pointers directly into the Model data, so that the client can link to them directly. All the TourismServer then has to do is provide a pointer to the first Zone object. This new idl file would then look like: interface Zone Model get_model(); Image get_image(in long imageno); Movie get_movie(in long movieno); SoundClip get_soundclip(in long clipno); interface TourismServer Zone get_first_zone(); (d) In order to do this load balancing, the pointers contained in the models must be separated from the actual Zone objects that will supply the downloading. The simplest way to do this is to introduce an intermediate object: interface ZoneFactory Zone get_zone(); The Model data will now contain pointers to ZoneFactorys (there is a separate ZoneFactory per zone). The ZoneFactory object can perform the load balancing when a client uses its get_zone method by determining which of the server s computers is the least heavily loaded and returning a pointer to a Zone object created on that computer, which is used as in part (c). (e) In order for users to see each other moving within the world, two things need to happen: Users need to let the server know about their movements in the world. This could be achieved by adding a move_to method to the Zone object 3
interface Zone void move_to(in string userid, in long x, in long y); Users need to find out about the movements of other users. This can be achieved in one of two ways; either the user requests the information from the server (client pull), or the server automatically notifies the user (server push). Using the client pull technique, this could be achieved using the following interface: struct User string userid; long xpos; long ypos; typedef sequence<user> UserList; interface Zone UserList get_users(); Using the server push technique is a little more complicated. In order to do this, each client program has to become a server also (since it is going to provide a method which will be called by the Zone object). This means that the client programs will need an interface: interface ZoneClient void notify(in UserList users); Also the clients will have to provide a pointer to their ZoneClient object when moving within a zone (in place of their userid): interface Zone void move_to(in ZoneClient clientptr, in long x, in long y); To allow users to send text messages to each other requires an additional method on the Zone object: interface Zone void send_message(in string userid, in string message); The users then have to be able to obtain messages either by client pull: 4
interface Zone string get_message(in string userid); or server push: interface ZoneClient void deliver_message(in string message); Database Systems 3. (a) Pessimism and optimism are two extremes of transactional concurrency control policy. The main difference between optimism and pessimism with reference to the ACID properties is isolation. Pessimistic policies exhibit strict isolation using locking systems. This means that no transaction can access an object that has had a write operation performed on it until the transaction that performed the write operation has committed. More optimistic policies allow other transactions to perform operations on objects before a previous invoker has committed. If a potential conflict is subsequently detected then all of the transactions involved are aborted. The difference is a tradeoff between efficiency and the likelihood of an abort. If simultaneous access to an object is unlikely, then optimistic strategies will be more efficient. Alternatively, if simultaneous access is frequent, then pessimistic strict locking will probably be better although note that dead-lock is more likely with pessimistic schemes. The locking mechanism itself carries an overhead, hence optimistic strategies are also preferred for cases where data is read frequently but updated very rarely. (b) i. A pessimistic policy would be more efficient since there can be a large number of potential concurrent accesses associated with a flight booking system (e.g. when a large group of people need to book on the same flight). ii. An optimistic policy would lead to a more efficient implementation since it is likely that updates will be reasonably infrequent but many concurrent reads may occur when several interested parties review a case. iii. An optimistic policy would be more efficient. The assumptions above about criminals hold for patients. Also, it is important to be able to get a potentially life-saving information quickly and optimistic methods do not delay at transaction startup since they never have to wait for locks. 4. (a) When a transaction T x issues a Q.read, then a lock Q.S must be requested. If the lock is granted then it should be drawn as pointing to 5
T x (see T 4 in the given graph). When multiple reads are requested on the same account Q, then Q.S should point to each transaction (see T 2 and T 3 both reading F in the graph). When an exclusive lock Q.X is requested, then Q.S may or may not be already allocated to T x. If it is not, it should be implicitly allocated. In either case, the exclusive lock Q.X should point to Q.S which in turn points to the transaction to which it is allocated (see E.X in the graph). All of the above assumes that requested locks are granted. As explained in the lectures, a read request will block if some other transaction has exclusive access. A write will also block if some other process has shared or exclusive access. This occurs at step 10 when T 5 request a write on account A. In this case, a dotted arrow is inserted between T 5 and A.X to show that T 5 is blocked waiting to aquire A.X. (b) the graphs following steps 12, 20, 25, and 35 are shown below. When a transaction commits or aborts, all of the locks allocated to it are released (note for these purposes the effects of Commit and abort are identical). T 14 12 C.S A.S 15 C.X T T10 14 12 24 C.S A.S 15 C.X T5 T4 T2 T3 5 4 2 8 A.S D.S B.S E.S F.S 12 10 T A.X E.X T4 T2 T3 T T T8 4 2 8 1 18 1 D.S B.S E.S F.S G.S 20 H.S 1 E.X G.X T4 T2 4 2 T3 8 T T8 23 20 18 22 1 D.S B.S E.S F.S G.S 25 H.S 1 25 E.X G.X H.X a) Resource graph after step 12 b) Resource graph after step 20 c) Resource graph after step 25 Figure 2: (Question 4(b)) (c) A deadlock is indicated in a graph if there is a cycle of dependencies. Activity upto step 25 yields simple graphs with no loops. The final graph however shows a number of transactions waiting on locks, and there is a loop involving T 2, T 3, T 8 and T. These transactions are therefore dead-locked. T 4 depends on T, T 12 depends on T 4 and both T 10 and T 11 depend on T 12, so all of these transactions are also stalled by the deadlock. Concurrent Systems 5. (a) This solution fails to provide mutual exclusion. This can be seen by considering a likely sequence of instructions when the program first starts:
4 18 22 T11 2 31 C.S 31 C.X 2 T12 28 34 D.S 34 D.X 32 T3 8 F.S F.X 24 T10 A.S 33 33 A.X T4 H.X 35 G.X 25 25 1 G.S T H.S 23 1 T8 B.S 2 20 E.S T2 30 E.X d) Resource graph after step 35 Figure 3: (Question 4(b and c)) thread1 finds c2 is true thread2 finds c1 is true thread1 sets c1 false thread2 sets c2 false thread1 executes its critical section thread2 executes its critical section (b) The problem in the previous example is that each thread tests the other s so-called claim variable before setting its own. The suggested reordering of the instructions solves this problem and therefore may be expected to provide mutual exclusion (in fact it can be proved that it does). Unfortunately it deadlocks and is therefore also no use. This can be shown by considering the sequence thread1 sets c1 false thread2 sets c2 false thread1 finds c2 is false and remains in the spinlock forever thread2 finds c1 is false and remains in the spinlock forever. A solution is shown below. Important points to note in the solution are: secure the semaphore for each fork before using it, to ensure mutual exclusion each of the fork semaphores is initialised to 1
the semaphore room is initialised to 4 to make sure that at most 4 philosophers can be in the room at any instant of time. Without this, if five philosophers were allowed in at the same, deadlock could occur with each philosopher having one fork and all waiting for the second. (NB. A trivial solution would be to initialise the room semaphore to 1. This would satisfy the requirements given in the question, but is not an efficient solution as most of the forks would be sitting unused.) semaphore sfork[5]; // Initialised in main() semaphore sroom; // Initialised in main() void philosopher(int ID) while(true) think(); secure(sroom); secure(sfork[id % 5]; secure(sfork[(id + 1) % 5]); eat(); release(sfork[id % 5]; release(sfork[(id + 1) % 5]); release(sroom); int main() // Initialise the semaphores sroom = 4; for(int i = 0; i < 4; i++) sfork[i] = 1; // Start the philosophers for(int i=1; i < 5; i++) fork(philosopher(i));. The solution to this problem requires that the wrapper object maintains a count of the number of nested calls and also records the identity of the caller who currently holds the semaphore. Also, a second semaphore is needed to protect the extended classes critical sections. Thus, the new class definition might be class semaphorex public: void enter(); void leave(); private: semaphore sx; semaphore mutex; int count; // the semaphore being extended // to control access to this class // count nested calls 8
thread * caller; // pointer to calling thread Possible implementations of the enter and leave methods are semaphorex::enter() mutex.enter(); if (caller == CallingThread()) ++count; // nested call to enter else mutex.leave(); sx.enter(); mutex.enter(); count=1; caller = CallingThread(); mutex.leave(); // acquire the real semaphore semaphorex::leave() mutex.enter(); if (caller == CallingThread()) --count; // nested call to leave critical section if (count==0) sx.leave(); // final exit caller = NULL; else raise error; // too many leave calls mutex.leave(); Steve Young February 2008