Repository: netty/netty
Issue: IovArray.add(ByteBuf) throws if buffer is a composite of a single direct buffer

### Expected behavior

Not throwing unhandled `UnsupportedOperationException`.

### Actual behavior

Method throws unhandled `UnsupportedOperationException` which causes channel write operations to fail.

### Steps to reproduce

The problem is here: https://github.com/netty/netty/blob/4.1/transport-native-unix-common/src/main/java/io/netty/channel/unix/IovArray.java#L96

If `buf` is a `FixedCompositeByteBuf` with one direct buffer component, it will return `nioBufferCount() == 1` and `memoryAddress()` will throw `UnsupportedOperationException`. 

I can see two possible, complimentary fixes:

1. Make `memoryAddress()` work for composites that are one direct buffer
2. Change this if branch condition to `buf.nioBufferCount() == 1 && buf.hasMemoryAddress()`

Here's a stack trace showing the problem:

```
io.netty.channel.socket.ChannelOutputShutdownException: Channel output shutdown
	at io.netty.channel.AbstractChannel$AbstractUnsafe.shutdownOutput(AbstractChannel.java:641)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.flush0(AbstractChannel.java:948)
	at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.flush0(AbstractEpollChannel.java:543)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.flush(AbstractChannel.java:901)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.flush(DefaultChannelPipeline.java:1321)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:776)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:768)
	at io.netty.channel.AbstractChannelHandlerContext.flush(AbstractChannelHandlerContext.java:749)
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.flush(CombinedChannelDuplexHandler.java:533)
	at io.netty.channel.ChannelOutboundHandlerAdapter.flush(ChannelOutboundHandlerAdapter.java:115)
	at io.netty.channel.CombinedChannelDuplexHandler.flush(CombinedChannelDuplexHandler.java:358)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:776)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:768)
	at io.netty.channel.AbstractChannelHandlerContext.flush(AbstractChannelHandlerContext.java:749)
	at io.netty.channel.ChannelDuplexHandler.flush(ChannelDuplexHandler.java:117)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:776)
	at io.netty.channel.AbstractChannelHandlerContext.invokeWriteAndFlush(AbstractChannelHandlerContext.java:802)
	at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:814)
	at io.netty.channel.AbstractChannelHandlerContext.writeAndFlush(AbstractChannelHandlerContext.java:794)
	at io.netty.channel.AbstractChannelHandlerContext.writeAndFlush(AbstractChannelHandlerContext.java:831)
	at io.netty.channel.DefaultChannelPipeline.writeAndFlush(DefaultChannelPipeline.java:1041)
	at io.netty.channel.AbstractChannel.writeAndFlush(AbstractChannel.java:300)
	at ratpack.http.client.internal.RequestActionSupport.lambda$send$4(RequestActionSupport.java:137)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:507)
	at io.netty.util.concurrent.DefaultPromise.notifyListenerWithStackOverFlowProtection(DefaultPromise.java:450)
	at io.netty.util.concurrent.DefaultPromise.notifyListener(DefaultPromise.java:409)
	at io.netty.util.concurrent.CompleteFuture.addListener(CompleteFuture.java:49)
	at io.netty.channel.CompleteChannelFuture.addListener(CompleteChannelFuture.java:56)
	at io.netty.channel.CompleteChannelFuture.addListener(CompleteChannelFuture.java:27)
	at ratpack.http.client.internal.RequestActionSupport.send(RequestActionSupport.java:135)
	at ratpack.http.client.internal.RequestActionSupport.lambda$null$0(RequestActionSupport.java:99)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:507)
	at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:481)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:420)
	at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:104)
	at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:82)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.safeSetSuccess(AbstractChannel.java:978)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:512)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.access$200(AbstractChannel.java:423)
	at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:482)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:403)
	at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:309)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
	at ratpack.exec.internal.DefaultExecController$ExecControllerBindingThreadFactory.lambda$newThread$0(DefaultExecController.java:136)
	at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.UnsupportedOperationException: null
	at io.netty.buffer.FixedCompositeByteBuf.memoryAddress(FixedCompositeByteBuf.java:635)
	at io.netty.buffer.ReadOnlyByteBuf.memoryAddress(ReadOnlyByteBuf.java:121)
	at io.netty.channel.unix.IovArray.add(IovArray.java:94)
	at io.netty.channel.unix.IovArray.processMessage(IovArray.java:209)
	at io.netty.channel.ChannelOutboundBuffer.forEachFlushedMessage(ChannelOutboundBuffer.java:738)
	at io.netty.channel.epoll.AbstractEpollStreamChannel.doWriteMultiple(AbstractEpollStreamChannel.java:500)
	at io.netty.channel.epoll.AbstractEpollStreamChannel.doWrite(AbstractEpollStreamChannel.java:442)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.flush0(AbstractChannel.java:934)
	at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.flush0(AbstractEpollChannel.java:543)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.flush(AbstractChannel.java:901)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.flush(DefaultChannelPipeline.java:1321)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:776)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:768)
	at io.netty.channel.AbstractChannelHandlerContext.flush(AbstractChannelHandlerContext.java:749)
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.flush(CombinedChannelDuplexHandler.java:533)
	at io.netty.channel.ChannelOutboundHandlerAdapter.flush(ChannelOutboundHandlerAdapter.java:115)
	at io.netty.channel.CombinedChannelDuplexHandler.flush(CombinedChannelDuplexHandler.java:358)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:776)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:768)
	at io.netty.channel.AbstractChannelHandlerContext.flush(AbstractChannelHandlerContext.java:749)
	at io.netty.channel.ChannelDuplexHandler.flush(ChannelDuplexHandler.java:117)
	at io.netty.channel.AbstractChannelHandlerContext.invokeFlush0(AbstractChannelHandlerContext.java:776)
	at io.netty.channel.AbstractChannelHandlerContext.invokeWriteAndFlush(AbstractChannelHandlerContext.java:802)
	at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:814)
	at io.netty.channel.AbstractChannelHandlerContext.writeAndFlush(AbstractChannelHandlerContext.java:794)
	at io.netty.channel.AbstractChannelHandlerContext.writeAndFlush(AbstractChannelHandlerContext.java:831)
	at io.netty.channel.DefaultChannelPipeline.writeAndFlush(DefaultChannelPipeline.java:1041)
	at io.netty.channel.AbstractChannel.writeAndFlush(AbstractChannel.java:300)
	at ratpack.http.client.internal.RequestActionSupport.lambda$send$4(RequestActionSupport.java:137)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:507)
	at io.netty.util.concurrent.DefaultPromise.notifyListenerWithStackOverFlowProtection(DefaultPromise.java:450)
	at io.netty.util.concurrent.DefaultPromise.notifyListener(DefaultPromise.java:409)
	at io.netty.util.concurrent.CompleteFuture.addListener(CompleteFuture.java:49)
	at io.netty.channel.CompleteChannelFuture.addListener(CompleteChannelFuture.java:56)
	at io.netty.channel.CompleteChannelFuture.addListener(CompleteChannelFuture.java:27)
	at ratpack.http.client.internal.RequestActionSupport.send(RequestActionSupport.java:135)
	at ratpack.http.client.internal.RequestActionSupport.lambda$null$0(RequestActionSupport.java:99)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:507)
	at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:481)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:420)
	at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:104)
	at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:82)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.safeSetSuccess(AbstractChannel.java:978)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:512)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.access$200(AbstractChannel.java:423)
	at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:482)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:403)
	at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:309)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
```

### Netty version

4.1.21.

Issue comments:
@ldaley sorry took me a while due travelling / meetings etc.

https://github.com/netty/netty/pull/7773

@normanmaurer no problem sir. Appreciated all the same.
And https://github.com/netty/netty/pull/7774
Fixed

Pull request review comments:

Patch that resolved the issue:
diff --git a/transport-native-unix-common/src/main/java/io/netty/channel/unix/IovArray.java b/transport-native-unix-common/src/main/java/io/netty/channel/unix/IovArray.java
index 3e0946e255e..151abe609ae 100644
--- a/transport-native-unix-common/src/main/java/io/netty/channel/unix/IovArray.java
+++ b/transport-native-unix-common/src/main/java/io/netty/channel/unix/IovArray.java
@@ -91,7 +91,7 @@ public boolean add(ByteBuf buf) {
         if (count == IOV_MAX) {
             // No more room!
             return false;
-        } else if (buf.nioBufferCount() == 1) {
+        } else if (buf.hasMemoryAddress() && buf.nioBufferCount() == 1) {
             final int len = buf.readableBytes();
             return len == 0 || add(buf.memoryAddress(), buf.readerIndex(), len);
         } else {


Test patch: