[ 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:
  • Create an empty directory (call it "lab7") and change directory into it.
  • Download lab7data.tar.gz into that directory and type:
        tar xvf lab7data.tar.gz
    This should create a subdirectory called "lab7data" with a Makefile and several .cpp files in it which we have covered in class when we discussed "multithreading part 3 - mutex".
  • Type "script lab7a.script" to start a transcript. (If your command shell is bash, the "foreach" command below will not work and you should first type "tcsh" to change your command shell to tcsh before proceeding.) Then type:
        uname -a
        cat /etc/os-release
        cd lab7data
        make clean
        foreach f (race no-race no-race-atomic)
            make $f
            ./a.out
            ./a.out
            ./a.out
            ./a.out
            ./a.out
        end
        make clean
        cd ..
  • Type "exit" to close the transcript.
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.

USC E-mail:    @usc.edu
Submission PIN:  
Event ID (read-only):  
Submission File Full Path:  
   
 
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.