ChannelHandler and ChannelPipeline
This chapter covers
- The
ChannelHandler
andChannelPipeline
APIs - Detecting resource leaks
- Exception handling
The ChannelHandler family
The Channel lifecycle
Interface Channel
defines a simple but powerful state model that’s closely related to the ChannelInboundHandler
API. The four Channel
states are listed in the following table.
The normal lifecycle of a Channel
is shown in figure 6.1.
The ChannelHandler lifecycle
The lifecycle operations defined by interface ChannelHandler
, listed in the following table, are called after a ChannelHandler
has been added to, or removed from, a ChannelPipeline
.
Netty defines the following two important subinterfaces of ChannelHandler
:
ChannelInboundHandler
— Processes inbound data and state changes of all kindsChannelOutboundHandler
— Processes outbound data and allows interception of all operations
Interface ChannelInboundHandler
The following table lists the lifecycle methods of interface ChannelInboundHandler
. These are called when data is received or when the state of the associated Channel
changes.
When a ChannelInboundHandler
implementation overrides channelRead()
, it is responsible for explicitly releasing the memory associated with pooled ByteBuf
instances. Netty provides a utility method for this purpose, ReferenceCountUtil.release()
, as shown next.
A simpler alternative is to use SimpleChannelInboundHandler
.
Because SimpleChannelInboundHandler
releases resources automatically, you shouldn’t store references to any messages for later use, as these will become invalid.
Interface ChannelOutboundHandler
The following table shows all of the methods defined locally by ChannelOutboundHandler
(leaving out those inherited from ChannelHandler
).
CHANNELPROMISE VS. CHANNELFUTURE
Most of the methods inChannelOutboundHandler
take aChannelPromise
argument to be notified when the operation completes.ChannelPromise
is a subinterface ofChannelFuture
that defines the writable methods, such assetSuccess()
orsetFailure()
, thus makingChannelFuture
immutable.
ChannelHandler adapters
The resulting class hierarchy is shown in figure 6.2.
The method bodies provided in ChannelInboundHandlerAdapter
and ChannelOutboundHandlerAdapter
call the equivalent methods on the associated ChannelHandlerContext
, thereby forwarding events to the next ChannelHandler
in the pipeline.
Resource management
Netty provides class ResourceLeakDetector
, which will sample about 1% of your application’s buffer allocations to check for memory leaks. If a leak is detected, a log message similar to the following will be produced:
1 | LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable |
Netty currently defines the four leak detection levels, as listed in the following table.
The leak-detection level is defined by setting the following Java system property to one of the values in the table:1
java -Dio.netty.leakDetectionLevel=ADVANCED
This listing shows how to release the message.
On the outbound side, if you handle a write()
operation and discard a message, you’re responsible for releasing it. The next listing shows an implementation that discards all written data.
It’s important not only to release resources but also to notify the ChannelPromise
. Otherwise a situation might arise where a ChannelFutureListener
has not been notified about a message that has been handled.
In sum, it is the responsibility of the user to call ReferenceCountUtil.release()
if a message is consumed or discarded and not passed to the next ChannelOutboundHandler
in the ChannelPipeline
. If the message reaches the actual transport layer, it will be released automatically when it’s written or the Channel
is closed.
Interface ChannelPipeline
Every new Channel
that’s created is assigned a new ChannelPipeline
. This association is permanent.
ChannelHandlerContext
AChannelHandlerContext
enables aChannelHandler
to interact with itsChannelPipeline
and with other handlers. A handler can notify the nextChannelHandler
in theChannelPipeline
and even dynamically modify theChannelPipeline
it belongs to.
Modifying a ChannelPipeline
A ChannelHandler
can modify the layout of a ChannelPipeline
in real time by adding, removing, or replacing other ChannelHandlers
. (It can remove itself from the ChannelPipeline
as well.) The relevant methods are listed in the following table.
This listing shows these methods in use.
ChannelHandler execution and blocking
Normally eachChannelHandler
in theChannelPipeline
processes events that are passed to it by itsEventLoop
(the I/O thread). It’s critically important not to block this thread as it would have a negative effect on the overall handling of I/O.
The following table shows the ChannelPipeline
operations for accessing ChannelHandlers
.
Firing events
The following table lists the inbound operations, which notify ChannelInboundHandlers
of events occurring in the ChannelPipeline
.
The following table lists the outbound operations of the ChannelPipeline
API.
In summary,
- A
ChannelPipeline
holds theChannelHandlers
associated with aChannel
. - A
ChannelPipeline
can be modified dynamically by adding and removingChannelHandlers
as needed. ChannelPipeline
has a rich API for invoking actions in response to inbound and outbound events.
Interface ChannelHandlerContext
A ChannelHandlerContext
represents an association between a ChannelHandler
and a ChannelPipeline
and is created whenever a ChannelHandler
is added to a ChannelPipeline
.
The methods called on a ChannelHandlerContext
will start at the current associated ChannelHandler
and propagate only to the next ChannelHandler
in the pipeline that is capable of handling the event.
The following table summarizes the ChannelHandlerContext
API.
When using the ChannelHandlerContext
API, please keep the following points in mind:
- The
ChannelHandlerContext
associated with aChannelHandler
never changes, so it’s safe to cache a reference to it. ChannelHandlerContext
methods involve a shorter event flow than do the identically named methods available on other classes. This should be exploited where possible to provide maximum performance.
Using ChannelHandlerContext
Figure 6.4 shows the relationships.
In the following listing you acquire a reference to the Channel from a ChannelHandlerContext
. Calling write()
on the Channel
causes a write event to flow all the way through the pipeline.
The next listing shows a similar example, but writing this time to a ChannelPipeline
.
As you can see in figure 6.5, the flows in listings 6.6 and 6.7 are identical. It’s important to note that although the write()
invoked on either the Channel
or the ChannelPipeline
operation propagates the event all the way through the pipeline, the movement from one handler to the next at the ChannelHandler
level is invoked on the ChannelHandlerContext
.
If you want to propagate an event starting at a specific point in the ChannelPipeline
, the following listing and figure 6.6 illustrate this use.
As shown in figure 6.6, the message flows through the ChannelPipeline
starting at the next ChannelHandler
, bypassing all the preceding ones.
Advanced uses of ChannelHandler and ChannelHandlerContext
The following list shows caching a ChannelHandlerContext
.
Because a ChannelHandler
can belong to more than one ChannelPipeline
, it can be bound to multiple ChannelHandlerContext
instances. A ChannelHandler
intended for this use must be annotated with @Sharable
; otherwise, attempting to add it to more than one ChannelPipeline
will trigger an exception. This listing shows a correct implementation of this pattern.
Conversely, the code in listing 6.11 will cause problems.
In summary, use @Sharable
only if you’re certain that your ChannelHandler
is thread-safe.
Exception handling
Handling inbound exceptions
The following listing shows a simple example that closes the Channel
and prints the exception’s stack trace.
To summarize,
- The default implementation of
ChannelHandler.exceptionCaught()
forwards the current exception to the next handler in the pipeline. - If an exception reaches the end of the pipeline, it’s logged as unhandled.
- To define custom handling, you override
exceptionCaught()
. It’s then your decision whether to propagate the exception beyond that point.
Handling outbound exceptions
Notification mechanisms:
- Every outbound operation returns a
ChannelFuture
. TheChannelFutureListeners
registered with aChannelFuture
are notified of success or error when the operation completes. - Almost all methods of
ChannelOutboundHandler
are passed an instance ofChannelPromise
. As a subclass ofChannelFuture
,ChannelPromise
can also be assigned listeners for asynchronous notification. ButChannelPromise
also has writable methods that provide for immediate notification:1
2ChannelPromise setSuccess();
ChannelPromise setFailure(Throwable cause);
The following listing uses this approach to add a ChannelFutureListener
that will print the stack trace and then close the Channel
.
The code shown next will have the same effect as the previous listing.