Skip to content
This repository was archived by the owner on Dec 12, 2022. It is now read-only.

Commit 78f04ee

Browse files
committed
Update docs and examples
1 parent 1b65bf9 commit 78f04ee

File tree

7 files changed

+175
-37
lines changed

7 files changed

+175
-37
lines changed

README.adoc

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
= Netty Incubator Buffer API
2+
3+
This repository is incubating a new buffer API proposed for Netty 5.
4+
5+
== Building and Testing
6+
7+
Short version: just run `make`.
8+
9+
The project currently relies on snapshot versions of the https://github.com/openjdk/panama-foreign[Panama Foreign] fork of OpenJDK.
10+
This allows us to test out the most recent version of the `jdk.incubator.foreign` APIs, but also make building, and local development more involved.
11+
To simplify things, we have a Docker based build, controlled via a Makefile with the following commands:
12+
13+
* `image` – build the docker image.This includes building a snapshot of OpenJDK, and download all relevant Maven dependencies.
14+
* `test` – run all tests in a docker container.This implies `image`.The container is automatically deleted afterwards.
15+
* `dbg` – drop into a shell in the build container, without running the build itself.The debugging container is not deleted afterwards.
16+
* `clean` – remove the leftover containers created by `dbg`, `test`, and `build`.
17+
* `build` – build binaries and run all tests in a container, and copy the `target` directory out of the container afterwards.This is the default build target.
18+
19+
== Example: Echo Client and Server
20+
21+
Making use of this new buffer API on the client side is quite easy.
22+
Even though Netty 5 does not have native support for these buffers, it is able to convert them to the old `ByteBuf` API as needed.
23+
This means we are able to send incubator buffers through a Netty pipeline, and have it work as if we were sending `ByteBuf` instances.
24+
25+
[source,java]
26+
----
27+
public final class Client {
28+
public static void main(String[] args) throws Exception {
29+
EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory());
30+
try (BufferAllocator allocator = BufferAllocator.pooledDirect()) { // <1>
31+
Bootstrap b = new Bootstrap();
32+
b.group(group)
33+
.channel(NioSocketChannel.class)
34+
.option(ChannelOption.TCP_NODELAY, true)
35+
.handler(new ChannelInitializer<SocketChannel>() {
36+
@Override
37+
public void initChannel(SocketChannel ch) throws Exception {
38+
ch.pipeline().addLast(new ChannelHandlerAdapter() {
39+
@Override
40+
public void channelActive(ChannelHandlerContext ctx) {
41+
Buffer message = allocator.allocate(256); // <2>
42+
for (int i = 0; i < message.capacity(); i++) {
43+
message.writeByte((byte) i);
44+
}
45+
ctx.writeAndFlush(message); // <3>
46+
}
47+
});
48+
}
49+
});
50+
51+
// Start the client.
52+
ChannelFuture f = b.connect("127.0.0.1", 8007).sync();
53+
54+
// Wait until the connection is closed.
55+
f.channel().closeFuture().sync();
56+
} finally {
57+
// Shut down the event loop to terminate all threads.
58+
group.shutdownGracefully();
59+
}
60+
}
61+
}
62+
----
63+
<1> A life-cycled allocator is created to wrap the scope of our application.
64+
<2> Buffers are allocated with one of the `allocate` methods.
65+
<3> The buffer can then be sent down the pipeline, and will be written to the socket just like a `ByteBuf` would.
66+
67+
[NOTE]
68+
--
69+
The same is not the case for `BufferHolder`.
70+
It is not treated the same as a `ByteBufHolder`.
71+
--
72+
73+
On the server size, things are more complicated because Netty itself will be allocating the buffers, and the `ByteBufAllocator` API is only capable of returning `ByteBuf` instances.
74+
The `ByteBufAllocatorAdaptor` will allocate `ByteBuf` instances that are backed by the new buffers.
75+
The buffers can then we extracted from the `ByteBuf` instances with the `ByteBufAdaptor.extract` method.
76+
77+
We can tell a Netty server how to allocate buffers by setting the `ALLOCATOR` child-channel option:
78+
79+
[source,java]
80+
----
81+
ByteBufAllocatorAdaptor allocator = new ByteBufAllocatorAdaptor(); // <1>
82+
ServerBootstrap server = new ServerBootstrap();
83+
server.group(bossGroup, workerGroup)
84+
.channel(NioServerSocketChannel.class)
85+
.childOption(ChannelOption.ALLOCATOR, allocator) // <2>
86+
.handler(new EchoServerHandler());
87+
----
88+
<1> The `ByteBufAllocatorAdaptor` implements `ByteBufAllocator`, and directly allocates `ByteBuf` instances that are backed by buffers that use the new API.
89+
<2> To make Netty use a given allocator when allocating buffers for receiving data, we set the allocator as a child option.
90+
91+
With the above, we just changed how the buffers are allocated, but we haven't changed the API we use for interacting with the buffers.
92+
The buffers are still allocated at `ByteBuf` instances, and flow through the pipeline as such.
93+
If we want to use the new buffer API in our server handlers, we have to extract the buffers from the `ByteBuf` instances that are passed down:
94+
95+
[source,java]
96+
----
97+
import io.netty.buffer.ByteBuf;
98+
import io.netty.buffer.api.Buffer;
99+
import io.netty.buffer.api.adaptor.ByteBufAdaptor;
100+
101+
@Sharable
102+
public class EchoServerHandler implements ChannelHandler {
103+
@Override
104+
public void channelRead(ChannelHandlerContext ctx, Object msg) { // <1>
105+
if (msg instanceof ByteBuf) { // <2>
106+
// For this example, we only echo back buffers that are using the new buffer API.
107+
Buffer buf = ByteBufAdaptor.extract((ByteBuf) msg); // <3>
108+
ctx.write(buf); // <4>
109+
}
110+
}
111+
112+
@Override
113+
public void channelReadComplete(ChannelHandlerContext ctx) {
114+
ctx.flush();
115+
}
116+
}
117+
----
118+
<1> Netty pipelines are defined as transferring `Object` instances as messages.
119+
<2> When we receive data directly from a socket, these messages will be `ByteBuf` instances with the received data.
120+
<3> Since we set the allocator to create `ByteBuf` instances that are backed by buffers with the new API, we will be able to extract the backing `Buffer` instances.
121+
<4> We can then operate on the extracted `Buffer` instances directly.
122+
The `Buffer` and `ByteBuf` instances mirror each other exactly.
123+
In this case, we just write them back to the client that sent the data to us.
124+
125+
The files in `src/test/java/io/netty/buffer/api/examples/echo` for the full source code to this example.

README.md

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/main/java/io/netty/buffer/api/adaptor/ByteBufAdaptor.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import java.nio.channels.ScatteringByteChannel;
3434
import java.nio.charset.Charset;
3535

36-
public class ByteBufAdaptor extends ByteBuf {
36+
public final class ByteBufAdaptor extends ByteBuf {
3737
private final ByteBufAllocatorAdaptor alloc;
3838
private final Buffer buffer;
3939

@@ -42,6 +42,23 @@ public ByteBufAdaptor(ByteBufAllocatorAdaptor alloc, Buffer buffer) {
4242
this.buffer = buffer;
4343
}
4444

45+
/**
46+
* Extracts the underlying {@link Buffer} instance that is backing this {@link ByteBuf}, if any.
47+
* This is similar to {@link #unwrap()} except the return type is a {@link Buffer}.
48+
* If this {@link ByteBuf} does not wrap a {@link Buffer}, then {@code null} is returned.
49+
*
50+
* @param byteBuf The {@link ByteBuf} to extract the {@link Buffer} from.
51+
* @return The {@link Buffer} instance that is backing the given {@link ByteBuf}, or {@code null} if the given
52+
* {@link ByteBuf} is not backed by a {@link Buffer}.
53+
*/
54+
public static Buffer extract(ByteBuf byteBuf) {
55+
if (byteBuf instanceof ByteBufAdaptor) {
56+
ByteBufAdaptor bba = (ByteBufAdaptor) byteBuf;
57+
return bba.buffer;
58+
}
59+
return null;
60+
}
61+
4562
@Override
4663
public int capacity() {
4764
return buffer.capacity();
@@ -1005,7 +1022,9 @@ public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
10051022
return true;
10061023
});
10071024
int read = (int) in.read(components);
1008-
writerIndex(read + writerIndex());
1025+
if (read > 0) {
1026+
writerIndex(read + writerIndex());
1027+
}
10091028
return read;
10101029
}
10111030

@@ -1019,7 +1038,10 @@ public int writeBytes(FileChannel in, long position, int length) throws IOExcept
10191038
});
10201039
int read = 0;
10211040
for (ByteBuffer component : components) {
1022-
read += in.read(component, position + read);
1041+
int r = in.read(component, position + read);
1042+
if (r > 0) {
1043+
read += r;
1044+
}
10231045
if (component.hasRemaining()) {
10241046
break;
10251047
}

src/main/java/io/netty/buffer/api/adaptor/ByteBufAllocatorAdaptor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ public ByteBuf directBuffer() {
9898

9999
@Override
100100
public ByteBuf directBuffer(int initialCapacity) {
101-
return new ByteBufAdaptor(this, offheap.allocate(initialCapacity));
101+
// TODO we cannot use off-heap buffers here, until the JDK allows direct byte buffers based on native
102+
// memory segments to be used in IO operations.
103+
return new ByteBufAdaptor(this, onheap.allocate(initialCapacity));
102104
}
103105

104106
@Override

src/test/java/io/netty/buffer/api/EchoIT.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,18 @@ void echoServerMustReplyWithSameData() throws Exception {
5353
try {
5454
ServerBootstrap server = new ServerBootstrap();
5555
server.group(bossGroup, workerGroup)
56-
.channel(NioServerSocketChannel.class)
57-
.option(ChannelOption.ALLOCATOR, allocator)
58-
.option(ChannelOption.SO_BACKLOG, 100)
59-
.handler(new LoggingHandler(LogLevel.INFO))
60-
.childHandler(new ChannelInitializer<SocketChannel>() {
61-
@Override
62-
public void initChannel(SocketChannel ch) throws Exception {
63-
ChannelPipeline p = ch.pipeline();
64-
p.addLast(new LoggingHandler(LogLevel.INFO));
65-
p.addLast(serverHandler);
66-
}
67-
});
56+
.channel(NioServerSocketChannel.class)
57+
.childOption(ChannelOption.ALLOCATOR, allocator)
58+
.option(ChannelOption.SO_BACKLOG, 100)
59+
.handler(new LoggingHandler(LogLevel.INFO))
60+
.childHandler(new ChannelInitializer<SocketChannel>() {
61+
@Override
62+
public void initChannel(SocketChannel ch) throws Exception {
63+
ChannelPipeline p = ch.pipeline();
64+
p.addLast(new LoggingHandler(LogLevel.INFO));
65+
p.addLast(serverHandler);
66+
}
67+
});
6868

6969
// Start the server.
7070
ChannelFuture bind = server.bind("localhost", 0).sync();
@@ -115,7 +115,7 @@ static class EchoClientHandler implements ChannelHandler {
115115
*/
116116
EchoClientHandler() {
117117
firstMessage = BufferAllocator.heap().allocate(SIZE);
118-
for (int i = 0; i < SIZE; i ++) {
118+
for (int i = 0; i < SIZE; i++) {
119119
firstMessage.writeByte((byte) i);
120120
}
121121
}

src/test/java/io/netty/buffer/api/examples/echo/EchoServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public static void main(String[] args) throws Exception {
5959
ServerBootstrap b = new ServerBootstrap();
6060
b.group(bossGroup, workerGroup)
6161
.channel(NioServerSocketChannel.class)
62-
.option(ChannelOption.ALLOCATOR, allocator)
62+
.childOption(ChannelOption.ALLOCATOR, allocator)
6363
.option(ChannelOption.SO_BACKLOG, 100)
6464
.handler(new LoggingHandler(LogLevel.INFO))
6565
.childHandler(new ChannelInitializer<SocketChannel>() {

src/test/java/io/netty/buffer/api/examples/echo/EchoServerHandler.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package io.netty.buffer.api.examples.echo;
1717

18+
import io.netty.buffer.ByteBuf;
19+
import io.netty.buffer.api.Buffer;
20+
import io.netty.buffer.api.adaptor.ByteBufAdaptor;
1821
import io.netty.channel.ChannelHandler;
1922
import io.netty.channel.ChannelHandler.Sharable;
2023
import io.netty.channel.ChannelHandlerContext;
@@ -24,10 +27,13 @@
2427
*/
2528
@Sharable
2629
public class EchoServerHandler implements ChannelHandler {
27-
2830
@Override
2931
public void channelRead(ChannelHandlerContext ctx, Object msg) {
30-
ctx.write(msg);
32+
if (msg instanceof ByteBuf) {
33+
// For this example, we only echo back buffers that are using the new buffer API.
34+
Buffer buf = ByteBufAdaptor.extract((ByteBuf) msg);
35+
ctx.write(buf);
36+
}
3137
}
3238

3339
@Override

0 commit comments

Comments
 (0)