CS 5523 Operating Systems
Laboratory 1 Post Mortem
Proxy Monitor Spring 2003
- Comments on
InitialLine
- General strategies for
implementing
InitialLine:
- Strategy 1: store most of the parsed information in a
URL
object.
- Strategy 2: do all parsing and validity testing in the constructor so that
all of the
get methods simply return values.
For the InitialLine class, I think Strategy 2 is better.
Once the object is constructed, it is immutable. You don't have to worry
about thread synchronization, and the logic is simpler. Common practice
would be to have a private init function that is called
from the constructor and contains most of the initialization code.
- Some people ignored the parsing
capabilities of the
URL class and tried to do the parsing directly. Always use existing classes
if they can do the job.
- Bugs for
InitialLine
- Used Strategy 1, but didn't test for valid
URL such as in:
public String getHost() {
return myURL.getHost();
}
This may blow up if myURL is invalid.
Didn't initialize the fields such as host prior to parsing.
If the line is invalid, you should still have
a strategy for returning something, since the
InitialLine constructor doesn't throw an exception.
InitialLine constructor or other methods
throw an exception (not the spec).
Allocated an array to hold the three tokens, and
tokenized all of the tokens in the line without first checking that
the line had at most 3 tokens. (This
throws an array out of bounds exception.)
A test input line of more than 3 tokens would have picked up this error.
Incorrect layout of try-catch for the URL:
try {
myurl = new URL(....
} catch (MalformedURLException e) { }
host = myurl.gethost();
...
If the URL is invalid, you do not want to use myurl to get the
values. Those gets should be in the try-catch.
Testing
- Many people did not give adequate test cases for
InitialLine.
- A good way to test is to output a message, what this test case was
designed to test (i.e., too many tokens on the line) and what the expected
result of the test should be. This makes your test program self-documenting.
- I would have liked to have seen a table of test cases in your report.
Comments on HTTPHeader
Comments on CopyFromTo
- Incorrect synchronization of the number of bytes read so far:
- No synchronization
- Synchronization of
getNumberBytes but no synchronization
of the increment of the number of bytes.
- Synchronization of the entire
run method. This may work,
but is probably not a good strategy since it will lock other threads
out of calling getNumberBytes until the thread terminates.
- Incorrect handling of the end of communication. Remember,
unless you close the streams
the underlying system resources are not released and the remote peers
don't know that it is ended
- Treating the end-of-file or "connection reset by peer" as an error.
- Not closing both streams when an end-of-file or I/O error occurs on
either.
- Putting the
close
calls in the same try catch as the read and write. Why is
this a problem?
try {
out.writeByte(in.readByte());
in.close();
out.close();
}
Putting the close
calls in the finally, but in the same try-catch.
finally {
try {
in.close();
out.close();
} catch ( ) {}
}
Comments on Tunnel
- Incorrect handling of logic:
- Example 1 --- strictly alternating:
c1= new CopyFromTo(a, b);
c1.join();
c2 = new CopyFromTo(b, a);
c2.join();
Example 2 --- strictly sequential:
while () {
s = p.accept();
c1= new CopyFromTo(a, b);
c2 = new CopyFromTo(b, a);
c1.join();
c2.join();
}
Example 3 --- busy waiting --- continually wasting
CPU cycles by testing with isAlive() in a loop --- this strategy turns
out to be much worse than you might imagine
Example 4 --- having the main thread (or even another thread)
sleep a long time and then check to see if the threads are done ---
you will not get a good overall time this way.
Example 5 --- only testing to see that the last thread started
is done ---- the start order doesn't determine when the threads finish
Example 6 --- what happens if accept has an error in
the following?
while () {
try {
t = s.accept();
} catch ( ) {....}
try {
c = new Connection(t);
} catch ( ) {....}
}
Example 7 -- what happens if accept has an error in
the following?
try {
while ( ) {
t = s.accept();
c = new Connection(t);
}
} catch ( ) { ...}
Example 8 --- if the ServerSocket creation or command line argument
parsing fails, some people just printed an error message and went on
into the infinite loop to accept connections.
try {
ServerSocket s = new ServerSocket(....);
} catch (Exception e) { ...just print a message ....}
while ( ) {
...
}
Another variation of this error has the creation of the
ServerSocket and the accept in the same inner try-catch
Incorrect measurement of time ---
There are two workable strategies for ending so that you can get
the total time and have the server handle requests in parallel.
- Strategy 1: Create a separate
Connection thread that
creates two CopyFromTo threads and then joins with them.
Measure the ending time of each thread.
- Strategy 2: Have one thread whose responsibility is cleanup.
Each
CopyFromTo thread puts itself on the cleanup thread's
list. The cleanup thread takes threads off a list and calls one of their
methods to get their time (like a call-back). This strategy is considerably
more complicated, but might be used in practice for efficiency.
Comments on ProxyMonitor
Most of the problems that were encountered here were due to
bugs in the first four parts.
Comments on LoggingProxy
- Incorrect implementations:
- Writing a separate log file for each request.
- Writing the resource into log file that was only supposed to
contain the headers.
- Not logging the server response header.
- Writing each byte or line of the header to the log file separately.
This results in interspersed headers from different requests and
makes the log useless.
- Making the writing of the log file part of a synchronized loop
in a way that effectively forced client requests to be processed
one at a time.
- Gathering the entire request and resource into a
StringBuffer
before writing the log and resource file out. The resource could be
huge. In fact, the header could be huge also and so the logging
facility should probably limit the number of lines in the header that
are logged to a user-settable number.
- Some people created a separate
Logger class to handle
the output of the header. This is the preferred method.
- Many people
had very complex if-else code segments
with lots of repeated code to handle the
three logging cases. An example of a more object-oriented design might
define a base class
Connection
for no logging and subclasses for
the different logging levels. Actually the "headers" case might be
a superclass for the "all" case. With this approach, you just instantiate
a different type of Connection object for the
different types of logging. Alternatively, you could have a
Connection class with multiple constructors, but you
need to make sure that you avoid the complex logic.
Threading and timing errors:
In general there were fewer of these errors than in previous semesters.
I am going to mention them here anyway.
- Assumed that client sends the request and then server responds.
This is not
the specification. You must have two threads capable of
simultaneously transferring information.
- Assumed that the entire request could be read in a single read,
provided the
buffer was big enough. This only works on a fast local network. TCP provides an abstraction of a stream of bytes without packet or message boundaries. You have no control over how much will
be delivered in a single read.
- Did not measure the time to process
the client requests in the tunnel program.
The specification for the tunnel
asked you to measure the time of the client communication.
- Incorrectly measured the time to process the client requests.
For example, measuring the start time after
the
accept and the end time after the thread creation.
This placement measures thread creation time, and indicates
to me that you do not understand thread flow of control. The end time should
be measured after the join.
- Measured time values that were completely unreasonable, such
as a time in milliseconds that was equivalent to several years. Look at the
output to see if it is reasonable.
- Used
sleep in the threads to inappropriately force
synchronization. When I see sleep(1000) in random places in
a program, I know it doesn't work right.
- Logging one line at a time to the log file. The log file is a shared
resource. To really do this right, you should accumulate the header in a
StringBuffer. When the header is complete, the thread should
write to the log file. Actually a good approach is to have a separate LogFile
class and make its write method synchronized.
(This still doesn't solve the problem of writing one header line at at time
to the file.)
- Connected to the destination web server
from the tunnel before accepting a
client connection.
If you type fast, you might not notice a problem. In a real
implementation, the destination web server will disconnect after a fairly short
time period when no incoming request appears.
Bad parsing and logging strategies:
- Used
indexOf("\n\r") to parse the header from
the resource. In most cases, this approach resulted in taking the first
line of the response instead of the entire header.
- Parsing inline in the main loop.
When I see this, I know that you didn't test your parsing method very well---
how could you? You should remove your parsing code from the main loop,
put it in a method, or better a class and test it separately.
(The new specification mostly eliminated this problem, as most
but not all, people called
HTTPHeader to do the parsing.
- Treated the resource as a
String.
The difference between a byte and a character in Java is quite confusing.
Uncaught exceptions, badly caught exceptions, and bad exits:
Writing style and presentation: Actually, the reports
this semester were much better than in previous semesters!
- Spelling and grammar. (Yikkes! Use a grammar checker and pay attention
to those green underlines, please!)
- Including completely irrelevant information in the introduction (to make the
report look longer?)
- Changing style, typeface, number scheme or bullet style in the middle
of the report. (Use Times-Roman 12-pt type so I don't go blind.
Also, please single-space and hand in with a single staple in the
upper left corner. If your report involved heavy deforestation,
please staple individual sections and then use a big spring clip
to put everything together.)
- Poor layout and organization. (Please follow the guidelines for
the reports.)
- Presented the entire report
in a single paragraph or in very long paragraphs.
(Use paragraphs. If
you have a single-spaced paragraph that is a third of a page or more, you
probably need more paragraphs or to be more concise.)
- Inappropriate or lack of section titles.
(Bold face the section titles and make the section titles correspond to
what the section is about. Your goal is to convey information, not to
challenge the reader's logic skills.)
- Excessive use of code in the report. (Some people pasted parts of
their programs into the report. Use outlines, pseudocode or block
diagrams to convey implementation
details.)
- Excessive use of "it". (After 20 sentences that start with "it" on the
same page, the reader won't know what it you mean. My personal style
problem is starting too many sentences with "This is". Some of you also
have this problem.)
- Gave separate architectural diagrams for the tunnel and proxy, but
the diagrams were identical. If the diagrams are going to be the same,
you should give one diagram and refer to it when discussing both items.
- Gave a confusing diagram. Diagrams are very useful and can greatly
improve the clarity of the presentation, but a diagram that conveys the wrong
idea is worse than no diagram. Ask yourself, what information
you are trying to convey by the diagram. For example don't use the same
style box to represent both a process and a port number, or the same type of arrow
to represent a connection request and a thread.
- Didn't answer the questions. (The specification asked several questions that
you were supposed to answer.)
Poor testing and presentation of results:
- Did not prove that the program correctly transmitted the files.
(Although it might appear that your program is working because the browser
displays a page, you must test that the program correctly transmitted the data.
For the tunnel, you should have first tested between a modified
TCPClient and TCPServer to make sure that the tunnel correctly transferred random files
in both directions --- including binary files. I could tell from reading
code that contained
BufferedReader or readUTF or
used String for the files, that this was not done and that
the test cases had been carefully selected.)
- Did not adequately explain the tests. The typical approach was
a short paragraph saying the program worked. A large log file was then included.
(A good approach might be to organize your test results into a table with an annotation
of what that test result was supposed to show and a column with page numbers of
the output so that the reader can actually see the tests.)
- Random tests. (You should think about what you are trying to test for ---
different types of web pages, different types of servers, different network
connections, different times of day, etc. and clearly organize your tests.)
- Did not state conditions under which experiment was run (machines, times of day, etc.)
- Did not give an analysis of what happened, what you expected to
happen and what actually did happen.
- Assumed everything was a
String. (Clearly you didn't run
tests with binary objects---or at least didn't mention it.)
Programming errors and bad style:
Last revision: March 3, 2003 at 9:10 am by Kay A. Robbins