Last time I worked on integrating pseudotcp code with ice4. It was quite simple as all I had to do was change package names and include my unit tests in the ice4j's test suite. The last one required to switch to older version of JUnit. It doesn't support annotations so I had to derive my base test class from the TestCase and rename all test methods to start with lower case "test" keyword.
Next task was to create an example how pseudotcp may be used with Ice4j library. The basic scenario is that there are two ice clients establishing UDP connectivity. Then selected addresses are used for pseudotcp transfer. Optionally STUN and TURN harvesters may be used - by default it works only with local interfaces.
Later I've finally switched to java.util.logging.Logger as a logging mechanism. The message templates helped me to resolve the issue with many string concatenations for messges that are not valid for current logging level. Profiler showed that it had visible impact on performance, as there are many messages generated while the protocol is working. Here's an example:
The main advantage of this approach is that the string will be generated only if message will be displayed(logging level will be set at least to FINE).
wtorek, 3 lipca 2012
It's been a while since my last post as I had a small break for the exams. Recently I worked on fixing tcp window unit tests. I had some problems with porting them from libjingle because of different threading model. In libjingle these tests were based on thread's message queue, but here I didn't wanted to implement such thing, because it wouldn't fit to the whole ice4j library where practically only pseudo tcp would use such feature. I based my solution on standard wait() and notify() monitor methods of java Object's class.
To test tcp window I used two instances of pseudo tcp called "local" and "remote". First local instance fills up send buffer. When it's filled, the byte count which were put into means the send window size. In this threading model in background runs another thread called the clock thread. Between two calls of the send method it may decide to send buffered data so we will fail to estimate the send window's size properly. So I used data 2x size of the send buffer to be sure that I'll fill it in a single call.
Then it must wait until data will be available in remote instance's buffer. I used polling here for simplicity. It runs in a loop with sleep and checks if the data arrived to the remote instance or the remote's receive buffer is full. At this point I've encountered one issue with window scaling which I'll describe later.
When all data put into the send buffer had arrived to remote or it's receive buffer is full I read all the data and store bytes count. This count means estimated receive window size. That sequence of send and read was used to estimate and check if both windows are set correctly.
Another thing we wanted to check was if the receive window is fully opened after the data has been read(and if the info about this fact was correctly propageted to the local instance). To do that, the routine of send/receive was repeated twice and there were performed calculations on subsequent send positions compared with estimated receive window size. When this difference was close to receive window size it was considered a positive result.
While working on this test I considered it hacky and I was thinking about dropping it. But it helped me to track few important bugs and also thanks to this I've decided to improve buffer class which is now much faster that the first implementation. First implementation was causing the receive window not to get fully opened as it was rewinded when it was filled up to 1/2. So for the half of time it was hiding available buffer's space.
Another thing I learnt was about the window scaling. When this tests were run with buffer's size greater than "32 bit unsigned int" then the window scaling is involved(32 bits are reserved for window size in tcp header). In RFC it's defined as an extension of TCP for enhanced performance. In some networks it's good to send larger packets to decrease overhead.
So in basic the window scale option is an additional byte which is used to bitwise shift window size value. For example when we want to use window of size 100 000 bytes we need to use scaling factor of 1. That means that we put into the header 100 000 >> 1 = 50 000 as window size and 1 as "window scale option". When the receivier gets that then he has to do the reverse operation: 50 000 << 1 = 100 000.
As I described earlier I used polling to check if all data put into the buffer is receivied by remote side. When window scaling is involved I'm not able to transfer exactly all data in a single call because small part of window is hidden. It happens because window size isn't constant during conversation and when there will be only 1 byte left of the free space avaialble in a buffer we will lost it with the shift operation : 1 >> 1 = 0. When the receivier makes reverse operation he will still get 0. As we know when window gets closed(is equal to 0) that means that remote side have some problems with processing the data and we should wait for it to open before sending any more data.
It was causing thread blocking and timeouts when none of these conditions were met: all data wasn't transferred and the receivier's buffer wasn't fully filled. I solved this by introducing a margin of error to that calcultion based on used window scale option. For scale option equal to 1 there will be 1 byte hidden, for 2 there will be 3 bytes hidden, for 3 -> 7 and so on. But I'm not sure if it's a good idea as it looks a bit hacky.
That's not the issue in normal operation when something will read data on the remote side and receive window will open. This problem was specific only for this test altthough I was considering this as a bug at first.
Now my task is to integrate the protocol with ice4j library and create some usage example of how we can use it together with an ice protocol. At this point I've managed to merge my project with ice4j and include unit tests inside. In about a week I should make first commit to the ice4j lib as there are still a few things I have to take care of. Then the temporary project "pseudo-tcp-java-impl" will probably disappear.