Friday, June 20, 2014

Homemade Internet Service Relay (IX)

Writing data is considerably simpler, so we will start with looking at write. Channel.Write() simply calls Connection.Write(), so the bulk of the logic is in Connection. When Connection receive a Write request, it breakdown the request into frames. Each frame contain a simple header that indicate which Channel it comes from, and the size of the frame so that it can be decoded later, and the bulk of the frame is then forwarded to SenderActor for processing. Note that while frame headers are newly allocated memory blocks, the frames are simply created using integer indexes so that the buffer is not copied.

After receiving frames, SenderActor keeps them in a list of SegmentHandlePairs. A SegmentHandlePair is a pair of Segment (of data to send) and a Completion handle to notify the sender the send is completed. Only the last frame has the segment handle pair attached. Normally it will just send the data through the socket, but it must be careful not to do it when the last sending is in progress.

On writing to transport completed, TCP will give us a notification how many bytes are written. SenderActor will receive this notification and then go back to the segment handle pairs. A scanning of the list will be done to see if there are completion handle to update. It is quite probable that not all data are sent, in that case we need to update the list with the right index, and then request the send to transport again.

These logic are best illustrated by an example. For simplicity, the frame size is 8 and the channel id/size take 1 byte.
Write request with 10 bytes comes from channel 1
1 2 3 4 5 6 7 8 9 10
The data are packed into frames
[Channel 1, Size 8] 1 2 3 4 5 6 7 8 [Channel 1 Size 2] 9 10
The frames are sent to SenderActor, sender Actor marks the last segment with the completion handle
[Channel 1, Size 4] 1 2 3 4 [Channel 1 Size 4] 5 6 7 8 [Channel 1 Size 2] 9 10
Sender Actor send these to the transports
Write request with 7 bytes comes from Channel 2
The data are packed into frames
[Channel 2, Size 7] A B C D E F G
[Channel 1, Size 4] 1 2 3 4 [Channel 1 Size 4] 5 6 7 8 [Channel 1 Size 2] 9 10 [Channel 2, Size 7] A B C D E F G
Sender Actor can’t send the data yet because it is transport write in progress.
Transport reports 11 bytes are written
Sender Actor updates its data structure
[Channel 1, Size 4] 1 2 3 4 [Channel 1 Size 4] 5 6 7 8 [Channel 1 Size 2] 9 10 [Channel 2, Size 7] A B C D E F G
Sender Actor realize there are more data to send, so it request transport to send these again.
Transport reports 6 bytes are written
Sender Actor updates its data structure
8 [Channel 1 Size 2] 9 10 [Channel 2, Size 7] A B C D E F G
Sender Actor knows it needs to notify a completion handle a send is completed.
Sender Actor realize there are more data to send, so it request transport to send these again.


Hopefully one can appreciate how actor makes the concurrency issue trivial here. If we had to deal with concurrency here it would be more complicated. With SenderActor, we can happily ignore the fact that the Channels and transport are concurrent because we are processing the messages one by one, the data structure need no locks.

No comments :

Post a Comment