Click here to Skip to main content
Click here to Skip to main content

EchoStream - An Echo/Tee Stream for .NET

By , 6 Apr 2003
 

Introduction

EchoStream is a full-featured echoing stream implementation for .NET. In a nutshell, an echoing stream "unions" two other streams, and anything that is written to it is in-turn written to the two underlying streams. This is sometimes called a "tee" stream. EchoStream supports this tee functionality as well as a particular kind of echoing read, in which reads always come from one of the two streams and anything that is read is echoed to the second. Finally, EchoStream includes extensive error handling options that allow a user to choose exactly how echoing errors should be handled.

Background

Back when I did a Java version of my current project, I found a class called TeeOutputStream (or TeeStream) invaluable. It was written by Anil Hemrajani and found somewhere on Sun's Java Developer Site. I modified and extended that class, and wrote an accompanying TeeInputStream, and life was good.

Now that our active development platform is .NET, I found myself needing the exact same functionality. However, I was unable to find a ready-made implementation myself, so I decided to write one from scratch. The result is EchoStream.

EchoStream is implemented in C#, and my code examples are in that language as well.

Using the Code

The simplest use of EchoStream is as a tee, or write-only, stream. One example of when you might need this is when doing network communications. You may have a requirement to log everything that you send over the network stream to a local file. The following code demonstrates this use case.

byte[] outBytes = GetDataToWrite();
NetStream netStream = GetNetworkStream();
FileStream logStream = GetLogStream();
EchoStream stream = new EchoStream(
  netStream, logStream, EchoStream.StreamOwnership.OwnNone
);

// Let's write to the echo stream.  The text that is written will end up
// in both netStream and logStream.
stream.Write(outBytes, 0, outBytes.Length);

// Now, we're done with the echo stream.  Closing it will flush the
// underlying streams, but will not close them because of
// EchoStream.StreamOwnership.OwnNone, above.
stream.Close();

The source code is pretty self-explanatory. As long as the EchoStream is open, writes to it are propagated into the two streams that were passed to its constructor.

As alluded to in the code above, you can use the EchoStream.StreamOwnership enumeration to control whether the EchoStream closes neither, either one, or both of its constituent streams whenever it is closed itself.

If you write anything directly to either of the underlying streams, EchoStream won't know anything about it, and the write will not be echoed. This may be a useful thing to do in some circumstances. EchoStream never buffers its input, so interspersing writes to the underlying streams with writes to an EchoStream should always be safe.

Now, it just so happens that if you have a requirement to log anything going out of your application through a stream, you will probably also have to log anything coming into it. EchoStream handles this as well, by identifying one of its input streams as its PrimaryStream, and the other as its SlaveStream. When writing, the primary stream and the slave stream work identically (well, almost; see below). However, when reading, the echo stream always reads from its primary stream, never from its slave stream. Anything that it reads from its primary stream is then "echoed", or written, into the slave stream. Thus the name of the class.

EchoStream takes the first stream passed to its constructor as the primary stream, and the second one as the slave stream. These streams are thereafter accessible through the PrimaryStream and SlaveStream properties.

This code shows how to read from an echo stream.

NetStream netStream = GetNetworkStream();
FileStream logStream = GetLogStream();
EchoStream stream = new EchoStream(
  netStream, logStream, EchoStream.StreamOwnership.OwnNone
);

// Read from the echo stream.  A read on the echo stream results in a
// read from the primary stream, and a write to the slave stream.
// NOTE: In many cases, you'd use a Reader instead of directly reading
// into a byte[], but I have not used one here for clarity.
byte[] inBytes = new byte[4096];    // Read 4k at a time.
int nRead = stream.Read(inBytes, 0, inBytes.Length);

// At this point, nRead bytes have been read from netStream, and nRead
// bytes have thus been written to logStream, as the input was logged to
// that stream.

As another example, say you need to read and write to the network stream, but you need your input and output to go to two separate streams. Because EchoStream never does any buffering itself, you can accomplish this simply by creating two EchoStream objects with the same primary stream, but different slave streams, as in the following code.

byte[] outBytes = GetDataToWrite();
NetStream netStream = GetNetworkStream();
FileStream outLogStream = GetOutLogStream();
FileStream inLogStream = GetInLogStream();

EchoStream outStream = new EchoStream(
  netStream, outLogStream, EchoStream.StreamOwnership.OwnNone
);
EchoStream inStream = new EchoStream(
  netStream, inLogStream, EchoStream.StreamOwnership.OwnNone
);

// Write to the output stream.  This will write to netStream and
// outLogStream.
outStream.Write(outBytes, 0, outBytes.Length);

// Read from the input stream.  This will read from netStream and
// write to inLogStream.
byte[] inBytes = new byte[4096];    // Read 4k at a time.
int nRead = stream.Read(inBytes, 0, inBytes.Length);

// Now, we're done with the echo stream.  Closing it will flush the
// underlying streams, but will not close them because of
// EchoStream.StreamOwnership.OwnNone, above.
stream.Close();

Exception Handling

The example above will hum right along until one day it runs on a client's machine who has 20 megs of free disk space. Suddenly, it will throw an exception and the network operation will fail, even though nothing bad happened with the network. In post-mortem analysis, it may come up that the 25 megs of data you were transferring were never stored on disk, but yet somehow a disk space error occurred. The answer, of course, is the log files. The way that code is written, writes to your log files must be successful in order for the network operation to also be successful.

For some cases, you may find that writing to the slave stream is just as important as writing to the primary stream, and both must succeed for the operation to succeed. However, in the case I just described above, let's assume that communication over the network stream should not be interrupted even if something had happens while writing to the log streams.

EchoStream treats the primary and slave streams differently in terms of exception handling, even in the write case. (This is the one difference for the write case noted above). Whenever both streams will be modified by an operation, EchoStream always modifies the primary stream first, and always propogates any exception that is thrown back to the caller. After the primary stream has been successfully modified, however, EchoStream's error handling abilities come into play. EchoStream supports a set of properties that control its behavior when exceptions occur while writing to the slave stream. These properties are as follows.

  • SlaveReadFailAction
  • SlaveReadFailFilter
  • SlaveWriteFailAction
  • SlaveWriteFailFilter
  • SlaveSeekFailAction
  • SlaveSeekFailFilter
  • LastReadResult

For each of the primary operations on a stream, EchoStream supports a pair of properties that specify an action to take on failure, and an optional filter to use. The action can be one of Propogate, Ignore or Filter.

The default action is Propogate. When this action is set, any exception caused by an operation on a slave stream after the primary stream has already been modified is allowed to propagate out of EchoStream. This is the most efficient behavior because EchoStream does not need to enter an expensive try block at any point during the operation.

The action that is most useful for the scenario described above is Ignore. When this action is set, any exception caused by an operation on a slave stream after the primary stream has been modified is silently caught and ignored by EchoStream. So for the example described above, adding these lines to the code before the first read or write would have solved the problem of a logging failure causing the entire operation to fail.

outStream.SlaveReadFailAction = EchoStream.SlaveFailAction.Ignore;
outStream.SlaveWriteFailAction = EchoStream.SlaveFailAction.Ignore;
outStream.SlaveSeekFailAction = EchoStream.SlaveFailAction.Ignore;

inStream.SlaveReadFailAction = EchoStream.SlaveFailAction.Ignore;
inStream.SlaveWriteFailAction = EchoStream.SlaveFailAction.Ignore;
inStream.SlaveSeekFailAction = EchoStream.SlaveFailAction.Ignore;

Also, in this particular case, where we simply want to ignore all possible exceptions that the logging streams can cause, we could use the following write-only shortcut property.

outStream.SlaveFailActions = EchoStream.SlaveFailAction.Ignore;
inStream.SlaveFailActions = EchoStream.SlaveFailAction.Ignore;

This is more maintainable for this common case because you don't have to go back and change your code later if new exception-related properties are added to EchoStream; using the shortcut properties shields you from that.

The final action, Filter, is probably the least common but definitely the most flexible of the possible actions. You cannot set any of the "action" properties to this value directly. Instead, it is set implicitly whenever you set one of the filter properties, as in the following code.

inStream.SlaveWriteFailFilter = new EchoStream.SlaveFailHandler(OnWriteFail);
Debug.Assert(inStream.SlaveWriteFailAction == EchoStream.SlaveFailAction.Filter);
...
private EchoStream.SlaveFailAction OnWriteFail(
			object oSender, EchoStream.SlaveFailMethod failMethod,
			Exception exc)
{
  // Here, examine the given failMethod and the exception that occurred, and
  // return one of SlaveFailAction.Propogate or SlaveFailAction.Ignore.
  // Returning SlaveFailAction.Filter will cause an InvalidOperationException.
}

As you can see, using a filter allows you to examine the exception that occurred and instruct the EchoStream on how to proceed. You can use the same method to handle read, write and seek failures by using the failMethod parameter to distinguish between them, or you can register different methods for each case. Also, just as with the "action" properties, you can set a handler for all filters at once by using the SlaveFailFilters method.

A final note is in order for the Propogate case. You may decide that, for maximum efficiency, you want to avoid the expensive try blocks inside EchoStream and instead allow exceptions to propogate back out to a handler that you install in your own code, somewhere such that it doesn't have to be entered once for each read and write operation. You handler may handle the exception somehow (perhaps by detaching the EchoStream and moving to writing only to the primary stream, for instance if the logging streams have "gone bad" due to bad disk space) and then restart your read or write loop. This will work fine, except in the case where the exception happened during a read operation. In that case, the exception caused the return value from Read to be lost from your point of view, so you can't tell how much was read from the stream in order to process the results of the successful read on the primary stream that happened before the slave stream threw an exception. That is where the LastReadResult property comes in. LastReadResult always reflects the result of the last Read operation that occurred on the stream, allowing you to pick up where you left off.

Additional Methods and Properties

Read, Write have been covered extensively now, but there are other methods that can potentially modify the underlying streams.

The Seek and Position properties both work in exactly the same way. They use the SlaveSeekFailAction and SlaveSeekFailFilter properties in exactly the same way as described for Read and Write. Additionally, these properties attempt to work correctly on both underlying streams. For example, imagine that you write "I see a little silhoueto" to a stream and then make it the primary stream in an EchoStream. Imagine also that you have not yet written anything to your slave stream. Writing " of a goat." to the echo stream results in the primary stream containing "I see a little silhouette of a goat.", and the slave stream containing simply "of a goat." Now you realize that you've badly misspelled "lamb" and written "goat" instead, so you decide to fix the mistake. If you set the position of the stream to the length of the "I see..." string, that position will be set directly on the primary stream, but a relative position will be computed for the slave stream based on the change in position of the primary stream. The result is that you get what you expect: the stream pointer for the primary stream goes back to the position just after the 'o' in "silhouette", and the stream pointer for the slave stream goes back to position zero.

Similar to Seek and Position is SetLength. This method changes the size of the slave stream by the same amount that it changes the size of the primary stream, rather than setting them both to the same size. This is in the same spirit as the implementation of Seek and Position.

The final method of Stream that can modify the stream itself is Flush. Flush, however, is just a special case of write, delayed due to buffering. EchoStream never buffers reads or writes, but its constituent streams may, so it is still possible for calling Flush on an EchoStream to cause an exception in one of the underlying streams. Because Flush is just a special case of Write, however, EchoStream just uses the same exception-handling properties used by Write. In this way, any buffering that is done by the underlying streams is relatively transparent to your error handling code, as long you remember that Flush may cause the Write exception-handling framework to be called into action.

Points of Interest

The initial implementation of EchoStream was very simple and straightforward. It was not until I began to write documentation for the methods and properties of the class that I realized just how woefully inadequate the class was in terms of exception handling. The nature of streams, such as the fact that many streams do not support seek operations or those operations are slow, prevent me from writing the methods of the class in an entirely exception-safe manner (in which either both streams are successfully changed, or neither are). However, because one of the two streams in the echo stream is so often going to be subordinate to the other (at least in the use cases I have thought of), it made sense to try to isolate callers from certain failures, or to give them control over what happens when failures occur with that subordinate stream. Thus was born the exception handling facilities for EchoStream, which were entirely absent in the Java Tee streams that were the inspiration for EchoStream.

Adding this error handling code increased the size of the source (including documentation) from ~360 lines to ~850 lines, and greatly increased the complexity of some of the core methods of the class. I feel confident, however, that with these error handling capabilities the class is much more robust and that, after any hidden bugs have been plied out of it through use by myself and any of you in the CodeProject community, it will be a very useful and reliable tool.

Finally, by necessity, the code downloads for EchoStream also include my Covidimus.Diagnostics.Debug class. That class is not covered in this article, but you may want to take a look at it; it may prove useful to you.

Enhancements

The most likely enhancement I can see for this class is actually something that I probably will never add to it due to efficiency concerns, and that is the ability to have multiple slave streams. I do not wish to add this capability directly to EchoStream because it implies keeping an array of slave streams and iterating over them for each modifying operation. Each slave stream would need to have errors handled separately for maximum robustness (meaning that any try blocks that must be entered will need to be entered once for each stream in the list). Even in the Propogate case, I just don't want to add the overhead of iterating over an array to the EchoStream methods when the most common case will be iteration over a single item. It is possible to chain multiple EchoStream objects together to get this behavior, but this is less efficient still than iterating over an array would be. The real solution, in my opinion, would be to develop a class parallel to EchoStream called MulticastEchoStream, which adds support for having multiple slaves. I will leave development of such a beast as an exercise for the reader.

History

  • April 7, 2003 - Initial Posting

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Stephen Quattlebaum
Web Developer
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionWhy do you say "try" blocks are expensive?memberM. Shawn Dillon14 Apr '03 - 9:47 
I like the class, but wonder why you insist that "try" blocks are expensive?
 
Throwing exceptions is expensive, to be sure, but erecting an exception handler frame to catch them has a negligible performance impact in my experience.
 
Shawn
AnswerRe: Why do you say "try" blocks are expensive?memberTim Smith14 Apr '03 - 10:14 
EDIT: I just noticed this is a NET article... So what I just said doesn't apply. Silly me.
 
ORIGINAL MESSAGE: Exception handling has come a long way. However, I have seen many instances where try blocks do indeed insert a fair amount of extra code. Obviously, when the routine starts, the FP is adjusted to point to the exception handler. However, it is very common to see a stack variable modified to keep track of which objects will need to be destructed. Thus everytime the destruct profile of the try block changes, this variable must be adjusted (i.e. a new local variable with a destructor is declared).
 
Tim Smith
 
I'm going to patent thought. I have yet to see any prior art.
AnswerRe: Why do you say "try" blocks are expensive?memberStephen Quattlebaum15 Apr '03 - 5:31 
Actually it's interesting that you asked because just yesterday I read somewhere that exact same sentiment that you just expressed, reported as fact.
 
My assertion that erecting exception handling frames is expensive comes from my experience with Java and the Microsoft VM. An older version of our product ran in that environment, and I discovered one day that something that should not have been very slow, and yet was, was due to a method called in a loop over approx. 10,000 items that included a try/catch block. As I recall, it wasn't that an exception was actually being thrown all 10,000 times (in fact it was rarely being thrown at all), but just that the try/catch block was there at all that resulted in the slowdown. Quick-and-dirty timing runs showed that rewriting the method called inside that loop to signal errors without an exception, so that the try/catch blocks could be removed, resulted in ~10x speedup.
 
However, it's been a while and I cannot remember the details. It is possible that my analysis of the situation was wrong and that it wasn't the try/catch block but rather the occasional exception in that 10,000-iteration loop that caused the 10x slowdown. It's also possible that Microsoft's .NET VM is considerably better than its Java VM when it comes to exception handling. Now that it has been brought to my attention, I will try to find time to do some timing runs with .NET to see if the special handling of the Propogate case in EchoStream was really necessary.
GeneralA SuggestionmemberChoppa9 Apr '03 - 7:02 
Just a quick suggestion... How about the ability to add and remove streams dynamically at runtime instead of specifying only two in the constructor?
 
Nice class by the way! Big Grin | :-D
GeneralRe: A SuggestionmemberStephen Quattlebaum9 Apr '03 - 16:23 

First, thanks! Big Grin | :-D
 
Second, I got to thinking about things that lead from your suggestion and ended up writing a lot, so please forgive the length.
 
I have considered adding 'set' ability for the PrimaryStream and SlaveStream properties. I generally tend toward the view that classes should require enough information passed to their constructor(s) to make an object of that class ready for use, to prevent errors caused by unset properties. Then you can use properties to further customize it, but you can't forget to set the really important ones. However, I suppose that adding properties that would allow you to switch out streams on-the-fly, as long as it doesn't let you set them to null, would not be so bad.
 
I am hesitant to add those properties, however, because doing so precludes me from maybe doing something fancy internally later on that requires that the streams never change. I can't think of anything like that, but I don't want to limit the options for the class for the little benefit that adding the ability to change streams on the fly would add, because I can't really think of a good example of something that you can do with that ability that you can't do by just creating a new EchoStream and throwing the old one away. The only thing I can think of is a situation in which you create object X, passing it an EchoStream, and then later on you want X to write to a different stream, but X doesn't provide a way to switch them out...you could then use EchoStream not only as an echo stream, but also as an adapter that lets you essentially change the stream that X writes to on-the-fly even though X itself doesn't support that functionality...
 
But that brings us right back to the fact that EchoStream itself doesn't support that functionality...hmmm...sounds to me like what's really the most flexible (and more fully factored Wink | ;-) ) in that case is to define another stream that's like a simple pass-through adapter, but where you can change out the destination stream on the fly. That actually sounds really cool, though I can't think of many uses for it right now (beyond the contrived object X exmaple above). Then you could do this:
 
AdapterStream astream = new AdapterStream();
astream.AdaptedStream = new SomeStream();
EchoStream estream = new EchoStream(pstream, astream, ...);
X x = new X(estream);
 
// ...x writes, and results go to SomeStream amongst other places...
 
// Later on...
astream.AdaptedStream = new SomeOtherStream();
 
// ...x writes, and now results go to SomeOtherStream amongst other places...
 

AdapterStream would be really easy to write, too. In fact, I probably should have written it instead of writing all this Wink | ;-)
QuestionWhat is it good for?sitebuilderUwe Keim9 Apr '03 - 1:04 
Having read the introduction, I think I understand how it works, but for what scenarios would you need these Echo- or Tee-streams?
 
--
- Free Windows-based CMS: www.zeta-software.de/enu/producer/freeware/download.html
- See me: www.magerquark.de
AnswerRe: What is it good for?memberStephen Quattlebaum9 Apr '03 - 3:03 
Well, the particular scenario for which I use it is illustrated in the body of the article Wink | ;-) Perhaps I should put a quick description of that example scenario in the introduction as well.
 
(In fact, I've now written so much below here that I'm almost definately going to update the article with bits from it...can't do it this morning though, unfortunately).
 
I use this class to keep a log everything that is written and read over a network connection in our application. In certain builds, we want complete logging of everything that the application does for debugging purposes. The application sends an XML document to a server and reads a response XML document. We have a logging framework that makes an entry in our application log, creates a "detail" file, and returns a stream to the file. (That logging framework is actually really cool and will be the topic of another article someday). We do that twice, once for the request document and once for the response. Then we use EchoStream to write the request and response documents to the log files at the same time that they are written to the server.
 
The benefits are:
 
1) Transparency: The server communication code doesn't know anything about log files, it just writes to a stream, which happens to be an EchoStream that's actually writing to multiple underlying streams. Also, if you have a complex scenario that requires writing to even more streams (or even 'n' streams), you can chain EchoStreams and never modify the writing code, whereas otherwise you'd have to modify the writing code to work with an array of streams or with your fixed number of streams. Basically, you can use preexisting code that doesn't know anything about logging, or plan new code that's nice and clean without having to worry about logging, by using this.
 
2) Efficiency: Without something like EchoStream, you either have to generate your output twice, or buffer it all so that it can be written first to the log and then to the destination stream. For large documents this is not feasible. Well, actually, you can do it in one pass without EchoStream *if* the code that's doing the writing knows about how many streams it needs to write to, but then #1.
 
Another way that I plan to use EchoStream is in a mail client that I occasionally work on when I have time, where I want to log SMTP, IMAP, etc. conversations as they happen.
 
Of course, there's nothing about EchoStream that says that it has to be used only for logging. I'd like to hear if anyone has any other good ideas for how it might be used. My particular uses all just happen to be based on logging.
GeneralRe: What is it good for?sitebuilderUwe Keim9 Apr '03 - 3:10 
Ah, I think I understand now. So you could make EchoStreams out of EchoStreams and write to 2..n targets with one stream?!?!
 
--
- Free Windows-based CMS: www.zeta-software.de/enu/producer/freeware/download.html
- See me: www.magerquark.de
GeneralRe: What is it good for?memberStephen Quattlebaum9 Apr '03 - 3:12 
I haven't tried it myself because I haven't needed it, but it should work perfectly well like that, yes.
GeneralRe: What is it good for?memberDaniel Turini9 Apr '03 - 7:19 
Uwe Keim wrote:
Ah, I think I understand now. So you could make EchoStreams out of EchoStreams and write to 2..n targets with one stream?!?!
I've coded once an application with a similar class, which simplified a lot some multithreaded code which needed to:
 
1. Read a text file
2. Make some calculations
3. Format these calculations to another text file
4. Compress the data
5. Encrypt the compressed file (I know, it's not safe, but it was required)
6. Write the compressed file to disk
 
It was meant to be run on a SMP server with 8 processors, so if the code could be run in paralel it would be very cool.
I've made 6 threads, and each thread was piping data from one to the other through a TeeStream. It was a solution that provided very high performance and each thread was very simple to code, basically being a loop reading from/writing to a pair of streams.
It could be done with arrays of bytes and synchronization objects, but it would be at least cumbersome...

 

It's not the fall that kills you: it's the sudden stop - Down by Law, Jim Jamursch (1986)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 7 Apr 2003
Article Copyright 2003 by Stephen Quattlebaum
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid