Hand out: Nov. 1, 2022
Deadline: Nov. 10 23:59 (GMT+8)
In this lab, you will learn how to achieve consistency under concurrent scenarios. This lab includes three parts:
If you have questions about this lab, either in programming environment or requirement, please ask TA Yiwen Zhang by email(besssszyw@gmail.com) or Wechat.
Please backup your solution of lab2A first.
First, save the lab2A’s solution:
xxxxxxxxxx$ cd lab-cse $ git commit -a -m “solution for lab2A”Then, pull from the repository:
xxxxxxxxxx$ git pull remote: Counting objects: 43, done. … [new branch] lab2B -> origin/lab2B Already up-to-date`Then, change to lab2B branch:
xxxxxxxxxx$ git checkout lab2BMerge with lab2A, and solve the conflict by yourself:
xxxxxxxxxx$ git merge lab2A Auto-merging fuse.cc CONFLICT (content): Merge conflict in extent_client.cc Automatic merge failed; fix conflicts and then commit the result ......After merge all of the conflicts, you should be able to compile successfully:
xxxxxxxxxx$ makeMake sure there's no error in make.
Note: For this lab, you will not have to worry about server failures or client failures. You also need not be concerned about malicious or buggy applications.
In lab 2B, your aim is to extend your filesystem to a multi-thread file server. And in part 1, it now moves on to the RPC part. You will use the RPC lib(we have provided in rpc/) to achieve RPC communication between extent_server and extent_client.

In principle, you can implement whatever design you like as long as it satisfies the requirements in the "Your Job" section and passes the testers. In practice, you should follow the detailed guidance below.
Using the RPC system:
The RPC library. In this lab, you don't need to care about the implementation of RPC mechanisms, rather you'll use the RPC system to make your single-thread filesystem become a multi-thread filesystem.
A server uses the RPC library by creating an RPC server object (rpcs) listening on a port and registering various RPC handlers (see main() function in demo_server.cc).
A client creates a RPC client object (rpcc), asks for it to be connected to the demo_server's address and port, and invokes RPC calls (see demo_client.cc).
You can learn how to use the RPC system by studying the stat call implementation. Please note it's for illustration purpose only, you won't need to follow the implementation.
make rpcdemo to build the RPC demo.RPC handlers have a standard interface with one to six request arguments and a reply value implemented as a last reference argument. The handler also returns an integer status code; the convention is to return zero for success and to return positive numbers for various errors. If the RPC fails in the RPC library (e.g.timeouts), the RPC client gets a negative return value instead. The various reasons for RPC failures in the RPC library are defined in rpc.h under rpc_const.
The RPC system marshalls objects into a stream of bytes to transmit over the network and unmarshalls them at the other end. Beware: the RPC library does not check that the data in an arriving message have the expected type(s). If a client sends one type and the server is expecting a different type, something bad will happen. You should check that the client's RPC call function sends types that are the same as those expected by the corresponding server handler function.
The RPC library provides marshall/unmarshall methods for standard C++ objects such as std::string, int, and char. You should be able to complete this lab with existing marshall/unmarshall methods.
To grade this part of lab, a overall test script
xxxxxxxxxx$ ./grade.shis provided. Here's a successful grading.
x
$ ./grade.sh Passed A Passed B Passed C Passed D Passed E Passed G (consistency) Lab2 part 1 passed ......You can also run test scripts one by one:
x
$ make clean && make$ ./start.shstarting ./chfs_client /home/stu/cse-lab/chfs1 > chfs_client1.log 2>&1 &$ perl ./test-lab2b-part1-a.pl chfs1create file-yyuvjztagkprvmxjnzrbczmvmfhtyxhwloulhggy-18674-0create file-hcmaxnljdgbpirprwtuxobeforippbndpjtcxywf-18674-1...Passed all tests!$ ./test-lab2b-part1-g chfs1 chfs2Create then read: OK...test-lab2b-part1-g: Passed all tests.Note test scripts a-f require only one mountpoint as input argument(use chfs1 or chfs2), test script g requires two mountpoint as input argument(use chfs1 and chfs2).
In part 2, you will implement a locking service to coordinate updates to the file system structures.

We provide you with a skeleton RPC-based lock server, a lock client interface, a sample application that uses the lock client interface, and a tester. Now compile and start up the lock server, giving it a port number on which to listen to RPC requests. You'll need to choose a port number that other programs aren't using. For example:
xxxxxxxxxx$ make $./lock_server 3772Now open a second terminal on the same machine and run lock_demo, giving it the port number on which the server is listening:
x
$ ./lock_demo 3772stat request from clt 1386312245stat returned 0$lock_demo asks the server for the number of times a particular lock has been acquired, using the stat RPC that we have provided. In the skeleton code, this will always return 0. You can use it as an example of how to add RPCs. You don't need to fix stat to report the actual number of acquisitions of the given lock in this lab, but you may if you wish.
The lock client skeleton does not do anything yet for the acquire and release operations; similarly, the lock server does not implement lock granting or releasing. Your job is to implement this functionality in the server, and to arrange for the client to send RPCs to the server.
Your job is to implement a correct lock server assuming a perfect underlying network. Correctness means obeying this invariant: at any point in time, there is at most one client holding a lock with a given identifier.
In principle, you can implement whatever design you like as long as it satisfies the requirements in the "Your Job" section and passes the testers. In practice, you should follow the detailed guidance below.
Using the RPC system:
Implementing the lock server:
The lock server can manage many distinct locks. Each lock is identified by an integer of type lock_protocol::lockid_t. The set of locks is open-ended: if a client asks for a lock that the server has never seen before, the server should create the lock and grant it to the client. When multiple clients request the same lock, the lock server must grant the lock to one client at a time.
You will need to modify the lock server skeleton implementation in files lock_server.{cc,h} to accept acquire/release RPCs from the lock client, and to keep track of the state of the locks. Here is our suggested implementation plan.
On the server, a lock can be in one of two states:
The RPC handler for acquire should first check if the lock is locked, and if so, the handler should block until the lock is free. When the lock is free, acquire changes its state to locked, then returns to the client, which indicates that the client now has the lock. The valuer returned by acquire doesn't matter. The handler for release should change the lock state to free, and notify any threads that are waiting for the lock. Consider using the C++ STL (Standard Template Library) std::map class to hold the table of lock states.
Implementing the lock client:
The class lock_client is a client-side interface to the lock server (found in files lock_client.{cc,h}). The interface provides acquire() and release() functions that should send and receive RPCs. Multiple threads in the client program can use the same lock_client object and request the same lock. See lock_demo.cc for an example of how an application uses the interface. lock_client::acquire must not return until it has acquired the requested lock.
Handling multi-thread concurrency:
We will use the program lock_tester to check the correctness invariant, i.e. whether the server grants each lock just once at any given time, under a variety of conditions. You run lock_tester with the same arguments as lock_demo. A successful run of lock_tester (with a correct lock server) will look like this:
xxxxxxxxxx$ ./lock_tester 3772simple lock clientacquire a release a acquire a release aacquire a acquire b release b release atest2: client 0 acquire a release atest2: client 2 acquire a release a..../lock_tester: passed all tests successfullyIf your lock server isn't correct, lock_tester will print an error message. For example, if lock_tester complains "error: server granted XXX twice", the problem is probably that lock_tester sent two simultaneous requests for the same lock, and the server granted both requests. A correct server would have granted the lock to just one client, waited for a release, and only then sent granted the lock to the second client.
Now in part 3, you will use the lock service to coordinate chfs clients.
You are going to ensure the atomicity of file system operations when there are multiple chfs_client processes sharing a file system. Your current implementation does not handle concurrent operations correctly. For example, your chfs_client's create method probably reads the directory's contents from the extent server, makes some changes, and stores the new contents back to the extent server. Suppose two clients issue simultaneous CREATEs for different file names in the same directory via different chfs_client processes. Both chfs_client processes might fetch the old directory contents at the same time and each might insert the newly created file for its client and write back the new directory contents. Only one of the files would be present in the directory in the end. The correct answer, however, is for both files to exist. This is one of many potential races. Others exist: concurrent CREATE and UNLINK, concurrent MKDIR and UNLINK, concurrent WRITEs, etc.
Your job is to add locking to chfs_client to ensure the correctness of concurrent operations.
You should eliminate ChFS races by having chfs_client use your lock server's locks. For example, a chfs_client should acquire a lock on the directory before starting a CREATE, and only release the lock after finishing the write of the new information back to the extent server. If there are concurrent operations, the locks force one of the two operations to delay until the other one has completed. All chfs_client must acquire locks from the same lock server.
What to lock?
Things to watch out for:
The testers for this part of the lab are test-lab2-part3-a and test-lab2-part3-b, source in test-lab2-part3-a.c and test-lab2-part3-b.c. The testers take two directories as arguments, issue concurrent operations in the two directories, and check that the results are consistent with the operations executing in some sequential order. Here's a successful execution of the testers:
xxxxxxxxxx$ ./start.sh$ ./test-lab2-part3-a ./chfs1 ./chfs2Create then read: OKUnlink: OKAppend: OKReaddir: OKMany sequential creates: OKWrite 20000 bytes: OKConcurrent creates: OKConcurrent creates of the same file: OKConcurrent create/delete: OKConcurrent creates, same file, same server: OKtest-lab2-part2-b: Passed all tests.$ ./stop.sh$ ./start.sh$ ./test-lab2-part3-b ./chfs1 ./chfs2Create/delete in separate directories: tests completed OK$ ./stop.shIf you try this before you add locking, it should fail at "Concurrent creates" test in test-lab2-part3-a. If it fails before "Concurrent creates", your code may have bugs despite having passed previous testers; you should fix them before adding locks.
Finally, after you've implemented all these features, run the grading script:
$ ./grade.shPassed part1 A Passed part1 B Passed part1 C Passed part1 D Passed part1 E Passed part1 G (consistency) lab2b part 1 passed Passed part2 Concurrent creates: OK Concurrent creates of the same file: OK Concurrent create/delete: OK Concurrent creates, same file, same server: OK Concurrent writes to different parts of same file: OKPassed part3 ACreate/delete in separate directories: tests completed OKPassed part3 B Score: 100/100After all above done:
x$ make handin
That should produce a file called lab2b.tgz. Change the file name to your student id:
x$ mv lab.tgz lab2b_[your student id].tgz
Then upload lab2b_[your student id].tgz file to Canvas before the deadline. Make sure your implementation has passed all the tests before final submit. (If you must re-submit a new version, add explicit version number such as "V2" to indicate).
You will receive full credit if your software passes the same tests we gave you when we run your software on our machines.