[ This content is protected and may not be shared, uploaded, or distributed. ]
(Please also check out PA3 FAQ.)
Lab 7 has two parts:
Part A (lab7a) - introduction to mutex (useful for PA3):
This part has no coding.
Please do the following:
Make sure you read the code and understand how to use a mutex when you do the next part of this lab.
If when you are running the code in "race.cpp" and you get 50000 every time, try adding a call to usleep(1) immeidately after balance_++; on line 19,
then recompile and run the above commands again to see if you get a different result (the program would run a little slower because of all the sleeping).
Part B (lab7b) - getting ready to add a console thread (useful for PA3):
The rest of this lab is web server-only.
Please continue to use "lab4data" as the root directory of your server for the rest of this lab
and make sure that you have downloaded and unpacked lab4data.tar.gz into the right place.
In Part C of this lab, you will be to adding a console thread
to implement an interactive commandline user interface to your multithreaded web server.
We will get there is two steps. The first step is to move the printouts into a log file.
Starting with Part B of Lab 6, you were required to print three types of messages to cout:
[#]\tClient connected from IP:PORT and requesting URI\n
[#]\tSent ??? KB to IP:PORT\n
[#]\tConnection closed with client at IP:PORT\n
If you continue to print all that to cout, it will mess up your commandline user interface.
For this part of the lab, all messages must go into a log file and not to cout.
Also, each line of log message must be timestamped and timestamps must appear in sequential order inside the log file.
The commandline syntax for this part of the lab is:
lab7b PORT LOGFILE
where LOGFILE is the name of the log file.
Please incorporate the code in "logging.cpp" in Part B of Lab 5 to do logging.
Please also incorporate the code in "progress.cpp" in Part D of Lab 5 to timestamp each log messages
(get_timestamp_now() and timestamp_diff_in_seconds() are part of "my_timestamp.cpp" which
you used in Part D of Lab 6). The above 3 types of log messages show then look like:
[TIMESTAMP] [#]\tClient connected from IP:PORT and requesting URI\n
[TIMESTAMP] [#]\tSent ??? KB to IP:PORT\n
[TIMESTAMP] [#]\tConnection closed with client at IP:PORT\n
where TIMESTAMP is obtained by calling get_timestamp_now() in "my_timestamp.cpp".
Please first do the following:
- Change directory into the "lab7" directory mentioned above.
- Copy "lab6d.cpp" (from Part D of Lab 6) into "lab7b.cpp" and copy the Makefile as well.
- Modify the Makefile so that when you type "make lab7b" in the commandline, the executable lab7b will be created.
For this lab, your server will only send response body at the rate of 1 KB/sec (as before, 1 KB is 1,024 bytes and not 1,000 bytes).
Therefore, if you start with Part D of Lab 6, you must assume that SPEED is a constant and always equal to 1.
When you are done with implementing lab7b, please do the following in a Terminal window:
uname -a
cat /etc/os-release
make clean
make lab7b
./lab7b 12345 lab7b.logfile
Start 3 more Terminal windows and cd into the same "lab7" directory.
In the 2nd Terminal window, type:
tail -f lab7b.logfile
This will monitor the "tail" of "lab7b.logfile" so that as soon as lines are appended to "lab7b.logfile",
it will get displayed.
In the 3rd Terminal window, type:
wget -O x http://localhost:12345/textbooks-3-small.jpg
As quickly as you can, type the following in the 4th Terminal window:
wget -O y http://localhost:12345/textbooks-2-small.jpg
Make sure that wget is getting within ±10% of 1 KB/s in both runs.
If not, please fix your code and try again.
Wait for download to complete and then do:
diff x lab4data/textbooks-3-small.jpg
diff y lab4data/textbooks-2-small.jpg
to make sure that your downloads were successful.
Press <Ctrl+c> in the first window to kill the server and
press <Ctrl+c> in the 2nd window to kill the tail command.
Your "lab7b.logfile" should look something like the following (of course, actual timestamps and values will be different and you just
need to make sure that the printout makes sense):
[Fri Feb 4 2021 14:33:22.754711] [1] Client connected from 127.0.0.1:36170 and requesting /textbooks-3-small.jpg
[Fri Feb 4 2021 14:33:22.756560] [1] Sent 1 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:23.756740] [1] Sent 2 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:24.434921] [2] Client connected from 127.0.0.1:36172 and requesting /textbooks-2-small.jpg
[Fri Feb 4 2021 14:33:24.452492] [2] Sent 1 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:24.757157] [1] Sent 3 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:25.436550] [2] Sent 2 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:25.756917] [1] Sent 4 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:26.436631] [2] Sent 3 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:26.756707] [1] Sent 5 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:27.437123] [2] Sent 4 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:27.756536] [1] Sent 6 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:28.476145] [2] Sent 5 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:28.756533] [1] Sent 7 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:29.437126] [2] Sent 6 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:29.757019] [1] Sent 8 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:30.436724] [2] Sent 7 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:30.756682] [1] Sent 9 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:31.438446] [2] Sent 8 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:31.756509] [1] Sent 10 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:32.436299] [2] Sent 9 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:32.757543] [1] Sent 11 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:33.439905] [2] Sent 10 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:33.756842] [1] Sent 12 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:34.436332] [2] Sent 11 KB to 127.0.0.1:36172
[Fri Feb 4 2021 14:33:34.757074] [1] Sent 13 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:35.442655] [2] Connection closed with client at 127.0.0.1:36172
[Fri Feb 4 2021 14:33:35.756493] [1] Sent 14 KB to 127.0.0.1:36170
[Fri Feb 4 2021 14:33:36.756614] [1] Connection closed with client at 127.0.0.1:36170
In the above sample, it's not showing HTTP request and response headers. Although it's optional for this lab, it's a good idea to also log that information
into the LOGFILE since PA3 requires it.
Alternatively, you can also do everything inside one Terminal and run tmux.
You can split the screen vertically into 4 panes.
Part C (lab7c) - console thread and connection management (useful for PA3):
Now that the preparation work is done, we can proceed to implement a console thread for your multithreaded web server.
Let's also make the logfile more compact by eliminating the "Sent ??? KB to" log entries (and only keep the "connected from" and "connection closed" entries).
Please also print the following line into the log file when your server starts successfully:
[TIMESTAMP] Server IP:PORT started\n
where IP:PORT is the string returned by calling get_ip_and_port_for_server(listen_socket_fd, 1)
where listen_socket_fd is the return value of create_listening_socket().
When your server is about to die "normally", please print the following line into the log file:
[TIMESTAMP] Server IP:PORT stopped\n
The problem with Part B of this lab is that you have to press
<Ctrl+c> to kill the server. For this part of the lab, please create a
console thread to implement an interactive commandline user interface.
Your console thread must also coordinate (using a mutex) with the connection-handling threads so that you can gracefully shutdown your server
when it has no active connections to clients.
Please only use one mutex in this lab (and make it a global variable, or equivalent, to make your life easier).
Please first do the following:
Just like in Part B, your server will only send response body at the rate of 1 KB/sec.
The commandline syntax for this part of the lab is:
lab7c PORT LOGFILE
Your lab7c must use "> "
(i.e., a "greater than" symbol followed by a space character but no '\n' afterwards)
as a command prompt to tell the user that your console is expecting the user to enter a line of command.
You need to handle three commands in this part of the lab:
| status |
If all connections are inactive (i.e., closed), you must print the following to cout:
No active connections.\n
Otherwise, you must print the following to cout:
The following connections are active:
followed by a list of connection numbers (in any order), then '\n'.
|
| quit |
If there are active connections, you must print the following to cout:
Cannot quit, the following connections are active:
followed by a list of connection numbers (in any order), then '\n'.
If all connections are inactive, you must do the following:
- shutdown and close the listening socket and set the listening socket (global variable) to (-1) to indicate that the server is shutting down
- console-thread must then self-terminate
- since the listening socket has been closed, your main thread's my_accept() should return with an error condition,
and you would break out of the infinite loop there
- once you are out of the infinite loop, your main thread must join with the console thread
- finally, your main thread must join with all the connection-handling threads and self-terminate
- if your main thread does not join with all these other threads, you shouldn't be surprised that your program will crash
in the destructor of a thread (see Part A of Lab 6 for examples)
|
| help |
You must print the following to cout:
Available commands are:\n
\thelp\n
\tstatus\n
\tquit\n
|
If the user types anything else, you should print the same message for the "help"
command and print the command prompt again to ask the user for another command.
If the user simply press <ENTER>, you should simply give the command prompt again.
How would the console thread know whether a connection is active or not?
We need some shared data structures to manage a list of connections and for each connection, we
need to know if a connection is active or inactive.
This is where you would need to use a mutex to ensure that threads are not stepping on each other's toes.
I would suggest that you use a Connection object.
Here's my recommendation of a simple Connection object:
class Connection {
public:
int conn_number; /* -1 means that the connection is not initialized properly */
int socket_fd; /* -1 means that the connection is inactive/closed */
shared_ptr<thread> thread_ptr; /* shared pointer to a thread object */
Connection() : conn_number(-1), socket_fd(-1), thread_ptr(NULL) { }
Connection(int c, int s, shared_ptr<thread> p) { conn_number = c; socket_fd = s; thread_ptr = p; }
};
I know the above is bad form! It's there to illustrate the basic idea. Feel free to define your Connection object any way you'd like.
As shown in Part A of Lab 6, when you need to share a dynamically allocated object,
you can either use a regular pointer or a shared pointer.
With a regular pointer, you need to figure out when to delete it.
With a shared pointer, you don't need to figure out when to delete it; therefore, it's a little easier to manage.
The examples I will use will mostly be using shared pointers because of its convenience.
I would strongly recommend that you use shared pointers as well to reduce hard-to-debug crashes.
Of course, you need to understand what shared pointers are, and if you are not sure about them, please ask the instructor questions
(please note that although the course producers are quite good at networking and sockets programming, they are not necessarily experts in C++).
To keep track of all the connections, we can put all the connections in a list.
In my examples, I will use a vector to store all the connections.
You can use some other list data structure (e.g., a map) if you'd prefer.
To share a list of connections, I will use a vector of shared pointers to Connection objects:
vector<shared_ptr<Connection>> connection_list;
It is very important to remember that whenver one of your thread accesses this list, it must
only do so when when it has the mutex locked. Otherwise, you may end up with weird crashes
that can be very difficult to debug.
What about sharing members of a Connection object on this list?
Do you have to have the mutex locked in order to access them? The general answer is "maybe".
What you need to do is to analyze, for each members in the Connection class, to determine if it's
safe to access it without having the mutex locked. Fortunately, for this lab, all members of the Connection class
are primitive objects (two integers and a shared pointer). If you have two threads trying to change
the value of a member simultaneously, then you need to lock the mutex. If only one thread can chnage
the value of a member and other threads would only read its value, then maybe its okay that you don't need to synchronize these threads
and you have to perform further analysis. If you don't synchronize them, you may end up with a race condition and you need to determine
if such a race condition can cause your program to produce incorrect results, or if the race condition ultimately is not harmful.
You should think about this carefully and if you are not sure if what you are doing is correct or not, please feel to ask the instructor questions.
IMPORTANT: It's very important to understand that a socket file descriptor is safe to share among multiple threads (without locking)!
When you make a system call and pass the socket file descriptor into the operating system, the operating system guarantees
that sharing is done properly. In later labs and programming assignments, we will see that you can have multiple threads reading from and writing to
the same socket simultaneously and that's perfectly fine since the OS is making the guarantee! Therefore, when you read from a socekt and write to a socket,
you need to make sure that you do not have a mutex locked because you actually want and need the parallellism to be able to
read and write a socket concurrently!
Now we need to create a Connection object and a connection-handling thread.
Which should we create first? Remember, in C++, when you create a thread, it is possible
that the first procedure of the new thread can start executing immediately. This is where
synchronization is needed (and whenever you think about synchronization, you think about mutexes).
As it turns out, you can create a Connection object first
and then create a thread, or you can create a thread first and then create a Connection object.
As long as you perform synchronization properly, either way is fine. In our example below, we
will create a Connection object first and pass it to the first procedure of the connection-handling thread
(i.e., talk_to_client()). Therefore, the function prototype of talk_to_client() would look like:
void talk_to_client(shared_ptr<Connection> conn_ptr);
When a connection-handling thread starts running in talk_to_client(), the Connection
object is incomplete! Its thread_ptr would be a null-pointer! To provide synchronization,
we can do the following in the main thread (m is a global mutex variable, and remember, you should
use only one mutex in this lab):
newsockfd = my_accept()
m.lock();
shared_ptr<Connection> conn_ptr = make_shared<Connection>(Connection(next_conn_number++, newsockfd, NULL));
shared_ptr<thread> thr_ptr = make_shared<thread>(thread(talk_to_client, conn_ptr));
conn_ptr->thread_ptr = thr_ptr;
connection_list.push_back(conn_ptr);
m.unlock();
At the beginning of talk_to_client(), we can do:
void talk_to_client(shared_ptr<Connection> conn_ptr)
{
/* Connection is incomplete */
m.lock();
m.unlock();
/* Connection is now complete */
... /* old stuff -- loop for persistent connection */
}
This way, when the connection-handling thread starts to run, if the main thread is not done
setting up the connection, connection-handling thread will get stuck in m.lock().
When m.lock() returns, the connection-handling thread knows that the connection setup
is complete and that the Connection object has been added to the list of connections (i.e., connection_list).
When a connection has been shutdown and closed, we need to let other threads know that this connection is dead.
Instead of using another member variable to keep the status of a connection,
we can simply set the socket_fd of the corresponding Connection object to (-1) to
indicate that this connection is dead AND that this thread is either terminated or is about to self-terminate.
It's very important that you don't pass newsockfd as a separate argument into talk_to_client()!
Inside talk_to_client(), you should always use conn_ptr->socket_fd when you need to read from the socket or write to the socket.
In later labs, we will use conn_ptr->socket_fd for multiple threads to interact with each other (or to "signal" each other).
Since these data members are shared among threads, it's probably a good idea to lock the mutex when you are changing them.
Therefore, talk_to_client() would look like the following:
void talk_to_client(shared_ptr<Connection> conn_ptr)
{
/* Connection is incomplete */
m.lock();
m.unlock();
/* Connection is now complete */
... /* old stuff -- loop for persistent connection */
shutdown(conn_ptr->socket_fd, SHUT_RDWR);
close(conn_ptr->socket_fd);
m.lock();
conn_ptr->socket_fd = (-1);
m.unlock();
}
To work with the connection-handling threads, the console thread should do the following.
- If the user types "status", the console thread should lock the mutex, walk down connection_list
and list all connections whose socket_fd is ≥ 0, then unlock the mutex.
- If the user types "quit", the console thread should lock the mutex, walk down connection_list
and list all connections whose socket_fd is ≥ 0, then unlock the mutex.
If every connection's socket_fd is < 0, then the console thread should self-terminate.
Right before the console thread self-terminates, it must lock the mutex, close the listening socket (this will force my_accept()
to return with an error code in the main thread), set the listening socket to be (-1) (i.e., an invalid socket file descriptor
so that calling my_accept() in the main thread will fail immediately), and unlock the mutex.
For this lab, we only have one mutex. The rule for locking the mutex is pretty simple.
In order to look at or change ANY shared objects or variables (e.g., the connection list, any part of a Connection object, listening socket),
you must lock the mutex (i.e., get into the "Serialization Box" mentioned in multithreading, part 3)).
The only exception are socket file descriptors and a shared pointer to a "read-only" object.
If you do something that potentially can take a very long time (i.e., seconds), you should probably do it with the mutex unlocked.
Otherwise, you will block out other threads from getting into the "Serialization Box" to access shared objects or variables
and this behavior maybe unacceptable as far as software usability is concerned.
When you are writing to the log file, you should remember that log file is shared among threads that perform logging.
Therefore, you should have the mutex locked when you write to the log file.
Also, multiple threads may be reading the clock to create timestamps for log messages, if you want to make sure
that the timestamps in the log file always appear in a non-decreasing order, reading the clock to create a timestamp
and writing to the log file should be done in one atomic operation, i.e., log mutex, perform these two operations, then unlock mutex.
The executable of your web server for this lab exercise must be named lab7c.
Please also create a Makefile so that when the user types "make lab7c",
this executable will be generated.
When you are done with implementing lab7c, please do the following:
- Change directory into the "lab7" directory mentioned above.
- Copy your source code and Makefile into this directory.
- Since this lab is originally based on Part A of Lab 4, we will use the same test data.
Please download lab4data.tar.gz into that directory and type:
tar xvf lab4data.tar.gz
This should create a subdirectory called "lab4data" with a bunch of files in it.
- Type "script lab7c.script" to start a transcript. Then type:
uname -a
cat /etc/os-release
make clean
make lab7c
./lab7c 12345 lab7c.logfile
- Run two more Terminal windows and cd into the same "lab7" directory.
- In the second Terminal window, type:
wget -O x http://localhost:12345/textbooks-3-small.jpg
- In the third Terminal window, type:
wget -O y http://localhost:12345/textbooks-1-small.jpg
- Make sure wget is seeing around 1KB/sec.
- In the first window, type "status" and make sure that it sees two active connections.
- In the first window, type "quit" and make sure that it says that it cannot quit because connections 1 and 2 are both active.
- Wait for one of the downloads to complete then type "status" to make sure that only one connection is active.
- Type "quit" and make sure that it says that it cannot quit because one connection is still active.
- Wait for the 2nd download to finish then type "status" to make sure that there are no active connections.
- Type "help" to see that the appropriate message gets printed.
- Press <ENTER> a few times to see that new prompts are printed.
- Repeat the same commands in the 2nd and 3rd Terminal windows and type "quit" in the first window
and the console should refuse to die.
- Keep typing "status" once every five seconds to wait for both downloads to finish.
- When both downloads are completed, type "quit" to see lab7c self-terminates.
- Type "exit" to close the transcript.
- Type:
cat lab7c.logfile
and make sure that you can see the timestampped "server started" and "server stopped" log messages.
Since "textbooks-3-small.jpg" is about 14 KB long, download it should take about 14 seconds.
If you don't start downloading "textbooks-1-small.jpg" soon after you started the first download,
there may not be too much parallelism. In that case, you should re-run the entire steps above.
Alternatively, you can also do everything inside one Terminal and run tmux.
You can split the screen vertically into 3 panes.
All pseudo-code is incomplete and error checking is often left out in pseudo-code.
Since some details are left out, depending on you how write your code, you may create race conditions
and you may need to fix your code so that your program won't freeze or crash.
Feel free to send your questions (and not your code) to the instructor.
Pseudo-code for lab7c:
Code in red are critical section codes.
Console Thread
do forever
write prompt
cmd = read command from user
if cmd is "status" then
lock mutex
iterate through connection list and print status
unlock mutex
else if cmd is "quit" then
lock mutex
count = number of active connections in connection list
if count == 0 then
shutdown and close listening socket
set listening socket to (-1)
unlock mutex
break out of infinite loop
end-if
print required message
unlock mutex
else if cmd is "help" or unrecognized then
print required message
end-if
end-do
Main Thread
log required message
create console thread
do forever
client socket = my_accept()
if client socket is (-1) then
break out of infinite loop
end-if
lock mutex
if listening socket is (-1) then
shutdown and close client socket
unlock mutex
break out of infinite loop
end-if
create connection object c
create connection-handling thread t
set c.thread to t
add c to connection list
unlock mutex
end-do
join with console thread
join with each thread in the connection list
log required message
Connection-handling Thread
c = connection object in argument of first procedure
lock mutex
unlock mutex
do forever
read HTTP request
if disconnected then
break out of infinite loop
else if should respond with error message then
send error response
else
send HTTP header and blank line
while there is more data to send
read 1KB from file
send 1KB into c.socket
sleep
end-while
end-if
end-do
shutdown and close c.socket
lock mutex
set c.socket to (-1)
log required message
unlock mutex
Below is the grading breakdown:
- (1 pt) submitted a valid lab7.tar.gz file with all the required files using the submission procedure below
- (1 pt) content in "lab7a.script" and "lab7c.script" are correct
- (1 pt) content in "lab7b.logfile" and "lab7c.logfile" are correct
- (1 pt) "Makefile" works for "make lab7b" and "make lab7c"
- (1 pt) source code in "lab7b.cpp" and "lab7c.cpp" looks right
Minimum deduction is 0.5 pt for anything that's incorrect.
Please note that for the " Makefile" item, you can only get credit for it if your "source code" is relevant to this lab; therefore, you can only get as many points as the "source code" item
in the best case.
Please keep in mind that even though lab grading is "light", it doesn't mean that you can just put anything
into your submission! It's still your responsibility to make sure that the files in your submission contains
information that's relevant to the tests you were supposed to run.
Use the "more" command to view your script/log files to make sure that they contain the right information.
If a file has the wrong stuff in it, you should delete it and create the file again and verify.
If most of the stuff in your script/log files are wrong and you did not notice it, we will most likely have to take points off.
To submit your work, you must first tar all the files you want to submit into a tarball and
gzip it to create a gzipped tarfile named " lab7.tar.gz".
Then you upload " lab7.tar.gz" to our Bistro submission server.
Change into the "lab7" directory you have created above and enter the following command
to create your submission file "lab7.tar.gz" (if you don't have any ".h" files, don't include "*.h*" at the end):
tar cvzf lab7.tar.gz lab7*.script lab7*.logfile Mak* *.c* *.h*
ls -l lab7.tar.gz
The last command shows you how big the created " lab7.tar.gz" file is.
If " lab7.tar.gz" is larger than 1MB in size, the submission server will not accept it.
If you use an IDE, the IDE may put your source code in subdirectories. In that case,
you need to modify the commands above so that you include ALL
the necessary source files and subdirectories (and don't include any binary files)
ane make sure that your code can be compiled without the IDE since the grader is not allowed to use an IDE to compile your code.
You should read the output of the above commands carefully to make sure that "lab7.tar.gz" is created properly.
If you don't understand the output of the above commands, you need to learn how to read it!
It's your responsibility to ensure that "lab7.tar.gz" is created properly.
To check the content of "lab7.tar.gz", you can use the following command:
tar tvf lab7.tar.gz
Please read the output of the above command carefully to see what files were included in " lab7.tar.gz"
and what are their file sizes and make sure that they make sense.
Please enter your USC e-mail address and your submission PIN below. Then click on the Browse button
and locate and select your submission file (i.e., "lab7.tar.gz").
Then click on the Upload button to submit your "lab7.tar.gz".
(Be careful what you click! Do NOT submit the wrong file!)
If you see an error message, please read the dialogbox carefully and fix what needs to be fixed and repeat the procedure.
If you don't know your submission PIN, please visit this web site to have your PIN e-mailed to your USC e-mail address.
When this web page was last loaded, the time at the submission server at merlot.usc.edu was
27Nov2025-18:59:26.
Reload this web page to see the current time on merlot.usc.edu.
If the command is executed successfully and if everything checks out,
a ticket will be issued to you to let you know "what" and "when"
your submission made it to the Bistro server. The next web page you
see would display such a ticket and the ticket should look like
the sample shown in the submission web page
(of course, the actual text would be different, but the format should be similar).
Make sure you follow the Verify Your Ticket instructions
to verify the SHA1 hash of your submission to make sure what you did not accidentally submit the wrong file.
Also, an e-mail (showing the ticket) will be sent to your USC e-mail address.
Please read the ticket carefully to know exactly "what" and "when"
your submission made it to the Bistro server.
If there are problems, please contact the instructor.
It is extreme important that you also verify your submission
after you have submitted "lab7.tar.gz" electronically to make
sure that every you have submitted is everything you wanted us to grade.
If you don't verify your submission and
you ended up submit the wrong files, please understand that due to our fairness policy,
there's absolutely nothing we can do.
Finally, please be familiar with the Electronic Submission Guidelines
and information on the bsubmit web page.
|