What's the right way to Dispose() a channel?
In the current Indigo bits, there are three things you can do to get rid of a channel object when you're done with it:
channel.Close()
channel.Abort()
channel.Dispose()
All the 'communications objects' in Indigo (channels, factories, listeners, etc) share a common Open/Close state machine - in that model, Close() means "do a clean shutdown" (which for some cases like reliable channels may involve sending and receiving additional infrastructure messages), while Abort() means "clean up your resources and quit right away."
The interesting one is Dispose(); its the one that provides support for this idiom:
using (MyProxy proxy = new MyProxy())
{
proxy.SendMessage();
}
The .NET Frameworks guidelines say that Dispose() should be the same as Close(); and if that were the case, the code above would be correct. However, there are a couple problems with that model:
problem #1: Since Close() may incur message exchanges, it can block for a while. Blocking shutdown, particularly if you're on the finalizer thread, is considered a 'bad thing'.
problem #2: if you're sending a series of messages in a session, and you fail to send all the messages you intended to send - you'd like the other side to know about it. Consider this case:
using (MySessionfulProxy proxy = new MySessionfulProxy())
{
proxy.SendFirstMessage();
// some other code throws an exception here
proxy.SendLastMessage();
}
When the exception gets thrown, Dispose() calls Close(), which will do a clean shutdown of the session - as far as the other side knows, everything was sent just fine - when in fact it failed to receive the last message.
Because of these issues, we chose to make Dispose() the same as Abort(). The correct way to use a channel in the current bits is:
using (MyProxy proxy = new MyProxy())
{
proxy.SendMessage();
proxy.Close();
}
If everything works correctly, we call Close() and get a clean shutdown; if an exception gets thrown, then we Abort().
We have gotten some feedback that requiring that explicit Close() call isn't intuitively obvious, and even if you know about the requirement, you may sometimes forget to do it. We're eager to get more feedback on this issue; especially if you have other compelling arguments pro or con. Please, weigh in and let us know what you think! - and if there is some third option even better than the two described above, I'd love to hear about it.
On a side note, another thing we like about the current model is that it is similar to how TransactionScope works; the transaction team is also part of Indigo:
using (TransactionScope tx = new TransactionScope())
{
// do some work here
tx.Complete();
}
channel.Close()
channel.Abort()
channel.Dispose()
All the 'communications objects' in Indigo (channels, factories, listeners, etc) share a common Open/Close state machine - in that model, Close() means "do a clean shutdown" (which for some cases like reliable channels may involve sending and receiving additional infrastructure messages), while Abort() means "clean up your resources and quit right away."
The interesting one is Dispose(); its the one that provides support for this idiom:
using (MyProxy proxy = new MyProxy())
{
proxy.SendMessage();
}
The .NET Frameworks guidelines say that Dispose() should be the same as Close(); and if that were the case, the code above would be correct. However, there are a couple problems with that model:
problem #1: Since Close() may incur message exchanges, it can block for a while. Blocking shutdown, particularly if you're on the finalizer thread, is considered a 'bad thing'.
problem #2: if you're sending a series of messages in a session, and you fail to send all the messages you intended to send - you'd like the other side to know about it. Consider this case:
using (MySessionfulProxy proxy = new MySessionfulProxy())
{
proxy.SendFirstMessage();
// some other code throws an exception here
proxy.SendLastMessage();
}
When the exception gets thrown, Dispose() calls Close(), which will do a clean shutdown of the session - as far as the other side knows, everything was sent just fine - when in fact it failed to receive the last message.
Because of these issues, we chose to make Dispose() the same as Abort(). The correct way to use a channel in the current bits is:
using (MyProxy proxy = new MyProxy())
{
proxy.SendMessage();
proxy.Close();
}
If everything works correctly, we call Close() and get a clean shutdown; if an exception gets thrown, then we Abort().
We have gotten some feedback that requiring that explicit Close() call isn't intuitively obvious, and even if you know about the requirement, you may sometimes forget to do it. We're eager to get more feedback on this issue; especially if you have other compelling arguments pro or con. Please, weigh in and let us know what you think! - and if there is some third option even better than the two described above, I'd love to hear about it.
On a side note, another thing we like about the current model is that it is similar to how TransactionScope works; the transaction team is also part of Indigo:
using (TransactionScope tx = new TransactionScope())
{
// do some work here
tx.Complete();
}


7 Comments:
I think this is a real mess. I'm not sure from your post how much of the problem is with the overall Indigo model and how much of it is with the channel object. Let's start by looking at Abort(). You say that in the general Indigo model Abort() means "clean up your resources and quit right away." This seems like a substantial change from the common understanding of Abort(). Abort() has traditional meant drop everything on the floor. In a non-transactional context (such as Thread.Abort() ), that has always meant the programmer is responsible for cleaning up whatever mess is left. In a transactional context, the transaction manager is responsible for cleaning up the mess (i.e. rolling things back to the initial state), but the object being aborted has not generally been expected to do much of anything. I, as a prospective Indigo programmer, am not clear what the differences between "do a clean shutdown" and "clean up your resources and quit right away" would be for Indigo objects. The idea that Dispose() and Abort() could ever mean the same thing just completely boggles my mind.
Here's what I would recommend. Get rid of Abort(), unless there is some overwhelming reason for it. Sometimes I think Thread.Abort() was a horrible mistake. Every time I've done a code review and found a Thread.Abort(), it was being used incorrectly. I really think if the method hadn't been there, the programmer would have found the right solution. I know multi-threaded programming is hard, but easy access to dangerous methods makes it worse.
Close() and Dispose() should be the same, in keeping with the traditional .NET idiom. For most of the Indigo objects, they should probably "do a clean shutdown". As you've described the channel object, it's Close/Dispose() method should probably do what Abort/Dispose() does now. I think the Indigo team is trying to supply too much application semantic in Close() for reliable channels. I would rather be in more direct control of the messages I send to the other side. You need an explicit method to tear down the channel, if that means sending messages. Maybe something like SignOff() or OverAndOut() to do what you have Close() doing now. I think I could explain that to other programmers a lot easier than the conversations I forsee:
Me: So, for this channel, calling Dispose() is really like calling Abort(), rather than calling Close(). You have to call Close() and then call Dispose(). Unless, of course, you're using "using", then you just have to call Dispose().
Other programmer: Huh? I thought you told me to never call Abort() and that Close() and Dispose() should always do the same thing. You said that was Microsoft's rule and you made me change my object model to work that way.
John Cavnar-Johnson
By
Anonymous, at 2:51 PM, March 25, 2005
Our notion of Abort is essentially the same as yours, I think - the channel that is aborted is just dropped on the floor. All I meant by the 'clean up resources' comment was that Indigo will keep itself internally consistent - when you abort a channel, that channel isn't good for much else, but it doesn't keep you from using Indigo to create other new channels.
It sounds like you're OK with our model, you just don't like our names; instead of:
using (Proxy p = new Proxy())
{
...
p.Close();
}
you want to see:
using (Proxy p = new Proxy())
{
...
p.OverAndOut();
}
By
Bruce, at 10:47 PM, March 25, 2005
No, I am definitely not ok with your model. You're trivializing my concerns to say that I don't like your names. The naming conventions that you are using are symptomatic of a mind-set that I thought (based on conversations at the PDC) had finally been put to rest. Obviously, the Indigo team hasn't given up on the whole location transparency concept. I thought we were going to get a programming model that required explicitness to cross machine boundaries, but that isn't the case here. You shouldn't conduct a message exchange with a remote machine and cover it up with an innocuous sounding function like Close(). This is truly disappointing.
John Cavnar-Johnson
By
Anonymous, at 9:05 PM, March 27, 2005
Hey Bruce, I saw this blog entry about disposing indigo channels last week, but didn’t get a chance to put up a comment. I wanted to do a little more reading on Contexts before I opened my mouth or rather let loose my fingers, but anyways here goes:
So we have three ways to ‘get rid of’ a channel object, Close(), Abort() and Dispose(). The way that I see it is that we are mixing disciplines a bit when we attempt to teach persons that you can close a channel by disposing it. We are mixing up (and I am by no means an expert, still a grasshopper) proper Messaging with Garbage collection paradigms, yes... disposing of a channel closes the channel, but that is incidental to disposing the object and shouldn’t really be the thought of as a way to close a channel, know what I mean? I was going to say that the decision to close channels in the dispose method should be left to the developers... but, you guys are the developers creating the libraries for us to consume so in the end I understand what you’re saying. When it is being taught, I think the ‘proper’ Messaging thing should be taught first i.e. that to Close a channel, use the Close() method. Then you can say, “... Incidentally, the indigo team implemented a check to Close() channels during finalization. What this means is that we can make use of the .NET dispose pattern and using construct to close our channel objects ...” Right, all that said, I agree with your position on Problem #1. In fact I will take it a little further and say that Close should probably not be called at all in the dispose method, rather Abort() should be called. This will force developers to pay attention to properly and cleanly tearing down communication sessions and other resources before either disposing of objects intentionally or letting them fall out of scope.
Now I think that you and I mean the same thing when we say Abort(). This is how I think of it. Lets use as an example your QueueChannel.
In the channel say we had some ListenLoop() method that basically had a thread spinning in a while loop listening for messages on the queue.
protected void ListenLoop()
{
lock(this.processing)
{
this.processingThread = System.Threading.Thread.CurrentThread;
TimeSpan waitTime = new TimeSpan(0, 0, 5);
while(this.state == QueueChannelState.Open)
{
try
{
Thread.Sleep(TimeSpan.Zero);
Message item = this.queue.Dequeue(waitTime);
inputPipeline.ProcessMessage(item.Body);
}
catch(ThreadAbortException ex)
{
this.Close();
if(this.ExceptionOccuredCallback != null)
ExceptionOccuredCallback(ex);
}
catch(MessageQueueException ex)
{
...
}
}
}
}
Then I’m thinking that the abort and close methods of this guy would look something like this
protected override void Abort()
{
if(this.processingThread != null)
this.processingThread.Abort();
}
protected override void Close()
{
// close the queue
this.queue.Close();
this.ChangeState(QueueChannelState.Closed);
// clean up any other resources
}
Okay... enough about that one. Now on to your problem #2, I should say that I also disagree with you example in that the problem of the other side knowing that a message was not sent is not related to the channel’s clean or shutdown process, but is a ‘implementing a reliable messaging system’ problem i.e. If I wanted the other side to know that I was about to send a set of messages and I wanted him to pay special attention to this such that if one is missing the other side will know then I’ll have to develop a message to convey this and other info e.g.
BeginBulk_Message bulk_msg = new BeginBulk_Message();
bulk_msg.NumberOfMessages = 10;
bulk_msg.Order = MessageOrder.Ordered;
bulk_msg.CorrelationID = Guid.NewGuid();
using(Channel channel = Factory.GetChannel())
{
channel.Send(bulk_msg);
channel.Send(messages[0]);
channel.Send(messages[1]);
throw new ApplicationException();
channel.Send(messages[2]);
}
So I don’t really see this as being a problem.
Make any sense?
Cordell Lawrence [clawrence@teleios-systems.com]
By
Cordell Lawrence, at 4:58 AM, March 29, 2005
John, there is certainly a tension between explicitness and usability - a lot of our focus since the PDC has been on usability - if we've gone too far in that direction, we'd love to hear about it.
Could you tell me some more about how we should explicitly expose infrastructure messages? We have a variety of them; RM can send a standalong 'I'm the last message' message, like we've been discussing; transactions could potentially do message exchange with remote transaction managers; and security can do token exchange negotiations. Right now you don't really see these messages in the programming model, except for the fact that you've picked the associated characteristics for your bindings. It sounds like you'd like to see these explicitly exposed?
I wasn't at the PDC, but if you tell me who you talked to there, I can pull them into this conversation; perhaps they could explain it better.
By
Bruce, at 8:26 AM, March 29, 2005
Cordell, our channel Dispose() method does call Abort(), not Close() - I was just using the Close() example to show a route we considered and then declined to take. Sorry for the confusion!
By
Bruce, at 8:36 AM, March 29, 2005
Cordell, you're right that if you don't care about reliable messaging semantics, then additional coordinating 'end' messages aren't necessary. I was assuming the use of Indigo reliable messaging in order to demonstrate my point that Close() could potentially block; but you're not required to use RM. Do note (as I mentioned in another recent reply on this thread) that there are other infrastructure messages that Indigo can send under the covers, if you have your channel configured with the appropriate characteristics.
By
Bruce, at 8:41 AM, March 29, 2005
Post a Comment
<< Home