|
|
Browse by Tags
All Tags » Channels (RSS)
-
How do I add SOAP headers to an outgoing request? There are a few different ways to add headers to a message depending on how you need to control the header content and where you need to insert the header. I like to think of these methods as being split among the application, the proxy, and the protocol. In your application code you can create an OperationContextScope around the request in order to change some of the request properties. Inside an OperationContextScope, you have a valid instance of OperationContext.Current, which allows manipulation of the message headers through the OutgoingMessageHeaders collection. Use of this method deeply bakes control of the headers into the application code. You would have to be responsible for copying the appropriate code wherever it was needed. Now, assume that you want the header to be present whenever you're talking to a particular service and the header value can be determined consistently. Rather than having to put the same code at each call site, you can centralize the code in the proxy or in a protocol. The simplest way to centralize header manipulation in the proxy is to create an instance of the IClientMessageInspector and attach that to the proxy. This gives you a hook for every message going through the proxy to modify the message headers. An instance of IChannel similarly gives you a hook for every message going through the channel stack to modify the message headers. Using the Message Interceptor sample gives you an extensibility point in the binding that is similar in spirit to the extensibility point provided by a message inspector. The primary difference from your perspective of a message inspector and a channel is a matter of timing. A message inspector always runs before any of the protocols in the binding while a channel can be positioned precisely in the protocol stack. In most cases you don't need precise positioning, so you should go with the simpler approach. Next time: Adding Headers to a Call (HTTP Version) Read More...
|
-
The basis of a channel stack is that there is a series of objects that share a common interface for communication. This leaves message encoders with something of a problem as the primitive operations for encoding and decoding messages are different than the primitive operations for sending and receiving messages. Message encoders avoid this problem by being contained within a channel rather than acting as a peer in the channel stack. However, this is different than the model used for bindings, which make the message encoding binding element a peer of the other channel binding elements. To make things harder, bindings use a "no lookahead" construction process where constructing an object cannot speculate about future construction by looking into the unprocessed information in the binding. That results in a subtle dance between a message encoder and a channel that wants to use a message encoder. During the construction process, a binding gives you only a context for storing computed information and the ability to initiate the next phase of the construction process. When we go to build a message encoder, it's not possible to actually build anything because the channel that will hold the message encoder doesn't exist yet. The message encoder doesn't know how or why it is going to be used. Instead, what a message encoding binding element does to build is add itself to the BindingParameters in the context. The message encoding binding element then initiates the next phase of the construction process. At some point in the future, a channel may look at the context to see if it contains a previously saved binding element, which can now be built. The delay in instantiation is what allows channels to have a different order for physical containment than the logical order of the binding elements. Next time: Quotas for Copying Messages Read More...
|
-
At their user conference in San Francisco yesterday, TIBCO announced two integration initiatives to bring the TIBCO and Microsoft platforms closer together. TIBCO is developing a TIBCO EMS transport channel for WCF. Although Microsoft doesn't have a formal certification process for releasing third-party WCF components, I got to do some code and design reviews over the last few months with the TIBCO developers to understand the work that they are doing. I am very pleased that it was possible to make TIBCO EMS fit naturally into the WCF model. This integration work benefits both Microsoft and TIBCO customers by expanding the reach of applications on each platform. I fully support the partner ecosystem in delivering WCF components and connectivity options that go beyond what Microsoft provides in the framework. TIBCO is also going to be making use of Silverlight for development and deployment of Internet applications. Silverlight allows developers to reuse much of the tooling and knowledge that they have from desktop development when creating browser-based applications. Read More...
|
-
What channels can be used in a context binding? The primary limitation for building a context binding is that the channel stack has to have the right shape. The context exchange protocol used by a context binding requires that the first invoked operation be a request-reply operation. This is so that the initial context can be established. In order to support a request-reply operation, the channel stack needs to support one of a particular set of shapes. There are currently five channel shapes allowed when using a context binding: IRequestChannel IRequestSessionChannel IReplyChannel IReplySessionChannel IDuplexSessionChannel The request and reply channel shapes are paired for the client and server so on any particular endpoint there are three valid channel shapes. Conditions are limited further if you want to use HTTP cookies as your context exchange mechanism rather than the default of SOAP headers. In that case it's no longer possible to use a duplex channel so you're limited to variations on the request-reply message exchange pattern. Next time: Manual Context Management Read More...
|
-
When a named pipe channel listener creates a new named pipe it has to supply a discretionary ACL that describes who can connect to the pipe. Here is how that DACL is constructed: An access control entry is added to deny GENERIC_ALL access to the well-known network SID (S-1-5-2). Access control entries are added to allow GENERIC_READ and GENERIC_WRITE access to a list of SIDs that is defined on the binding element. The default is to allow the well-known world SID (S-1-1-0). Since this list is an internal setting, you will almost always be using the default. An access control entry is added to allow GENERIC_READ and GENERIC_WRITE access to the well-known creator owner SID (S-1-3-0). And that's how the DACL gets built. There are a few other settings as well required to create the pipe if you're interested in their values. The pipe is bidirectional (PIPE_ACCESS_DUPLEX), data is written to the pipe as messages (PIPE_TYPE_MESSAGE), data is read from the pipe as messages (PIPE_READMODE_MESSAGE), we use overlapped IO (FILE_FLAG_OVERLAPPED), and if this is the first pipe created by the listener, then we need to say that more pipes are coming (FILE_FLAG_FIRST_PIPE_INSTANCE). Next time: Writing Multiple Detail Elements in Faults Read More...
|
-
I have a system that sometimes uses a fast local object and sometimes needs to communicate over a network. I have built a proxy object that wraps the proxy factory for creating typed proxies together with a proxy factory for creating local objects. Are there any downsides to this strategy? Historically, there have been a number of attempts to hide whether objects are local or remote from programmers. These attempts have had varying degrees of success. Ultimately, the sticky issue is that it's difficult to prevent network abstractions from leaking. A leaky abstraction allows the environmental details that the abstraction is supposed to be hiding surface into the calling code. Like a leaky basement, a leaky abstraction can lay in wait for a long time before you realize that it's a problem. Problems with leaky abstractions show up when the abstraction author tries to make the surface layer completely seamless. Programmers love seamless abstractions because it makes their code very simple. However, implementers haven't yet figured out how to make any interesting abstraction be truly seamless. WCF attempts to deal with this issue by defining standard behaviors for channels. These standard behaviors are an escape valve so that when the abstraction leaks, it can leak in a controlled manner. For example, channels allow almost any operation to fail but restrict the exception types that the implementer can use and gives those types particular meanings. Or, channels require the programmer specify timeouts and quotas even when the application would rather trust the other side to behave. Or, and this is the most relevant one to the original question, channels require that the sender and receiver decouple their view of message data. Decoupling the sender and receiver means that there can be no way for the sender to modify the receiver's view of the data after a message is sent. In practical terms, achieving this decoupling requires that the data almost always be copied even when using direct object calls. While you can shortcut a lot of things with local objects, the system can't guarantee the abstraction if you bypass channels. There's not a big speed difference between a channel optimized for local communication and a local object that obeys all of the channel rules. Another way of saying that is that channels are extremely cheap if they don't have underlying network resources. There's currently no channel truly optimized for local communication although the named pipe Read More...
|
-
Being thread-safe is different than being concurrent. The channel interfaces are thread-safe so that multiple callers can use them at the same time without getting garbled messages. However, if multiple callers try to send messages on a single channel at the same time then a few different things might happen. The channel might be fully concurrent and process the sends in an overlapped fashion. The channel might be partially concurrent and process a limited number of the sends at a time. The other sends are blocked until one of the earlier requests completes. The channel might be singly concurrent and process the sends in a sequential fashion. A connection-oriented channel like TCP tends to be singly concurrent. Interleaving messages requires having a sophisticated framing protocol and the performance cost is rarely worth handling this special case. Datagram channels on the other hand tend to be at least partially concurrent. For example, HTTP in theory is fully concurrent to any degree that you'd like but the HTTP specification recommends limiting clients to two connections to any particular server, resulting in partial concurrency. Whenever you have less than full concurrency you run the risk of deadlock with certain application designs. That's because the logical operation order of the application might say that caller 1 must complete a send before caller 2 can complete, but the physical operation order of the channel says that caller 1 cannot start a send before caller 2 completes. I've seen customers encounter this problem when using HTTP and callbacks. The original request is waiting for the callback operation to complete but the callback thread cannot make any progress because all of the connections are tied up by existing callers, such as the original request. This is a case where the performance gain of sharing connections has led to unsafe behavior. The solution is to increase the concurrency of the system so that at least one call from any of the ongoing chains of operations can complete (there can be callbacks on top of callbacks in complicated knots so the problem is not as simple to solve as increasing the concurrency factor to two). Concurrency can be increased directly at the networking level or by isolating networking resources into separate pools at the application level. Next time: Windows and UPN Format Credentials Read More...
|
-
What are the best practices for building retry logic around network transport failures? Let's define some terms first so that we have a common language for communication. I'll say that "retry logic" is any automatically applied compensation activity that replays the same messages to either the same or a different destination. Also, I'll say that a "network transport failure" is any delivery or communication failure while attempting to transmit an application message or an infrastructure protocol message. With that out of the way, there are two places where you could attempt to handle transport failures. In a layered channel. Use a layered channel when you want the retry logic to be applied to all network calls. A layered channel also allows you to finely control the order of operations by positioning the retry logic channel within the channel stack. Placing the retry logic in a layered channel means that you don't have to deal with it at each application call site. In the application. Use application code to perform the retry logic when a retry decision involves business logic, business rules, or application state. For example, if you encounter an error while sending the third of four related messages, you may need to manipulate application state to reestablish consistency within the system. Placing the retry logic in application code means that you have to make use of it explicitly at each application call site. Regardless of the location, the retry logic for a network transport failure is going to look fairly similar. You will find out about a transport failure because some network operation threw an exception. The exceptions that you should consider handling will either be a subtype of TimeoutException or CommunicationException. Your retry logic needs to decide whether the specific exception is recoverable. That decision depends on both the types of network operations that you’re performing and the types of failures that your application is resilient to. Before attempting to retry an operation that uses the same channel, you first need to check that the channel is still usable. If the channel state is anything except for Opened, then you will be unable to send messages using that channel. The only thing that you can do with a Closed or Faulted channel is to throw it away and create a replacement. Next time: A Call to SSPI Failed Read More...
|
-
We're back to the channel pump for another round. In the previous channel pump article we had introduced an asynchronous coroutine between the main channel pump loop and a callback on acquiring a channel throttle. This use of coroutines let us suspend the channel pump until a throttle was available without having to tie up a thread during that wait. That left us with the following channel pump code. ChannelPump BEGIN LOOP AcceptChannel IF AcquireThrottle THEN DoWork (runs in the background) ELSE STOP (GotThrottle will run when a throttle is free) END LOOP GotThrottle DoWork (runs in the background) ChannelPump DoWork MessagePump CloseChannel ReleaseThrottle Now, let's look at the problem of accepting a channel. In many cases, we could fail to actually get a channel when we call AcceptChannel. For example, the channel listener may have been closed, the timeout for listening may have expired, or the network may have gone down. Some of these conditions are transient and some indicate that no more channels will ever be created. What we really want to know after failing to get a channel is whether the channel pump should keep trying. In a message pump, we can build the desired behavior by calling TryReceive on the channel. Unfortunately, there's no equivalent TryAcceptChannel. We'll have to build our own TryAcceptChannel to replace the call to AcceptChannel in ChannelPump. Under the covers, we'll call AcceptChannel inside of a try-catch block. The trick is to handle the exceptions properly and then return whether TryAcceptChannel succeeded in accepting a channel. Here's how TryAcceptChannel should handle each case. AcceptChannel completes normally and returned a channel: success AcceptChannel completes normally and returned null: success ( read this article if you're unclear why) AcceptChannel fails due to being aborted or faulted: success (read the same article above if you're unclear why) AcceptChannel fails due to a timeout: retry AcceptChannel fails due to some other reason: either retry or abort depending on whether the exception is recoverable We can finish up by rewriting ChannelPump to take advantage of our new TryAcceptChannel method. ChannelPump BEGIN LOOP IF TryAcceptChannel THEN IF channel IS NULL THEN STOP END IF IF AcquireThrottle THEN DoWork (runs in the background) ELSE STOP (GotThrottle will run when a throttle is free) END IF END IF END LOOP GotThrottle DoWork (runs in the background) ChannelPump DoWork MessagePump CloseChannel ReleaseThrottle Read More...
|
-
When you create a sessionful channel, that implies the existence of some correlation factor for all of the messages that are associated with the session. For example, the correlation factor for a TCP session is that all of the messages travel over the same TCP connection and the correlation factor for a WS-RM session is that all of the messages belong to the same reliable sequence. There is no way to identify what the correlation factor is at runtime but the channel provides an ISession object so that one correlation factor is distinguishable from another. This is done just by having a unique identifier associated with the session. Since the session and session identifier are part of the channel interface for a sessionful channel, it's possible to try to access the session as soon as the channel is created. However, there's no guarantee that the session information is valid if the channel is not open at the time. Here is how some of the standard client-side sessions behave if accessed after the channel is created but before the channel is opened. TCP session: A unique identifier is generated on the fly, this identifier will continue to be used after the channel is opened. Reliable session: The session identifier is empty, an identifier will be created when the channel is opened. Security session: Trying to access the identifier produces a runtime exception, an identifier will be created when the channel is opened. In short, you can't rely on any meaningful behavior for the session until you're ready to start sending data. Next time: Substituting for TryAccept Read More...
|
-
The hierarchy of channels derives from the single interface IChannel. By itself, IChannel is not particularly interesting because it doesn't introduce any new methods for communication. Each channel shape, such as IInputChannel or IDuplexSessionChannel, has its own interface derived from IChannel that describes the capabilities and semantics of the channel. When you write a channel, you may support variations of your channel for many different channel shapes. For example, you may support both an IInputChannel and an IOutputChannel variation for sending and receiving. Or, you may support both an IDuplexChannel and IDuplexSessionChannel variation depending on whether sessionful communication is needed. During the channel build process, your channel factory or listener will be asked to supply a single particular variation of IChannel. Since the variation is part of the generic contract, the concrete channel implementation that gets returned by the channel factory or listener must support the specified interface. Here are two best practices that specify more explicitly how that concrete channel implementation should work. Your channel factory or listener can't substitute an extended variation of a channel shape even though the type system lets you cast to the parent type. For example, IDuplexChannel and IInputSessionChannel both derive from IInputChannel. It would be wrong to return a duplex or sessionful channel if asked for an IInputChannel though. The build process is asking for the semantics of a one-way, non-sessionful input channel rather than just a type that has a callable interface compatible with IInputChannel. Use separate shape-changing channels if you need to adapt a channel implementation or supply a channel implementation that natively supports the correct shape. Each concrete channel implementation should only implement a single channel shape. While it's possible to write a single implementation that simultaneously supports multiple channel interfaces, you should instead have multiple channel implementations each supporting a single channel interface. The fact that you have different channel implementations for different shapes is hidden by the channel factory or listener so there's no additional complexity that the user of your implementation needs to worry about. Next time: Extensibility Read More...
|
-
A common corner-case in programming is what happens when multiple operations are attempted at the same time. One way to avoid the troubles of this corner-case is to simply prohibit having multiple operations occur simultaneously. However, it typically isn't possible to make this exclusion work in a universal fashion. Consider an object that prevents having multiple operations but permits having long-running (perhaps indefinitely long-running) operations. When faced with a long-running operation, it's desirable to have some way to prematurely stop that operation. The trick is that stopping an operation is often an operation in itself, violating the guarantee that only one operation runs at a time for at least a tiny period during shutdown of the object. This makes shutdown particularly prone to being a corner-case. Channels don't make any guarantees about the simultaneous execution of methods, but it can still be confusing to describe their behavior during shutdown. The basic operations of accept and receive are often long-running, meaning that there frequently is going to be some overlap between these operations and the end of the channel's lifetime. The accept or receive operation needs to have some outcome when it completes. How can that outcome be determined? Here's a rule of thumb to guess the behavior of any related method for a channel. This rule of thumb can be applied for example when shutting down the system or when a session finishes all of its input. If the operation is supposed to return some object, then that object is null. We didn't actually get an object instance during the operation. If the operation is conditional and reports whether it succeeded or failed, then the outcome is that it succeeded. A failure indicates that something went wrong, but there's nothing wrong with being done. For example, the TryReceive method returns both an object and a conditional report of success or failure. According to the rule of thumb, when the channel has no more data, it should return null for the received message. Also, it should report a conditional result of success (true, in this case) for the attempt to receive a message. Next time: Body is a Stream Read More...
|
-
Last time, we were looking at how to control flow through a channel pump by introducing the concept of a throttle. The implementation of the throttle was a semaphore, allowing us to limit the number of channels active in the system at any point in time. This lead to a straightforward approach; the channel pump stopped to acquire the semaphore each time before starting new work. ChannelPump BEGIN LOOP AcceptChannel AcquireThrottle DoWork END LOOP DoWork MessagePump CloseChannel ReleaseThrottle The straightforward approach to throttling has the drawback that a thread of execution is blocked sitting in the ChannelPump loop. We can have several instances of DoWork running simultaneously by executing DoWork asynchronously. We can similarly use asynchronous execution to straighten out the ChannelPump loop. This produces an approach where execution ping-pongs between two asynchronous methods. The key to the ping-pong approach is that the asynchronous execution can wait to start until a condition is satisfied. Let's change AcquireThrottle to return immediately from the call rather than blocking. If AcquireThrottle returns true, then it means that we acquired the semaphore. If AcquireThrottle returns false, then it means that we weren't successful but in the future a method will be executed once the semaphore is available. Let's call that method GotThrottle. We can now bounce execution from ChannelPump to GotThrottle and then back to ChannelPump in the future. ChannelPump BEGIN LOOP AcceptChannel IF AcquireThrottle THEN DoWork ELSE STOP END LOOP GotThrottle DoWork ChannelPump DoWork MessagePump CloseChannel ReleaseThrottle There's no longer any need to wait for the semaphore to become available. There is some code waiting to start running pending availability of the semaphore but it doesn't have to be assigned any resources. We have to be careful now though about being very precise with asynchronous execution. Since ChannelPump is calling itself through an intermediary, synchronous execution of those calls would lead to building up an unlimited number of stack frames. We have also created some new complexity for ourselves in the form of state management between the calls. That's all for the channel pump this week. I'll have some more articles in this series in the future. Next time: ContractNamespaceAttribute Read More...
|
-
When programming using channels, the common usage pattern for the server is to sit in a loop accepting channels. This loop, sometimes called a pump, cycles through each time accepting a channel and initiating some work. Let's look at the most basic channel pump possible. I'm going to write things with pseudocode to make it even simpler. ChannelPump BEGIN LOOP AcceptChannel DoWork END LOOP We'll assume that DoWork goes off in the background to perform some asynchronous processing and will take care of cleaning up resources. There's nothing we could cut out of this pump. There are many things that we'd have to add though to make the pump survive more than a few iterations without blowing up. One problem with the pump is that we spin through it as fast as possible. The pump will continue kicking off additional work even if the system can't withstand the load. We need some way to throttle the flow of data. A simple throttling mechanism is to limit the number of channels that we can have working at once. Let's add a semaphore to count the number of active channels and prevent DoWork from being called if we're already using all of the available work throttles. We'll need to change ChannelPump and also put some details into DoWork. ChannelPump BEGIN LOOP AcceptChannel AcquireThrottle DoWork END LOOP DoWork MessagePump CloseChannel ReleaseThrottle There are some new constructs here but the only one that needs explanation is MessagePump. MessagePump is what actually receives messages from the channel and processes them. All of DoWork runs asynchronously though so ChannelPump will continue running while DoWork is taking place. Unfortunately, ChannelPump will block when AcquireThrottle isn't able to acquire the semaphore immediately. This does what we want because DoWork won't get called but it's less than ideal that we sit there waiting. Next time, we'll look at how to change ChannelPump to solve the blocking problem. Next time: Unblocking Flow Throttles Read More...
|
-
Inside a service, there's a fundamental loop running whose job it is to create channels for the incoming connections to the service. There's another loop that runs later, which you may argue is equally fundamental, that reads messages from each channel to determine the actual service invocation. Every one of these service requests though was preceded at some point by a channel being accepted (one channel may produce multiple requests of course). Depending on how things are configured, the service may be running a transactional loop or a non-transactional loop. The transactional loop runs when the service needs to create its own transaction for reading messages. As a custom channel author, the two loops have different implications for how your channel gets used. In the non-transactional case, the service sits in a loop calling BeginAcceptChannel on the topmost channel listener. This loop continues until the listener fails to give back a channel or the listener faults. The loop pauses if a quota is reached that prevents more channels from being accepted, but the loop can later be restarted when the quota is no longer an issue. In the transactional case, the service first sits in a loop calling WaitForChannel. When WaitForChannel completes, the loop creates a new transaction and calls AcceptChannel. The use of WaitForChannel is an optimization to reduce the number of unnecessary transactions that get created. The loop will later complete or abort the transaction as appropriate but otherwise functions very similarly from this point. Next time: SOAP Extensions Read More...
|
|
|
|