├── .dockerignore ├── .github └── workflows │ ├── ci-test-results.yml │ └── ci-workflow.yml ├── .gitignore ├── Dockerfile ├── Makefile ├── RATIONALE.adoc ├── README.adoc ├── pom.xml └── src ├── main ├── java │ ├── io │ │ └── netty5 │ │ │ └── buffer │ │ │ └── memseg │ │ │ ├── MemSegBuffer.java │ │ │ ├── ReduceNativeMemoryUsage.java │ │ │ ├── SegmentMemoryManager.java │ │ │ └── package-info.java │ └── module-info.java.disabled └── resources │ └── META-INF │ └── services │ └── io.netty.buffer.MemoryManager └── test └── java └── io └── netty5 └── buffer ├── memseg └── benchmarks │ ├── MemSegBufAccessBenchmark.java │ ├── MemorySegmentCloseBenchmark.java │ └── MemorySegmentClosedByCleanerBenchmark.java └── tests ├── EchoIT.java ├── benchmarks ├── ByteIterationBenchmark.java └── SendBenchmark.java └── examples ├── AsyncExample.java ├── ComposingAndSplittingExample.java ├── FileCopyExample.java ├── SendExample.java ├── echo ├── EchoClient.java ├── EchoClientHandler.java ├── EchoServer.java └── EchoServerHandler.java └── http └── snoop ├── HttpSnoopClient.java ├── HttpSnoopClientHandler.java ├── HttpSnoopClientInitializer.java ├── HttpSnoopServer.java ├── HttpSnoopServerHandler.java └── HttpSnoopServerInitializer.java /.dockerignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.iml 3 | target 4 | .idea 5 | *.jfr 6 | .git 7 | -------------------------------------------------------------------------------- /.github/workflows/ci-test-results.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Copyright 2021 The Netty Project 3 | # 4 | # The Netty Project licenses this file to you under the Apache License, 5 | # version 2.0 (the "License"); you may not use this file except in compliance 6 | # with the License. You may obtain a copy of the License at: 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | # ---------------------------------------------------------------------------- 16 | name: Upload Test Results 17 | 18 | on: 19 | workflow_run: 20 | workflows: ["Build"] 21 | types: [completed] 22 | 23 | jobs: 24 | test_reports: 25 | runs-on: ubuntu-latest 26 | strategy: 27 | matrix: 28 | java: [java19] 29 | steps: 30 | - name: Download Artifacts 31 | uses: dawidd6/action-download-artifact@v2.14.0 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | workflow: ${{ github.event.workflow_run.workflow_id }} 35 | workflow_conclusion: completed 36 | commit: ${{ github.event.workflow_run.head_commit.id }} 37 | # The artefact name must be coordinated with the "Upload * Test Results" steps in ci-workflow.yml. 38 | name: test-results-${{ matrix.java }} 39 | - name: Publish Test Report 40 | uses: scacap/action-surefire-report@v1.0.10 41 | with: 42 | github_token: ${{ secrets.GITHUB_TOKEN }} 43 | report_paths: '**/target/surefire-reports/TEST-*.xml' 44 | commit: ${{ github.event.workflow_run.head_commit.id }} 45 | check_name: Test results ${{ matrix.java }} 46 | -------------------------------------------------------------------------------- /.github/workflows/ci-workflow.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Copyright 2021 The Netty Project 3 | # 4 | # The Netty Project licenses this file to you under the Apache License, 5 | # version 2.0 (the "License"); you may not use this file except in compliance 6 | # with the License. You may obtain a copy of the License at: 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | # ---------------------------------------------------------------------------- 16 | name: Build 17 | 18 | on: 19 | push: 20 | branches: [ main ] 21 | pull_request: 22 | branches: [ main ] 23 | workflow_dispatch: # This allows us to run the workflow manually from the Actions tab 24 | 25 | concurrency: 26 | group: ${{ github.workflow }}-${{ github.ref }} 27 | cancel-in-progress: true 28 | 29 | jobs: 30 | java19: 31 | runs-on: ubuntu-latest 32 | steps: 33 | # http://man7.org/linux/man-pages/man1/date.1.html 34 | - name: Create Cache Key 35 | id: cache-key 36 | run: | 37 | echo "::set-output name=key::$(/bin/date -u "+%Y%U-2")" 38 | shell: bash 39 | 40 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 41 | - uses: actions/checkout@v2 42 | 43 | # Enable caching of Docker layers 44 | - uses: satackey/action-docker-layer-caching@v0.0.11 45 | continue-on-error: true 46 | with: 47 | key: docker-cache-${{ steps.cache-key.outputs.key }}-{hash} 48 | restore-keys: | 49 | docker-cache-${{ steps.cache-key.outputs.key }}- 50 | 51 | # Run the make script 52 | - name: Make build 53 | run: make build 54 | - name: Upload Java 19 Test Results 55 | if: always() 56 | uses: actions/upload-artifact@v2 57 | with: 58 | name: test-results-java19 59 | path: '**/target/surefire-reports/TEST-*.xml' 60 | - name: Upload build artefacts 61 | uses: actions/upload-artifact@v2 62 | if: ${{ failure() }} 63 | with: 64 | name: artifacts 65 | path: target/ 66 | # Make room for the docker layer caching to package up layers 67 | - name: Cleanup 68 | run: rm -fr * 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse project files 2 | .project 3 | .classpath 4 | .settings 5 | 6 | # IntelliJ IDEA project files and directories 7 | *.iml 8 | *.ipr 9 | *.iws 10 | .idea/ 11 | .shelf/ 12 | 13 | # Geany project file 14 | .geany 15 | 16 | # KDevelop project file and directory 17 | .kdev4/ 18 | *.kdev4 19 | 20 | # Build targets 21 | /target 22 | */target 23 | 24 | # Report directories 25 | /reports 26 | */reports 27 | 28 | # Mac-specific directory that no other operating system needs. 29 | .DS_Store 30 | 31 | # JVM crash logs 32 | hs_err_pid*.log 33 | 34 | dependency-reduced-pom.xml 35 | 36 | */.unison.* 37 | 38 | # exclude mainframer files 39 | mainframer 40 | .mainframer 41 | 42 | # exclude docker-sync stuff 43 | .docker-sync 44 | */.docker-sync 45 | 46 | # exclude vscode files 47 | .vscode/ 48 | *.factorypath 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Prepare environment 2 | FROM fedora:35 3 | RUN dnf -y install file findutils unzip zip libXtst-devel libXt-devel libXrender-devel libXrandr-devel \ 4 | libXi-devel cups-devel fontconfig-devel alsa-lib-devel make autoconf diffutils git clang \ 5 | java-latest-openjdk-devel automake libtool 6 | 7 | # Build panama-foreign openjdk 8 | WORKDIR /home/build 9 | RUN git clone --depth 1 --branch foreign-memaccess+abi https://github.com/openjdk/panama-foreign.git panama-foreign 10 | WORKDIR /home/build/panama-foreign 11 | RUN chmod +x configure 12 | RUN ./configure --with-debug-level=fastdebug \ 13 | --with-toolchain-type=clang \ 14 | --with-vendor-name=jackalope \ 15 | --enable-warnings-as-errors=no 16 | RUN make images && mv build/linux-*-server-fastdebug/images/jdk /home/jdk && rm -fr * 17 | ENV JAVA_HOME="/home/jdk" 18 | 19 | # Prepare our own build environment 20 | WORKDIR /home/build 21 | RUN curl https://downloads.apache.org/maven/maven-3/3.8.6/binaries/apache-maven-3.8.6-bin.tar.gz | tar -xz 22 | ENV PATH=/home/build/apache-maven-3.8.6/bin:$PATH 23 | 24 | # Prepare a snapshot of Netty 5 25 | RUN git clone --depth 1 -b main https://github.com/netty/netty.git netty \ 26 | && cd netty \ 27 | && mvn install -DskipTests -T1C -Pfast -B -am \ 28 | && cd .. \ 29 | && rm -fr netty 30 | 31 | # Make sure Maven has enough memory to keep track of all the tests we run 32 | ENV MAVEN_OPTS="-Xmx4g -XX:+HeapDumpOnOutOfMemoryError" 33 | 34 | # Prepare our own build 35 | COPY pom.xml pom.xml 36 | RUN mvn --version 37 | RUN mvn install dependency:go-offline surefire:test checkstyle:check -ntp 38 | 39 | # Copy over the project code and run our build 40 | COPY . . 41 | # Run tests 42 | CMD mvn verify -o -B -C -T1C -fae -nsu 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: image test dbg clean build rebuild 2 | .DEFAULT_GOAL := build 3 | 4 | image: 5 | docker build $(DOCKER_BUILD_OPTS) --tag netty-incubator-buffer:build . 6 | 7 | test: image 8 | docker run --rm --name build-container netty-incubator-buffer:build 9 | 10 | dbg: 11 | docker create --name build-container-dbg --entrypoint /bin/bash -t netty-incubator-buffer:build 12 | docker start build-container-dbg 13 | docker exec -it build-container-dbg bash 14 | 15 | clean: 16 | docker rm -fv build-container-dbg 17 | docker rm -fv build-container 18 | 19 | clean-layer-cache: 20 | docker builder prune -f -a 21 | 22 | build: image 23 | docker create --name build-container netty-incubator-buffer:build 24 | mkdir -p target/container-output 25 | docker start -a build-container || (docker cp build-container:/home/build target/container-output && false) 26 | docker wait build-container || (docker cp build-container:/home/build target/container-output && false) 27 | docker cp build-container:/home/build/target . 28 | docker rm build-container 29 | 30 | rebuild: clean clean-layer-cache build 31 | -------------------------------------------------------------------------------- /RATIONALE.adoc: -------------------------------------------------------------------------------- 1 | = A Propsed new Buffer API for Netty 2 | :toc: 3 | 4 | In the Netty team, we have been working a new buffer API, in preparation for Netty 5. 5 | In this document, we wish to introduce you to the main changes in this new API, the reasons behind them, and the principles guiding us. 6 | The new API is not yet done, and is still subject to change (especially in response to the feedback we’ll receive here), but we believe that we are far enough along that we have something tangible to show. 7 | We hope that you will use this opportunity to see how the new API might work for your use cases, and provide feedback. 8 | There are many tensions to balance, when designing an API that is going to see such wide-spread use, and it’s important that we don’t lock anyone out of upgrading, by accidentally making their use case unreasonably hard to implement, or cause it to have an unacceptable performance hit. 9 | 10 | == How we got here 11 | 12 | The existing Netty ByteBuf API has been around for a long time, and grown organically over the years. 13 | The API surface has become large, with multiple ways of doing the same things, with varying degree of consistency. 14 | None of the APIs and implementations were designed to make use of anything introduced after Java 6. 15 | Take, reference counting, for example. 16 | It could not be implemented in a way that takes advantage of the try-with-resources clause that was introduced in Java 7. 17 | We have also ended up with a proliferation of buffer implementation classes, and a tall class hierarchy. 18 | Both aspects of this makes it harder for the JIT compiler to optimise integrating code, and adds overhead. 19 | The large number of features spread across many implementations, also makes the API surface harder to test thoroughly. 20 | It makes it harder to ensure consistent behaviour across all implementations and combinations. 21 | Backwards compatibility has prevented us from cleaning this up. 22 | Until now, that is, with Netty 5 in the works. 23 | 24 | The Java platform is also not standing still. 25 | The OpenJDK project is on a long quest to deprecate and replace APIs and technologies that compromise the safety and security of the Java platform. 26 | This work includes building replacements for sun.misc.Unsafe, and JNI. 27 | Many use cases of Unsafe already have replacements, mostly in the form of VarHandles, but also a few other APIs. 28 | However, working with native memory, in a way that guarantees deterministic deallocation, remains an unsolved problem. 29 | 30 | To address this, JDK 14 included a new incubating API for memory management, called MemorySegment (https://openjdk.java.net/jeps/370). 31 | This API is evolving as part of the panama-foreign project, which aims to provide credible replacements to not only the native memory management APIs in Unsafe, but also to the C interoperability features of JNI. 32 | 33 | We have been collaborating with the panama-foreign project, providing feedback to their API designs, and championing our use cases. 34 | Our new buffer API is being designed with a future in mind, where access to Unsafe and JNI, is no longer possible. 35 | This is, however, not the implementation we are going to provide at first. 36 | The APIs from panama-foreign are still not finished, and likely won’t be in time for the release of JDK 17. 37 | With this in mind, Netty 5 will baseline on Java 11. 38 | 39 | == Where we are going 40 | 41 | The design of the new buffer API is guided by a number of principles, that together will make it intuitive, consistent, and fit for purpose: 42 | 43 | * _Safe memory handling._ 44 | The buffer API we design should not, on its own, allow anyone to segfault the JVM, or corrupt memory. 45 | This is also a strong requirement for the MemorySegment API. 46 | Alignment on this point means the API we design must support MemorySegment API safety requirements. 47 | 48 | * _Misuse resistance._ 49 | As much as is possible, we should make it difficult or impossible to use the buffer API in ways that are wrong and dangerous. 50 | When we cannot prevent misuse, we should make it easier to use the API in a correct way, than a wrong way. 51 | One way in which this manifest itself, is to ensure that reference counting can always be coded as a series of, potentially nested, try-with-resources clauses. 52 | 53 | * _Simple things should be easy; complex things should be possible._ 54 | Sane defaults and intuitive names should cater to the most common use cases. 55 | At the same time, we cannot simplify to the point of restricting expressiveness. 56 | We aim to strike a balance that does not obstruct advanced uses. 57 | 58 | * _Intuitive API._ 59 | The API should, as much as possible, be intuitive to use relative to the existing ByteBuf API and concepts. 60 | The mental model of how it works should be simple, with as few hidden states as possible. 61 | Any hidden magic should remain hidden, rather than leak through the abstractions. 62 | 63 | * _High performance._ 64 | Lastly, the API must permit fast and efficient implementations. 65 | People already have a certain expectation for the performance of Netty, that we cannot violate. 66 | If the new API is to replace the existing one, it must be able to match it in performance. 67 | 68 | Hopefully you’ll be able to see these principles reflected in the new API. 69 | 70 | == Changes and points of interest 71 | 72 | In this section we’ll outline the major changes, and most prominent points of interest in the new API. 73 | 74 | === Reference counting 75 | 76 | Buffers are now `AutoCloseable` and can be used in try-with-resources clauses. 77 | Every allocation and acquire call on a buffer (any `Resource` object, really) must be paired with a `close()`, and every `receive()` call on a `Send` must also be paired with a `close()`. 78 | 79 | While reference counting is a useful thing for tracking resource life-cycles internally, it is not itself exposed in the public API. 80 | Instead, the public API effectively has a boolean open/closed state. 81 | This simplifies the API a great deal; buffers are created, and in the end they are closed. 82 | The code in between needs to be arranged such that it just avoids ever holding on to buffers that might be closed. 83 | 84 | [source,java] 85 | ---- 86 | try (Buffer buf = allocator.allocate(8)) { 87 | // Access the buffer. 88 | } // buf is deallocated here. 89 | ---- 90 | 91 | The change of the open/closed state is not thread-safe, because the buffers themselves - their contents and their offsets - are not thread-safe. 92 | This is a deviation from how ByteBuf works, where the updates are atomic, and the reference count checks on memory accesses are “optimistic” in that they permit data races to occur. 93 | This codifies that buffers cannot be modified by more than one thread at a time, and that buffers should be shared via safe publication. 94 | Using the `send()` mechanism helps with the thread-safe transfer of buffer ownership. 95 | A buffers contents can still be access from multiple threads via the `get*` methods. 96 | However, the buffer should be effectively read-only while it is exposed like that, as accesses would otherwise be racy. 97 | 98 | If these simple rules and patterns are followed strictly, then memory leaks should not occur. 99 | 100 | === Cleaner attached by default 101 | 102 | To avoid memory leaks due to bugs, like forgetting to close a buffer, buffers in the new API will always have a Cleaner attached. 103 | If the buffer instance gets garbage collected without being closed properly, then the Cleaner thread will eventually reclaim the memory. 104 | This works for both pooled and unpooled buffers, and in the case of the former, the Cleaner will return the leaked memory to the pool. 105 | 106 | Note, however, that the buffers are still reference counted, because this has more predictable memory usage - especially when using off-heap buffers. 107 | Off-heap (or direct) buffers can give the GC an inaccurate picture of memory usage, which in turn can lead to abrupt bouts of poor performance when the system is under load. 108 | The cleaner is a fall back that will likely also be used as part of leak detection. 109 | 110 | === Slices are gone 111 | 112 | The existing ByteBuf API has a number of methods that allow multiple buffers to share access to the same memory. 113 | It turns out that this capability is at the heart of why reference counting is a necessary part of the ByteBuf API. 114 | By removing the various `slice()` and `duplicate()` methods, along with the `retain()`/`release()` family of methods, we also remove the ability for buffers to share memory. 115 | This allows us to simplify the reference counting concept to a simple boolean open/closed state. 116 | Buffers are created, and at the end of their life, they are closed, which releases their memory back to where it came from. 117 | 118 | === Buffer interface 119 | 120 | The abstract `ByteBuf` class, and its hierarchy of various buffer implementations, are all replaced by a single interface: `Buffer`. 121 | The 14 public `ByteBuf` and derived classes, plus numerous other non-public implementations, will be removed from the Netty API surface. 122 | Internally, the number of implementations will also be significantly reduced. 123 | 124 | See https://github.com/netty/netty-incubator-buffer-api/blob/main/src/main/java/io/netty/buffer/api/Buffer.java and https://github.com/netty/netty-incubator-buffer-api/blob/main/src/main/java/io/netty/buffer/api/BufferAccessors.java 125 | 126 | In our current prototype code, we only have two implementations: one based on `MemorySegment`, and a generic `CompositeBuffer` that composes other `Buffer` instances into one larger `Buffer` instance. 127 | None of these implementations are public; only the interface is. 128 | It is our aim to keep it that way, and to keep the number of concrete implementations very small, when we build an implementation that supports Java 11. 129 | 130 | All of our tests are also written in terms of the interface, and are parameterised over the implementations in various states. 131 | This gives us high confidence that all implementations behave exactly the same. 132 | 133 | === Allocator interface 134 | 135 | The `BufferAllocator` replaces the `ByteBufAllocator`. 136 | The difference is that the `Allocator` “just allocates” `Buffer` instances, and leaves the details of what that means up to the implementation. 137 | This means that if the buffers are pooled or not, are off-heap or on-heap, are decisions to consider when picking an `Allocator` implementation. 138 | 139 | See https://github.com/netty/netty-incubator-buffer-api/blob/main/src/main/java/io/netty/buffer/api/BufferAllocator.java 140 | 141 | In the `ByteBufAllocator` API, the implementation of the allocator made decisions about whether the buffers were pooled or not, and also if there was a preference for the buffers to be on- or off-heap, but the `ByteBufAllocator` API also has methods for explicitly allocating either on- or off-heap. 142 | 143 | This API surface is much reduced in the new `BufferAllocator` API. 144 | The `BufferAllocator` implementation decision is making a choice on the on-/off-heap, and pooled/unpooled axis. 145 | These choices are made available as a family of static factory methods on the `BufferAllocator` interface, so they’re easy to find. 146 | Once you got an `BufferAllocator` instance, you can only allocate buffers. 147 | 148 | [source,java] 149 | ---- 150 | try (BufferAllocator allocator = BufferAllocator.heap(); 151 | Buffer buf = allocator.allocate(8)) { 152 | // Access the buffer. 153 | } 154 | ---- 155 | 156 | === ByteCursor 157 | 158 | The `ByteProcessor` is not going away, but we are introducing a new concept for processing the data in a buffer, called the `ByteCursor`. 159 | A cursor is similar to an `Iterator`, except the `hasNext()` (checking if there is a next element) and `next()` (moving to that next element) methods are combined into one, and there is a separate method for obtaining the newly acquired element. 160 | 161 | See https://github.com/netty/netty-incubator-buffer-api/blob/main/src/main/java/io/netty/buffer/api/ByteCursor.java 162 | 163 | This API style turns out to be generally easier for the JIT compiler to optimise (https://github.com/netty/netty-incubator-buffer-api/pull/11), without much deviation from the familiar `Iterator` pattern. 164 | This also allows external iteration, where it is generally easier to decide when to stop iterating, than it is inside a `ByteProcessor` callback method. 165 | By moving to external iteration, it also becomes possible for integrating code to process bytes in bulk, by iterating 8 bytes at a time, as longs, instead of being forced to process them one at a time as in the `ByteProcessor`. 166 | 167 | Here’s an example where `ByteCursor` is used to copy the readable bytes from one buffer to another. 168 | Note that the byte order of the destination is temporarily set to big endian, because the `ByteCursor.getLong()` method always returns the value in big endian format: 169 | 170 | [source,java] 171 | ---- 172 | var order = dest.order(); 173 | dest.order(BIG_ENDIAN); 174 | try { 175 | var cursor = src.openCursor(); 176 | while (cursor.readLong()) 177 | dest.writeLong(cursor.getLong()); // Bulk move. 178 | while (cursor.readByte()) 179 | dest.writeByte(cursor.getByte()); // Tail move. 180 | } finally { 181 | dest.order(order); 182 | } 183 | ---- 184 | 185 | The `Buffer` interface also has `copyTo()` methods that can accomplish the same in fewer lines, and potentially faster as well. 186 | The above is just for illustration purpose. 187 | 188 | === Composite buffers 189 | 190 | In our existing API, `CompositeByteBuf` is a publicly exposed class, part of the API surface. 191 | In our new API, composite buffers mostly hide behind the `Buffer` interface, and all methods on `Buffer` have been designed such that they work equally well on both composite and non-composite buffers. 192 | This is to avoid the pains currently observed where we code that branches on whether a buffer is composite or not, and do one thing or another based on this information. 193 | Being able to unify these code paths will help with maintainability. 194 | 195 | There are, however, some methods of composite buffers that don't make sense on non-composite buffers. 196 | One such method is extending a composite buffer with more components. 197 | For this reason, the `CompositeBuffer` class is still public, such that these composite buffer specific methods have a natural home. 198 | 199 | Buffers need to know their allocators, in order to implement `ensureWritable()`, and the same is true for composite buffers. 200 | That’s why the method to compose buffers takes a `BufferAllocator` as a first argument: 201 | 202 | [source,java] 203 | ---- 204 | try (Buffer x = allocator.allocate(128); 205 | Buffer y = allocator.allocate(128)) { 206 | return CompositeBuffer.compose(allocator, x.send(), y.send()); 207 | } 208 | ---- 209 | 210 | The static `compose()` method will create a composite buffer, even when only given a single buffer, or no buffers. 211 | 212 | The composite buffer takes ownership of each of its constituent component buffers, via the `Send` arguments. 213 | This guarantees that the composite cannot be brought into a state that is invalid, through direct manipulation of its components. 214 | 215 | Although there is in principle is no need for integrating code to know whether a buffer is composite, it is still possible to query, in case it is helpful for some optimisations. 216 | This is done with the `countComponents()`, `countReadableComponents()`, and `countWritableComponents()` family of methods. 217 | These methods exist on the `Buffer` interface, so non-composite buffers have them too, and will pretend to have a single component, namely themselves. 218 | If it is important to know with certainly, if a buffer is composite or not, then the static `CompositeBuffer.isComposite()` method can be used. 219 | 220 | If you know that a buffer is composite, and the composite buffer is owned, then it’s possible to extend the composite buffer with more components, using the `CompositeBuffer.extendWith()` method. 221 | 222 | Composite buffers can be nested, but they will flatten themselves internally. 223 | That is, you can pass composite buffers to the `CompositeBuffer.compose()` method, and the resulting composite buffer will appear to contain all their data just as if the components had been non-composite. 224 | However, the new composite buffer will end up with the flattened concatenation of all constituent components. 225 | This means the number of indirections will not increase in the new buffer. 226 | 227 | === Iterating components 228 | 229 | The `forEachReadable()` and `forEachWritable()` methods iterate a buffers readable and writable areas, respectively. 230 | A composite buffer can have multiple such areas, while a non-composite buffer will at most have one of each. 231 | This uses internal iteration, where a `ReadableComponent` or a `WritableComponent` is passed to the component processor, which will probably be a lambda expression in the common case. 232 | By using internal iteration, we are able to completely hide any sort of nesting of the buffer implementations. 233 | link 234 | 235 | The `ReadableComponent` and `WritableComponent` objects expose a restricted set of methods. 236 | Their primary purpose is to support interfacing the buffer with system calls and the like. 237 | A component will always be able to make a `ByteBuffer` available, and it may optionally expose an array or a native pointer. 238 | 239 | Similar to how `ByteProcessor` works today, the component processor is allowed to stop the iteration early by returning false. 240 | The `forEachReadable()` and `forEachWritable()` methods return the number of components processed, and if the iteration was stopped early, this number will have a negative sign. 241 | 242 | These `ReadableComponent` and `WritableComponent` objects, and the way they expose memory, replace the `internalNioBuffer()` and `nioBuffer*()` family of methods. 243 | The component objects themselves are only valid within the callback method, but the `ByteBuffer` they expose can be used until an ownership-requiring method is called on the buffer. 244 | As a rule of thumb, the byte buffers should be used and discarded within the same method scope as the call to the `forEachReadable()` or `forEachWritable()` method. 245 | 246 | === Capacity and max capacity 247 | 248 | `ByteBuf` has separate `capacity()` and `maxCapacity()` concepts, and allows one to freely change the capacity of the buffer. 249 | In the new API we are making things a little more strict. 250 | The concept of a buffer having loosely defined capacity is going away. 251 | 252 | There will only be a `capacity()`, no `maxCapacity()`. 253 | The capacity can only be increased by calling `ensureWritable()`, or alternatively in the case of a composite buffer, by calling `CompositeBuffer.extendWith()`. 254 | 255 | There is only one `ensureWritable()` method. 256 | It works similar to the `ByteBuf.ensureWritable(size, true)` where the “true” means it is allowed to allocate new backing memory. 257 | Since it may change the size of the buffer, and its allocated memory, the `ensureWritable()` method requires ownership. 258 | 259 | Capacity is no longer increased automatically by the various `write*()` methods. 260 | If you run out of memory, an exception will be thrown. 261 | 262 | This means that where you previously could do something like this: 263 | 264 | [source,java] 265 | ---- 266 | byte[] toWrite = ...; 267 | buf.write(toWrite); 268 | ---- 269 | 270 | You now have to do something like this: 271 | 272 | [source,java] 273 | ---- 274 | byte[] toWrite = ...; 275 | buf.ensureWritable(toWrite.length); 276 | buf.write(toWrite); 277 | ---- 278 | 279 | The `maxWritableBytes()` and `maxFastWritableBytes()` methods are replaced by a single `writableBytes()` method. 280 | Likewise, the `discardReadBytes()` and `discardSomeReadBytes()` are both replaced by a single `compact()` method, which will require ownership to call. 281 | 282 | === No more marker indexes 283 | 284 | Marker indexes, and the `mark`/`resetReader`/`WriterIndex()` family of methods are going away, with no replacement planned. 285 | 286 | === No more ReplayingDecoder 287 | 288 | The `ReplayingDecoder` is relying on a complicated exception-based protocol, in order to simulate continuations and create the illusion of infinitely readable buffers. 289 | This is being removed with no replacement planned. 290 | 291 | === Byte order 292 | 293 | In the new API, the `Buffer.order(ByteOrder)` method will change the byte order for accessors on the existing buffer instance. 294 | In the old API, `ByteBuf.order(ByteOrder)` returned a new buffer instance that presented a view of the original buffer using the given byte order. 295 | 296 | Since the old API forced allocation and wrapping of the buffer to occur, it incurred some overhead. 297 | To cope with that, the `get`/`set`/`read`/`write*LE()` family of methods where introduced. 298 | These, however, have inconsistent behaviour depending on the buffer implementation. 299 | 300 | In the new API, there are no more little-endian specific accessor methods. 301 | If a particular byte order is desired, then this should be set on the buffer. 302 | Since the new API changes the state of the buffer instead of wrapping it, it is a cheap operation to do. 303 | 304 | === Indexes vs. offsets 305 | 306 | The `readerIndex` and `writerIndex` are now called `readerOffset` and `writerOffset`. 307 | This is to make the naming more consistent and precise. 308 | An “index” implies access to memory at a multiple of the element size, like indexes into a long-array for instance,while “offset” is a difference in bytes from some base address. 309 | 310 | The MemorySegment APIs that are being developed in the OpenJDK project will use the same terminology, and making these name changes now will avoid confusion in the future. 311 | 312 | === No more boolean accessors 313 | 314 | The `get`/`set`/`read`/`writeBoolean` accessor methods are being removed with no replacement planned. 315 | They have ambiguous meaning when working with buffers that are fundamentally byte-granular. 316 | 317 | === Splitting buffers with split() 318 | 319 | With the removal of the `slice()` family of methods, we are in need of an alternative way to process a buffer in parts. 320 | For instance, in Netty, the `ByteToMessageDecoder` collects data into a collecting buffer, from which data frames are produced and then sent off to be processed further down a pipeline, potentially in parallel in other threads. 321 | 322 | Since slices would cause memory to be shared, they would effectively lock out all methods that require ownership. 323 | This would be a problem for such a collecting buffer, since it needs to grow dynamically to accommodate the largest message or frame size. 324 | 325 | To address this, the new API introduces a `Buffer.split()` (https://github.com/netty/netty-incubator-buffer-api/blob/main/src/main/java/io/netty/buffer/api/Buffer.java#L529) method. 326 | This method splits the ownership of a buffer in two. 327 | All the read and readable bytes are returned in a new, independent buffer, and the existing buffer gets truncated at the head by a corresponding amount. 328 | The capacities and offsets of both buffers are adjusted such that they cannot access each others memory. 329 | 330 | This way, the two regions of memory can be considered to be independent, and thus they have independent ownership. 331 | The two buffers still share the same underlying memory allocation, and the restrictions and mechanics ensure that this is safe to do. 332 | 333 | The memory management is handled internally with a second level of reference counting, which means that the original memory allocation is only reused or freed, when all split buffers have been closed. 334 | These internal details are safely managed even when slicing, sending, or expanding the split buffers with `ensureWritable()`. 335 | 336 | [source,java] 337 | ---- 338 | buf.writeLong(x); 339 | buf.writeLong(y); 340 | executor.submit(new Task(buf.split().send())); 341 | buf.ensureWritable(512); 342 | // ... 343 | ---- 344 | 345 | In the above example, we have written some data to the buffer, and we wish to process it in another thread while at the same time being able to write more data into our buffer. 346 | The `split()` call splits off the readable part of the `buf` buffer, into a new buffer with its own independent ownership, which we then send off for processing. 347 | Since `split()` splits the ownership of the memory, we retain ownership of the writable part of the `buf` buffer, and we are able to call `ensureWritable()` on it. 348 | Recall that `ensureWritable()` requires ownership, or else it will throw an exception. 349 | 350 | 351 | === Transferring ownership with send() 352 | 353 | Since reference counts are meant to be managed with try-with-resources clauses, we run into trouble when a buffer’s life cycle, and the code that manages it, is no longer tree-shaped. 354 | For instance, if we want to send a buffer from one thread to another. 355 | 356 | The `send()` method is the solution to this. 357 | It deactivates the existing buffer and returns a `Send` object, which can then safely be shared with other threads. 358 | The receiving thread then calls `Send.receive()`, and gets the buffer back out. 359 | Because `send()` only works on owned buffers, the receiving threads are guaranteed to get their buffers in an owned state. 360 | 361 | It is important to take some care with error handling around `send()` calls. 362 | If the `receive()` method is not called on the `Send` object, then the memory of the buffer will not be accessible. 363 | In the end, the buffer might have to be reclaimed by the `Cleaner` in order to prevent leaks. 364 | 365 | The “deactivation” of the existing buffer mentioned above, means that the memory is safely shared, even if the code breaks protocol and tries to access their buffer instance after the `send()` call. 366 | When this happens, and exception will be thrown to the offending thread. 367 | 368 | [source,java] 369 | ---- 370 | var send = buf.send(); 371 | executor.submit(() -> { 372 | try (Buf received = send.receive()) { 373 | // process received buffer... 374 | } 375 | }); 376 | ---- 377 | 378 | In the above, the `buf.send()` call creates a `Send` object, and deactivates the `buf` instance, making its memory inaccessible. 379 | A `Buffer` instance is a view onto some memory, but it is not the memory itself. 380 | When the receiving thread calls `send.receive()`, it gets a new `Buffer` instance back. 381 | This new `received` buffer instance is backed by the same memory that the `buf` instance used. 382 | The small amount of object allocation is a necessary part of the safety properties of the `send()` mechanism. 383 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Netty Incubator Buffer API 2 | 3 | [NOTE] 4 | -- 5 | The memory segment implementation has been merged into Netty 5: https://github.com/netty/netty/pull/13028 6 | Development now continue in the main Netty repository, and this incubator repository is no longer maintained. 7 | -- 8 | 9 | This repository is incubating a new buffer API proposed for Netty 5. 10 | 11 | See the xref:RATIONALE.adoc[RATIONALE] document for more background. 12 | 13 | == Building and Testing 14 | 15 | Short version: just run `make` if you want to build on Java 17, otherwise run `mvn install` if you want to build with Java 11, and without support for the `java.lang.foreign` APIs. 16 | 17 | The project (specifically, the `buffer-memseg` module) currently relies on snapshot versions of the https://github.com/openjdk/panama-foreign[Panama Foreign] fork of OpenJDK. 18 | This allows us to test out the most recent version of the `java.lang.foreign` APIs, but also make building, and local development more involved. 19 | To simplify things, we have a Docker based build, controlled via a Makefile with the following commands: 20 | 21 | * `image` – build the docker image.This includes building a snapshot of OpenJDK, and download all relevant Maven dependencies. 22 | * `test` – run all tests in a docker container.This implies `image`.The container is automatically deleted afterwards. 23 | * `dbg` – drop into a shell in the build container, without running the build itself.The debugging container is not deleted afterwards. 24 | * `clean` – remove the leftover containers created by `dbg`, `test`, and `build`. 25 | * `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. 26 | 27 | == Example: Echo Client and Server 28 | 29 | Making use of this new buffer API on the client side is quite easy. 30 | 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. 31 | This means we are able to send incubator buffers through a Netty pipeline, and have it work as if we were sending `ByteBuf` instances. 32 | 33 | [source,java] 34 | ---- 35 | public final class Client { 36 | public static void main(String[] args) throws Exception { 37 | EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory()); 38 | try (BufferAllocator allocator = BufferAllocator.pooledDirect()) { // <1> 39 | Bootstrap b = new Bootstrap(); 40 | b.group(group) 41 | .channel(NioSocketChannel.class) 42 | .option(ChannelOption.TCP_NODELAY, true) 43 | .handler(new ChannelInitializer() { 44 | @Override 45 | public void initChannel(SocketChannel ch) throws Exception { 46 | ch.pipeline().addLast(new ChannelHandlerAdapter() { 47 | @Override 48 | public void channelActive(ChannelHandlerContext ctx) { 49 | Buffer message = allocator.allocate(256); // <2> 50 | for (int i = 0; i < message.capacity(); i++) { 51 | message.writeByte((byte) i); 52 | } 53 | ctx.writeAndFlush(message); // <3> 54 | } 55 | }); 56 | } 57 | }); 58 | 59 | // Start the client. 60 | ChannelFuture f = b.connect("127.0.0.1", 8007).sync(); 61 | 62 | // Wait until the connection is closed. 63 | f.channel().closeFuture().sync(); 64 | } finally { 65 | // Shut down the event loop to terminate all threads. 66 | group.shutdownGracefully(); 67 | } 68 | } 69 | } 70 | ---- 71 | <1> A life-cycled allocator is created to wrap the scope of our application. 72 | <2> Buffers are allocated with one of the `allocate` methods. 73 | <3> The buffer can then be sent down the pipeline, and will be written to the socket just like a `ByteBuf` would. 74 | 75 | [NOTE] 76 | -- 77 | The same is not the case for `BufferHolder`. 78 | It is not treated the same as a `ByteBufHolder`. 79 | -- 80 | 81 | On the server side, things are more complicated because Netty itself will be allocating the buffers, and the `ByteBufAllocator` API is only capable of returning `ByteBuf` instances. 82 | The `ByteBufAllocatorAdaptor` will allocate `ByteBuf` instances that are backed by the new buffers. 83 | The buffers can then we extracted from the `ByteBuf` instances with the `ByteBufAdaptor.extract` method. 84 | 85 | We can tell a Netty server how to allocate buffers by setting the `ALLOCATOR` child-channel option: 86 | 87 | [source,java] 88 | ---- 89 | ByteBufAllocatorAdaptor allocator = new ByteBufAllocatorAdaptor(); // <1> 90 | ServerBootstrap server = new ServerBootstrap(); 91 | server.group(bossGroup, workerGroup) 92 | .channel(NioServerSocketChannel.class) 93 | .childOption(ChannelOption.ALLOCATOR, allocator) // <2> 94 | .handler(new EchoServerHandler()); 95 | ---- 96 | <1> The `ByteBufAllocatorAdaptor` implements `ByteBufAllocator`, and directly allocates `ByteBuf` instances that are backed by buffers that use the new API. 97 | <2> To make Netty use a given allocator when allocating buffers for receiving data, we set the allocator as a child option. 98 | 99 | 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. 100 | The buffers are still allocated at `ByteBuf` instances, and flow through the pipeline as such. 101 | 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: 102 | 103 | [source,java] 104 | ---- 105 | import io.netty.buffer.ByteBuf; 106 | import io.netty.buffer.api.Buffer; 107 | import io.netty.buffer.api.adaptor.ByteBufAdaptor; 108 | 109 | @Sharable 110 | public class EchoServerHandler implements ChannelHandler { 111 | @Override 112 | public void channelRead(ChannelHandlerContext ctx, Object msg) { // <1> 113 | if (msg instanceof ByteBuf) { // <2> 114 | // For this example, we only echo back buffers that are using the new buffer API. 115 | Buffer buf = ByteBufAdaptor.extract((ByteBuf) msg); // <3> 116 | ctx.write(buf); // <4> 117 | } 118 | } 119 | 120 | @Override 121 | public void channelReadComplete(ChannelHandlerContext ctx) { 122 | ctx.flush(); 123 | } 124 | } 125 | ---- 126 | <1> Netty pipelines are defined as transferring `Object` instances as messages. 127 | <2> When we receive data directly from a socket, these messages will be `ByteBuf` instances with the received data. 128 | <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. 129 | <4> We can then operate on the extracted `Buffer` instances directly. 130 | The `Buffer` and `ByteBuf` instances mirror each other exactly. 131 | In this case, we just write them back to the client that sent the data to us. 132 | 133 | The files in `src/test/java/io/netty/buffer/api/examples/echo` for the full source code to this example. 134 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 4.0.0 20 | 21 | org.sonatype.oss 22 | oss-parent 23 | 9 24 | 25 | 26 | io.netty.incubator 27 | netty5-incubator-buffer-memseg 28 | 0.0.1.Final-SNAPSHOT 29 | Netty/Incubator/Buffer MemorySegment 30 | jar 31 | https://netty.io/ 32 | 33 | Netty is an asynchronous event-driven network application framework for 34 | rapid development of maintainable high performance protocol servers and 35 | clients. 36 | 37 | 38 | 39 | The Netty Project 40 | https://netty.io/ 41 | 42 | 43 | 44 | 45 | Apache License, Version 2.0 46 | https://www.apache.org/licenses/LICENSE-2.0 47 | 48 | 49 | 2020 50 | 51 | 52 | https://github.com/netty/netty-incubator-buffer-api 53 | scm:git:git://github.com:chrisvest/netty-incubator-buffer-api.git 54 | scm:git:ssh://git@github.com:chrisvest/netty-incubator-buffer-api.git 55 | HEAD 56 | 57 | 58 | 59 | 60 | netty.io 61 | The Netty Project Contributors 62 | netty@googlegroups.com 63 | https://netty.io/ 64 | The Netty Project 65 | https://netty.io/ 66 | 67 | 68 | 69 | 70 | io.netty.incubator.buffer.memseg 71 | 5.0.0.Alpha6-SNAPSHOT 72 | 29 73 | 74 | 20 75 | 76 | 20 77 | 5.8.2 78 | 3.0.0-M5 79 | 80 | 81 | 82 | 83 | 84 | kr.motd.maven 85 | os-maven-plugin 86 | 1.6.2 87 | 88 | 89 | 90 | 91 | org.apache.maven.plugins 92 | maven-dependency-plugin 93 | 3.1.2 94 | 95 | 96 | 97 | properties 98 | 99 | 100 | 101 | 102 | 103 | maven-compiler-plugin 104 | 3.10.0 105 | 106 | ${java.version} 107 | true 108 | ${java.compatibility} 109 | ${java.compatibility} 110 | ${java.version} 111 | true 112 | true 113 | true 114 | true 115 | 116 | -Xlint:-options 117 | --enable-preview 118 | 119 | 256m 120 | 1024m 121 | 122 | 123 | 124 | maven-checkstyle-plugin 125 | 3.1.2 126 | 127 | 128 | check-style 129 | 130 | check 131 | 132 | validate 133 | 134 | true 135 | true 136 | true 137 | true 138 | io/netty/checkstyle.xml 139 | 140 | ${project.build.sourceDirectory} 141 | ${project.build.testSourceDirectory} 142 | 143 | 144 | false 145 | 146 | 147 | 148 | 149 | com.puppycrawl.tools 150 | checkstyle 151 | 8.41 152 | 153 | 154 | io.netty 155 | netty-build-common 156 | ${netty.build.version} 157 | 158 | 159 | 160 | 161 | 162 | org.apache.felix 163 | maven-bundle-plugin 164 | 5.1.1 165 | 166 | 167 | generate-manifest 168 | process-classes 169 | 170 | manifest 171 | 172 | 173 | 174 | jar 175 | bundle 176 | 177 | 178 | ${project.groupId}.* 179 | 180 | sun.misc.*;resolution:=optional,sun.nio.ch;resolution:=optional,sun.security.*;resolution:=optional 181 | 182 | !* 183 | 184 | 185 | 186 | 187 | 188 | 189 | maven-source-plugin 190 | 3.2.0 191 | 194 | 195 | 196 | 197 | 2 198 | ${project.name} 199 | ${project.groupId}.${project.artifactId}.source 200 | ${project.organization.name} 201 | ${parsedVersion.osgiVersion} 202 | ${project.groupId}.${project.artifactId};version="${parsedVersion.osgiVersion}";roots:="." 203 | 204 | 205 | 206 | 207 | 208 | 209 | attach-sources 210 | prepare-package 211 | 212 | jar-no-fork 213 | 214 | 215 | 216 | attach-test-sources 217 | prepare-package 218 | 219 | test-jar-no-fork 220 | 221 | 222 | 223 | 224 | 225 | maven-javadoc-plugin 226 | 2.10.4 227 | 228 | false 229 | true 230 | false 231 | false 232 | true 233 | 234 | 235 | 236 | maven-deploy-plugin 237 | 2.8.2 238 | 239 | 10 240 | 241 | 242 | 243 | maven-release-plugin 244 | 2.5.3 245 | 246 | false 247 | -P restricted-release,sonatype-oss-release,full 248 | true 249 | false 250 | ${project.artifactId}-@{project.version} 251 | 252 | 253 | 254 | org.apache.maven.scm 255 | maven-scm-api 256 | 1.9.4 257 | 258 | 259 | org.apache.maven.scm 260 | maven-scm-provider-gitexe 261 | 1.9.4 262 | 263 | 264 | 265 | 266 | org.apache.maven.plugins 267 | maven-surefire-plugin 268 | ${surefire.version} 269 | 270 | --enable-preview 271 | io.netty:* 272 | 273 | io.netty5.buffer.api.**.*Test.java 274 | io.netty5.buffer.api.**.*IT.java 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | io.netty 284 | netty5-common 285 | ${netty.version} 286 | 287 | 288 | io.netty 289 | netty5-buffer 290 | ${netty.version} 291 | 292 | 293 | org.openjdk.jmh 294 | jmh-core 295 | 1.34 296 | test 297 | 298 | 299 | org.openjdk.jmh 300 | jmh-generator-annprocess 301 | 1.34 302 | test 303 | 304 | 305 | org.junit.jupiter 306 | junit-jupiter-api 307 | ${junit.version} 308 | test 309 | 310 | 311 | org.junit.jupiter 312 | junit-jupiter-engine 313 | ${junit.version} 314 | test 315 | 316 | 317 | org.junit.jupiter 318 | junit-jupiter-params 319 | ${junit.version} 320 | test 321 | 322 | 323 | org.assertj 324 | assertj-core 325 | 3.18.0 326 | test 327 | 328 | 329 | io.netty 330 | netty5-buffer 331 | ${netty.version} 332 | test 333 | test-jar 334 | 335 | 336 | io.netty 337 | netty5-handler 338 | ${netty.version} 339 | test 340 | 341 | 342 | io.netty 343 | netty5-codec-http 344 | ${netty.version} 345 | test 346 | 347 | 348 | -------------------------------------------------------------------------------- /src/main/java/io/netty5/buffer/memseg/MemSegBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.memseg; 17 | 18 | import io.netty5.buffer.AllocatorControl; 19 | import io.netty5.buffer.Buffer; 20 | import io.netty5.buffer.BufferAllocator; 21 | import io.netty5.buffer.BufferComponent; 22 | import io.netty5.buffer.BufferReadOnlyException; 23 | import io.netty5.buffer.ByteCursor; 24 | import io.netty5.buffer.ComponentIterator; 25 | import io.netty5.buffer.Drop; 26 | import io.netty5.buffer.Owned; 27 | import io.netty5.buffer.internal.AdaptableBuffer; 28 | import io.netty5.buffer.internal.InternalBufferUtils; 29 | 30 | import java.io.IOException; 31 | import java.lang.foreign.Arena; 32 | import java.lang.foreign.MemorySegment; 33 | import java.lang.foreign.ValueLayout; 34 | import java.nio.ByteBuffer; 35 | import java.nio.ByteOrder; 36 | import java.nio.ReadOnlyBufferException; 37 | import java.nio.channels.FileChannel; 38 | import java.nio.channels.ReadableByteChannel; 39 | import java.nio.channels.WritableByteChannel; 40 | 41 | import static io.netty5.buffer.internal.InternalBufferUtils.MAX_BUFFER_SIZE; 42 | import static io.netty5.buffer.internal.InternalBufferUtils.bufferIsClosed; 43 | import static io.netty5.buffer.internal.InternalBufferUtils.bufferIsReadOnly; 44 | import static io.netty5.buffer.internal.InternalBufferUtils.checkImplicitCapacity; 45 | import static io.netty5.buffer.internal.InternalBufferUtils.checkLength; 46 | import static io.netty5.util.internal.ObjectUtil.checkPositiveOrZero; 47 | import static io.netty5.util.internal.PlatformDependent.roundToPowerOfTwo; 48 | 49 | class MemSegBuffer extends AdaptableBuffer 50 | implements BufferComponent, ComponentIterator, ComponentIterator.Next { 51 | private static final ValueLayout.OfByte JAVA_BYTE = 52 | ValueLayout.JAVA_BYTE.withOrder(ByteOrder.BIG_ENDIAN).withBitAlignment(Byte.SIZE); 53 | private static final ValueLayout.OfChar JAVA_CHAR = 54 | ValueLayout.JAVA_CHAR.withOrder(ByteOrder.BIG_ENDIAN).withBitAlignment(Byte.SIZE); 55 | private static final ValueLayout.OfShort JAVA_SHORT = 56 | ValueLayout.JAVA_SHORT.withOrder(ByteOrder.BIG_ENDIAN).withBitAlignment(Byte.SIZE); 57 | private static final ValueLayout.OfInt JAVA_INT = 58 | ValueLayout.JAVA_INT.withOrder(ByteOrder.BIG_ENDIAN).withBitAlignment(Byte.SIZE); 59 | private static final ValueLayout.OfFloat JAVA_FLOAT = 60 | ValueLayout.JAVA_FLOAT.withOrder(ByteOrder.BIG_ENDIAN).withBitAlignment(Byte.SIZE); 61 | private static final ValueLayout.OfLong JAVA_LONG = 62 | ValueLayout.JAVA_LONG.withOrder(ByteOrder.BIG_ENDIAN).withBitAlignment(Byte.SIZE); 63 | private static final ValueLayout.OfDouble JAVA_DOUBLE = 64 | ValueLayout.JAVA_DOUBLE.withOrder(ByteOrder.BIG_ENDIAN).withBitAlignment(Byte.SIZE); 65 | 66 | private static final MemorySegment CLOSED_SEGMENT; 67 | 68 | static { 69 | try (Arena arena = Arena.openShared()) { 70 | CLOSED_SEGMENT = MemorySegment.allocateNative(0, arena.session()); 71 | } 72 | } 73 | 74 | private final AllocatorControl control; 75 | private MemorySegment base; 76 | private MemorySegment seg; 77 | private MemorySegment wseg; 78 | private int roff; 79 | private int woff; 80 | private int implicitCapacityLimit; 81 | 82 | MemSegBuffer(MemorySegment base, MemorySegment view, AllocatorControl control, Drop drop) { 83 | super(drop, control); 84 | this.control = control; 85 | this.base = base; 86 | seg = view; 87 | wseg = view; 88 | implicitCapacityLimit = MAX_BUFFER_SIZE; 89 | } 90 | 91 | /** 92 | * Constructor for {@linkplain BufferAllocator#constBufferSupplier(byte[]) const buffers}. 93 | */ 94 | MemSegBuffer(MemSegBuffer parent, Drop drop) { 95 | super(drop, parent.control); 96 | control = parent.control; 97 | base = parent.base; 98 | seg = parent.seg; 99 | wseg = parent.wseg; 100 | roff = parent.roff; 101 | woff = parent.woff; 102 | implicitCapacityLimit = parent.implicitCapacityLimit; 103 | } 104 | 105 | @Override 106 | public String toString() { 107 | return "Buffer[roff:" + roff + ", woff:" + woff + ", cap:" + seg.byteSize() + ']'; 108 | } 109 | 110 | @Override 111 | protected RuntimeException createResourceClosedException() { 112 | return bufferIsClosed(this); 113 | } 114 | 115 | @Override 116 | public int capacity() { 117 | return (int) seg.byteSize(); 118 | } 119 | 120 | @Override 121 | public int readerOffset() { 122 | return roff; 123 | } 124 | 125 | @Override 126 | public MemSegBuffer skipReadableBytes(int delta) { 127 | readerOffset(readerOffset() + delta); 128 | return this; 129 | } 130 | 131 | @Override 132 | public MemSegBuffer readerOffset(int offset) { 133 | checkRead(offset, 0); 134 | roff = offset; 135 | return this; 136 | } 137 | 138 | @Override 139 | public int writerOffset() { 140 | return woff; 141 | } 142 | 143 | @Override 144 | public MemSegBuffer skipWritableBytes(int delta) { 145 | writerOffset(writerOffset() + delta); 146 | return this; 147 | } 148 | 149 | @Override 150 | public MemSegBuffer writerOffset(int offset) { 151 | if (readOnly()) { 152 | throw bufferIsReadOnly(this); 153 | } 154 | checkWrite(offset, 0, false); 155 | woff = offset; 156 | return this; 157 | } 158 | 159 | @Override 160 | public int readableBytes() { 161 | return writerOffset() - readerOffset(); 162 | } 163 | 164 | @Override 165 | public int writableBytes() { 166 | return capacity() - writerOffset(); 167 | } 168 | 169 | @Override 170 | public Buffer fill(byte value) { 171 | if (!isAccessible()) { 172 | throw bufferIsClosed(this); 173 | } 174 | checkSet(0, capacity()); 175 | seg.fill(value); 176 | return this; 177 | } 178 | 179 | // 180 | @Override 181 | public long baseNativeAddress() { 182 | return nativeAddress(0); 183 | } 184 | 185 | @Override 186 | public boolean hasReadableArray() { 187 | return false; 188 | } 189 | 190 | @Override 191 | public byte[] readableArray() { 192 | throw new UnsupportedOperationException("This component has no backing array."); 193 | } 194 | 195 | @Override 196 | public int readableArrayOffset() { 197 | throw new UnsupportedOperationException("This component has no backing array."); 198 | } 199 | 200 | @Override 201 | public int readableArrayLength() { 202 | throw new UnsupportedOperationException("This component has no backing array."); 203 | } 204 | 205 | @Override 206 | public long readableNativeAddress() { 207 | return nativeAddress(roff); 208 | } 209 | 210 | @Override 211 | public ByteBuffer readableBuffer() { 212 | var buffer = seg.asByteBuffer(); 213 | buffer = buffer.asReadOnlyBuffer(); 214 | buffer = buffer.position(readerOffset()).limit(readerOffset() + readableBytes()); 215 | return buffer; 216 | } 217 | 218 | @Override 219 | public boolean hasWritableArray() { 220 | return false; 221 | } 222 | 223 | @Override 224 | public byte[] writableArray() { 225 | throw new UnsupportedOperationException("This component has no backing array."); 226 | } 227 | 228 | @Override 229 | public int writableArrayOffset() { 230 | throw new UnsupportedOperationException("This component has no backing array."); 231 | } 232 | 233 | @Override 234 | public int writableArrayLength() { 235 | throw new UnsupportedOperationException("This component has no backing array."); 236 | } 237 | 238 | @Override 239 | public long writableNativeAddress() { 240 | return nativeAddress(woff); 241 | } 242 | 243 | @Override 244 | public ByteBuffer writableBuffer() { 245 | var buffer = wseg.asByteBuffer(); 246 | buffer = buffer.position(writerOffset()).limit(writerOffset() + writableBytes()); 247 | return buffer; 248 | } 249 | 250 | @Override 251 | public MemSegBuffer first() { 252 | return this; 253 | } 254 | 255 | @Override 256 | public N next() { 257 | return null; // There is no "next" component in our external-iteration of components. 258 | } 259 | // 260 | 261 | private long nativeAddress(int offset) { 262 | if (!isAccessible()) { 263 | throw bufferIsClosed(this); 264 | } 265 | if (seg.isNative()) { 266 | return seg.address() + offset; 267 | } 268 | return 0; // This is a heap segment. 269 | } 270 | 271 | @Override 272 | public Buffer makeReadOnly() { 273 | wseg = CLOSED_SEGMENT; 274 | return this; 275 | } 276 | 277 | @Override 278 | public boolean readOnly() { 279 | return wseg == CLOSED_SEGMENT && seg != CLOSED_SEGMENT; 280 | } 281 | 282 | @Override 283 | public boolean isDirect() { 284 | return seg.isNative(); 285 | } 286 | 287 | @Override 288 | public Buffer implicitCapacityLimit(int limit) { 289 | checkImplicitCapacity(limit, capacity()); 290 | implicitCapacityLimit = limit; 291 | return this; 292 | } 293 | 294 | @Override 295 | public int implicitCapacityLimit() { 296 | return implicitCapacityLimit; 297 | } 298 | 299 | @Override 300 | public Buffer copy(int offset, int length, boolean readOnly) { 301 | checkLength(length); 302 | checkGet(offset, length); 303 | if (readOnly && readOnly()) { 304 | // If both this buffer and the copy are read-only, they can safely share the memory. 305 | MemSegBuffer copy = newConstChild(); 306 | copy.seg = seg.asSlice(offset, length); 307 | copy.roff = 0; 308 | copy.woff = length; 309 | return copy; 310 | } 311 | 312 | Buffer copy = control.getAllocator().allocate(length); 313 | try { 314 | copyInto(offset, copy, 0, length); 315 | copy.writerOffset(length); 316 | if (readOnly) { 317 | copy.makeReadOnly(); 318 | } 319 | return copy; 320 | } catch (Throwable e) { 321 | copy.close(); 322 | throw e; 323 | } 324 | } 325 | 326 | @Override 327 | public void copyInto(int srcPos, byte[] dest, int destPos, int length) { 328 | copyInto(srcPos, MemorySegment.ofArray(dest), destPos, length); 329 | } 330 | 331 | @Override 332 | public void copyInto(int srcPos, ByteBuffer dest, int destPos, int length) { 333 | if (dest.isReadOnly()) { 334 | throw new ReadOnlyBufferException(); 335 | } 336 | copyInto(srcPos, MemorySegment.ofBuffer(dest.duplicate().clear()), destPos, length); 337 | } 338 | 339 | private void copyInto(int srcPos, MemorySegment dest, int destPos, int length) { 340 | if (seg == CLOSED_SEGMENT) { 341 | throw bufferIsClosed(this); 342 | } 343 | if (srcPos < 0) { 344 | throw new IllegalArgumentException("The srcPos cannot be negative: " + srcPos + '.'); 345 | } 346 | if (length < 0) { 347 | throw new IllegalArgumentException("The length cannot be negative: " + length + '.'); 348 | } 349 | if (seg.byteSize() < srcPos + length) { 350 | throw new IllegalArgumentException("The srcPos + length is beyond the end of the buffer: " + 351 | "srcPos = " + srcPos + ", length = " + length + '.'); 352 | } 353 | dest.asSlice(destPos, length).copyFrom(seg.asSlice(srcPos, length)); 354 | } 355 | 356 | @Override 357 | public void copyInto(int srcPos, Buffer dest, int destPos, int length) { 358 | if (!isAccessible()) { 359 | throw attachTrace(bufferIsClosed(this)); 360 | } 361 | if (dest.readOnly()) { 362 | throw bufferIsReadOnly(dest); 363 | } 364 | if (dest instanceof MemSegBuffer memSegBuf) { 365 | memSegBuf.checkSet(destPos, length); 366 | copyInto(srcPos, memSegBuf.seg, destPos, length); 367 | return; 368 | } 369 | 370 | InternalBufferUtils.copyToViaReverseLoop(this, srcPos, dest, destPos, length); 371 | } 372 | 373 | @Override 374 | public int transferTo(WritableByteChannel channel, int length) throws IOException { 375 | if (!isAccessible()) { 376 | throw bufferIsClosed(this); 377 | } 378 | length = Math.min(readableBytes(), length); 379 | if (length == 0) { 380 | return 0; 381 | } 382 | checkGet(readerOffset(), length); 383 | int bytesWritten = channel.write(readableBuffer().limit(length)); 384 | skipReadableBytes(bytesWritten); 385 | return bytesWritten; 386 | } 387 | 388 | @Override 389 | public int transferFrom(FileChannel channel, long position, int length) throws IOException { 390 | checkPositiveOrZero(position, "position"); 391 | checkPositiveOrZero(length, "length"); 392 | if (!isAccessible()) { 393 | throw bufferIsClosed(this); 394 | } 395 | if (readOnly()) { 396 | throw bufferIsReadOnly(this); 397 | } 398 | length = Math.min(writableBytes(), length); 399 | if (length == 0) { 400 | return 0; 401 | } 402 | checkSet(writerOffset(), length); 403 | int bytesRead = channel.read(writableBuffer().limit(length), position); 404 | if (bytesRead > 0) { // Don't skipWritable if bytesRead is 0 or -1 405 | skipWritableBytes(bytesRead); 406 | } 407 | return bytesRead; 408 | } 409 | 410 | @Override 411 | public int transferFrom(ReadableByteChannel channel, int length) throws IOException { 412 | if (!isAccessible()) { 413 | throw bufferIsClosed(this); 414 | } 415 | if (readOnly()) { 416 | throw bufferIsReadOnly(this); 417 | } 418 | length = Math.min(writableBytes(), length); 419 | if (length == 0) { 420 | return 0; 421 | } 422 | checkSet(writerOffset(), length); 423 | int bytesRead = channel.read(writableBuffer().limit(length)); 424 | if (bytesRead != -1) { 425 | skipWritableBytes(bytesRead); 426 | } 427 | return bytesRead; 428 | } 429 | 430 | @Override 431 | public int bytesBefore(byte needle) { 432 | // For the details of this algorithm, see Hacker's Delight, Chapter 6, Searching Words. 433 | // Richard Startin also describes this on his blog: https://richardstartin.github.io/posts/finding-bytes 434 | if (!isAccessible()) { 435 | throw bufferIsClosed(this); 436 | } 437 | int offset = roff; 438 | final int length = woff - roff; 439 | final int end = woff; 440 | 441 | if (length > 7) { 442 | final long pattern = (needle & 0xFFL) * 0x101010101010101L; 443 | for (final int longEnd = offset + (length >>> 3) * Long.BYTES; 444 | offset < longEnd; 445 | offset += Long.BYTES) { 446 | final long word = getLongAtOffset(seg, offset); 447 | 448 | long input = word ^ pattern; 449 | long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; 450 | tmp = ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); 451 | final int binaryPosition = Long.numberOfLeadingZeros(tmp); 452 | 453 | int index = binaryPosition >>> 3; 454 | if (index < Long.BYTES) { 455 | return offset + index - roff; 456 | } 457 | } 458 | } 459 | for (; offset < end; offset++) { 460 | if (getByteAtOffset(seg, offset) == needle) { 461 | return offset - roff; 462 | } 463 | } 464 | 465 | return -1; 466 | } 467 | 468 | @Override 469 | public int bytesBefore(Buffer needle) { 470 | InternalBufferUtils.UncheckedLoadByte uncheckedLoadByte = MemSegBuffer::uncheckedLoadByte; 471 | return InternalBufferUtils.bytesBefore(this, uncheckedLoadByte, 472 | needle, needle instanceof MemSegBuffer ? uncheckedLoadByte : null); 473 | } 474 | 475 | /** 476 | * Used by {@link #bytesBefore(Buffer)}. 477 | */ 478 | private static byte uncheckedLoadByte(Buffer buffer, int offset) { 479 | return ((MemSegBuffer) buffer).seg.get(JAVA_BYTE, offset); 480 | } 481 | 482 | @Override 483 | public ByteCursor openCursor() { 484 | return openCursor(readerOffset(), readableBytes()); 485 | } 486 | 487 | @Override 488 | public ByteCursor openCursor(int fromOffset, int length) { 489 | if (seg == CLOSED_SEGMENT) { 490 | throw bufferIsClosed(this); 491 | } 492 | if (fromOffset < 0) { 493 | throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.'); 494 | } 495 | if (length < 0) { 496 | throw new IllegalArgumentException("The length cannot be negative: " + length + '.'); 497 | } 498 | if (seg.byteSize() < fromOffset + length) { 499 | throw new IllegalArgumentException("The fromOffset + length is beyond the end of the buffer: " + 500 | "fromOffset = " + fromOffset + ", length = " + length + '.'); 501 | } 502 | return new ByteCursor() { 503 | final MemorySegment segment = seg; 504 | int index = fromOffset; 505 | final int end = index + length; 506 | byte byteValue = -1; 507 | 508 | @Override 509 | public boolean readByte() { 510 | if (index < end) { 511 | byteValue = segment.get(JAVA_BYTE, index); 512 | index++; 513 | return true; 514 | } 515 | return false; 516 | } 517 | 518 | @Override 519 | public byte getByte() { 520 | return byteValue; 521 | } 522 | 523 | @Override 524 | public int currentOffset() { 525 | return index; 526 | } 527 | 528 | @Override 529 | public int bytesLeft() { 530 | return end - index; 531 | } 532 | }; 533 | } 534 | 535 | @Override 536 | public ByteCursor openReverseCursor() { 537 | int woff = writerOffset(); 538 | return openReverseCursor(woff == 0? 0 : woff - 1, readableBytes()); 539 | } 540 | 541 | @Override 542 | public ByteCursor openReverseCursor(int fromOffset, int length) { 543 | if (seg == CLOSED_SEGMENT) { 544 | throw bufferIsClosed(this); 545 | } 546 | if (fromOffset < 0) { 547 | throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.'); 548 | } 549 | if (length < 0) { 550 | throw new IllegalArgumentException("The length cannot be negative: " + length + '.'); 551 | } 552 | if (seg.byteSize() <= fromOffset) { 553 | throw new IllegalArgumentException("The fromOffset is beyond the end of the buffer: " + fromOffset + '.'); 554 | } 555 | if (fromOffset - length < -1) { 556 | throw new IllegalArgumentException("The fromOffset - length would underflow the buffer: " + 557 | "fromOffset = " + fromOffset + ", length = " + length + '.'); 558 | } 559 | return new ByteCursor() { 560 | final MemorySegment segment = seg; 561 | int index = fromOffset; 562 | final int end = index - length; 563 | byte byteValue = -1; 564 | 565 | @Override 566 | public boolean readByte() { 567 | if (index > end) { 568 | byteValue = segment.get(JAVA_BYTE, index); 569 | index--; 570 | return true; 571 | } 572 | return false; 573 | } 574 | 575 | @Override 576 | public byte getByte() { 577 | return byteValue; 578 | } 579 | 580 | @Override 581 | public int currentOffset() { 582 | return index; 583 | } 584 | 585 | @Override 586 | public int bytesLeft() { 587 | return index - end; 588 | } 589 | }; 590 | } 591 | 592 | @Override 593 | public Buffer ensureWritable(int size, int minimumGrowth, boolean allowCompaction) { 594 | if (!isAccessible()) { 595 | throw bufferIsClosed(this); 596 | } 597 | if (!isOwned()) { 598 | throw attachTrace(new IllegalStateException( 599 | "Buffer is not owned. Only owned buffers can call ensureWritable.")); 600 | } 601 | if (size < 0) { 602 | throw new IllegalArgumentException("Cannot ensure writable for a negative size: " + size + '.'); 603 | } 604 | if (minimumGrowth < 0) { 605 | throw new IllegalArgumentException("The minimum growth cannot be negative: " + minimumGrowth + '.'); 606 | } 607 | if (seg != wseg) { 608 | throw bufferIsReadOnly(this); 609 | } 610 | if (writableBytes() >= size) { 611 | // We already have enough space. 612 | return this; 613 | } 614 | 615 | if (allowCompaction && writableBytes() + readerOffset() >= size) { 616 | // We can solve this with compaction. 617 | return compact(); 618 | } 619 | 620 | // Allocate a bigger buffer. 621 | long newSize = capacity() + (long) Math.max(size - writableBytes(), minimumGrowth); 622 | InternalBufferUtils.assertValidBufferSize(newSize); 623 | MemSegBuffer buffer = (MemSegBuffer) control.getAllocator().allocate((int) newSize); 624 | 625 | // Copy contents. 626 | copyInto(0, buffer, 0, capacity()); 627 | 628 | // Release the old memory segment and install the new one: 629 | Drop drop = buffer.unsafeGetDrop(); 630 | disconnectDrop(drop); 631 | attachNewMemorySegment(buffer, drop); 632 | return this; 633 | } 634 | 635 | private void disconnectDrop(Drop newDrop) { 636 | var drop = unsafeGetDrop(); 637 | // Disconnect from the current arc drop, since we'll get our own fresh memory segment. 638 | int roff = this.roff; 639 | int woff = this.woff; 640 | drop.drop(this); 641 | unsafeSetDrop(newDrop); 642 | this.roff = roff; 643 | this.woff = woff; 644 | } 645 | 646 | private void attachNewMemorySegment(MemSegBuffer donator, Drop drop) { 647 | base = donator.base; 648 | seg = donator.seg; 649 | wseg = donator.wseg; 650 | drop.attach(this); 651 | } 652 | 653 | @Override 654 | public Buffer split(int splitOffset) { 655 | if (splitOffset < 0) { 656 | throw new IllegalArgumentException("The split offset cannot be negative: " + splitOffset + '.'); 657 | } 658 | if (capacity() < splitOffset) { 659 | throw new IllegalArgumentException("The split offset cannot be greater than the buffer capacity, " + 660 | "but the split offset was " + splitOffset + ", and capacity is " + capacity() + '.'); 661 | } 662 | if (!isAccessible()) { 663 | throw attachTrace(bufferIsClosed(this)); 664 | } 665 | if (!isOwned()) { 666 | throw attachTrace(new IllegalStateException("Cannot split a buffer that is not owned.")); 667 | } 668 | var drop = unsafeGetDrop().fork(); 669 | var splitSegment = seg.asSlice(0, splitOffset); 670 | var splitBuffer = new MemSegBuffer(base, splitSegment, control, drop); 671 | drop.attach(splitBuffer); 672 | splitBuffer.woff = Math.min(woff, splitOffset); 673 | splitBuffer.roff = Math.min(roff, splitOffset); 674 | boolean readOnly = readOnly(); 675 | if (readOnly) { 676 | splitBuffer.makeReadOnly(); 677 | } 678 | seg = seg.asSlice(splitOffset, seg.byteSize() - splitOffset); 679 | if (!readOnly) { 680 | wseg = seg; 681 | } 682 | woff = Math.max(woff, splitOffset) - splitOffset; 683 | roff = Math.max(roff, splitOffset) - splitOffset; 684 | return splitBuffer; 685 | } 686 | 687 | @Override 688 | public Buffer compact() { 689 | if (!isAccessible()) { 690 | throw attachTrace(bufferIsClosed(this)); 691 | } 692 | if (!isOwned()) { 693 | throw attachTrace(new IllegalStateException("Buffer must be owned in order to compact.")); 694 | } 695 | if (readOnly()) { 696 | throw new BufferReadOnlyException("Buffer must be writable in order to compact, but was read-only."); 697 | } 698 | int distance = roff; 699 | if (distance == 0) { 700 | return this; 701 | } 702 | seg.copyFrom(seg.asSlice(roff, woff - roff)); 703 | roff -= distance; 704 | woff -= distance; 705 | return this; 706 | } 707 | 708 | @Override 709 | public int countComponents() { 710 | return 1; 711 | } 712 | 713 | @Override 714 | public int countReadableComponents() { 715 | return readableBytes() > 0? 1 : 0; 716 | } 717 | 718 | @Override 719 | public int countWritableComponents() { 720 | return writableBytes() > 0? 1 : 0; 721 | } 722 | 723 | @SuppressWarnings("unchecked") 724 | @Override 725 | public ComponentIterator forEachComponent() { 726 | return (ComponentIterator) acquire(); 727 | } 728 | 729 | // 730 | private static byte getByteAtOffset(MemorySegment seg, int roff) { 731 | return seg.get(JAVA_BYTE, roff); 732 | } 733 | 734 | private static void setByteAtOffset(MemorySegment seg, int woff, byte value) { 735 | seg.set(JAVA_BYTE, woff, value); 736 | } 737 | 738 | private static short getShortAtOffset(MemorySegment seg, int roff) { 739 | return seg.get(JAVA_SHORT, roff); 740 | } 741 | 742 | private static void setShortAtOffset(MemorySegment seg, int woff, short value) { 743 | seg.set(JAVA_SHORT, woff, value); 744 | } 745 | 746 | private static char getCharAtOffset(MemorySegment seg, int roff) { 747 | return seg.get(JAVA_CHAR, roff); 748 | } 749 | 750 | private static void setCharAtOffset(MemorySegment seg, int woff, char value) { 751 | seg.set(JAVA_CHAR, woff, value); 752 | } 753 | 754 | private static int getIntAtOffset(MemorySegment seg, int roff) { 755 | return seg.get(JAVA_INT, roff); 756 | } 757 | 758 | private static void setIntAtOffset(MemorySegment seg, int woff, int value) { 759 | seg.set(JAVA_INT, woff, value); 760 | } 761 | 762 | private static float getFloatAtOffset(MemorySegment seg, int roff) { 763 | return seg.get(JAVA_FLOAT, roff); 764 | } 765 | 766 | private static void setFloatAtOffset(MemorySegment seg, int woff, float value) { 767 | seg.set(JAVA_FLOAT, woff, value); 768 | } 769 | 770 | private static long getLongAtOffset(MemorySegment seg, int roff) { 771 | return seg.get(JAVA_LONG, roff); 772 | } 773 | 774 | private static void setLongAtOffset(MemorySegment seg, int woff, long value) { 775 | seg.set(JAVA_LONG, woff, value); 776 | } 777 | 778 | private static double getDoubleAtOffset(MemorySegment seg, int roff) { 779 | return seg.get(JAVA_DOUBLE, roff); 780 | } 781 | 782 | private static void setDoubleAtOffset(MemorySegment seg, int woff, double value) { 783 | seg.set(JAVA_DOUBLE, woff, value); 784 | } 785 | 786 | @Override 787 | public byte readByte() { 788 | checkRead(roff, Byte.BYTES); 789 | byte value = getByteAtOffset(seg, roff); 790 | roff += Byte.BYTES; 791 | return value; 792 | } 793 | 794 | @Override 795 | public byte getByte(int roff) { 796 | checkGet(roff, Byte.BYTES); 797 | return getByteAtOffset(seg, roff); 798 | } 799 | 800 | @Override 801 | public int readUnsignedByte() { 802 | checkRead(roff, Byte.BYTES); 803 | int value = getByteAtOffset(seg, roff) & 0xFF; 804 | roff += Byte.BYTES; 805 | return value; 806 | } 807 | 808 | @Override 809 | public int getUnsignedByte(int roff) { 810 | checkGet(roff, Byte.BYTES); 811 | return getByteAtOffset(seg, roff) & 0xFF; 812 | } 813 | 814 | @Override 815 | public Buffer writeByte(byte value) { 816 | checkWrite(woff, Byte.BYTES, true); 817 | setByteAtOffset(wseg, woff, value); 818 | woff += Byte.BYTES; 819 | return this; 820 | } 821 | 822 | @Override 823 | public Buffer setByte(int woff, byte value) { 824 | try { 825 | setByteAtOffset(wseg, woff, value); 826 | return this; 827 | } catch (IndexOutOfBoundsException e) { 828 | throw checkWriteState(e); 829 | } 830 | } 831 | 832 | @Override 833 | public Buffer writeUnsignedByte(int value) { 834 | checkWrite(woff, Byte.BYTES, true); 835 | setByteAtOffset(wseg, woff, (byte) (value & 0xFF)); 836 | woff += Byte.BYTES; 837 | return this; 838 | } 839 | 840 | @Override 841 | public Buffer setUnsignedByte(int woff, int value) { 842 | try { 843 | setByteAtOffset(wseg, woff, (byte) (value & 0xFF)); 844 | return this; 845 | } catch (IndexOutOfBoundsException e) { 846 | throw checkWriteState(e); 847 | } 848 | } 849 | 850 | @Override 851 | public char readChar() { 852 | checkRead(roff, 2); 853 | char value = getCharAtOffset(seg, roff); 854 | roff += 2; 855 | return value; 856 | } 857 | 858 | @Override 859 | public char getChar(int roff) { 860 | checkGet(roff, 2); 861 | return getCharAtOffset(seg, roff); 862 | } 863 | 864 | @Override 865 | public Buffer writeChar(char value) { 866 | checkWrite(woff, 2, true); 867 | setCharAtOffset(wseg, woff, value); 868 | woff += 2; 869 | return this; 870 | } 871 | 872 | @Override 873 | public Buffer setChar(int woff, char value) { 874 | try { 875 | setCharAtOffset(wseg, woff, value); 876 | return this; 877 | } catch (IndexOutOfBoundsException e) { 878 | throw checkWriteState(e); 879 | } 880 | } 881 | 882 | @Override 883 | public short readShort() { 884 | checkRead(roff, Short.BYTES); 885 | short value = getShortAtOffset(seg, roff); 886 | roff += Short.BYTES; 887 | return value; 888 | } 889 | 890 | @Override 891 | public short getShort(int roff) { 892 | checkGet(roff, Short.BYTES); 893 | return getShortAtOffset(seg, roff); 894 | } 895 | 896 | @Override 897 | public int readUnsignedShort() { 898 | checkRead(roff, Short.BYTES); 899 | int value = getShortAtOffset(seg, roff) & 0xFFFF; 900 | roff += Short.BYTES; 901 | return value; 902 | } 903 | 904 | @Override 905 | public int getUnsignedShort(int roff) { 906 | checkGet(roff, Short.BYTES); 907 | return getShortAtOffset(seg, roff) & 0xFFFF; 908 | } 909 | 910 | @Override 911 | public Buffer writeShort(short value) { 912 | checkWrite(woff, Short.BYTES, true); 913 | setShortAtOffset(wseg, woff, value); 914 | woff += Short.BYTES; 915 | return this; 916 | } 917 | 918 | @Override 919 | public Buffer setShort(int woff, short value) { 920 | try { 921 | setShortAtOffset(wseg, woff, value); 922 | return this; 923 | } catch (IndexOutOfBoundsException e) { 924 | throw checkWriteState(e); 925 | } 926 | } 927 | 928 | @Override 929 | public Buffer writeUnsignedShort(int value) { 930 | checkWrite(woff, Short.BYTES, true); 931 | setShortAtOffset(wseg, woff, (short) (value & 0xFFFF)); 932 | woff += Short.BYTES; 933 | return this; 934 | } 935 | 936 | @Override 937 | public Buffer setUnsignedShort(int woff, int value) { 938 | try { 939 | setShortAtOffset(wseg, woff, (short) (value & 0xFFFF)); 940 | return this; 941 | } catch (IndexOutOfBoundsException e) { 942 | throw checkWriteState(e); 943 | } 944 | } 945 | 946 | @Override 947 | public int readMedium() { 948 | checkRead(roff, 3); 949 | int value = getByteAtOffset(seg, roff) << 16 | 950 | (getByteAtOffset(seg, roff + 1) & 0xFF) << 8 | 951 | getByteAtOffset(seg, roff + 2) & 0xFF; 952 | roff += 3; 953 | return value; 954 | } 955 | 956 | @Override 957 | public int getMedium(int roff) { 958 | checkGet(roff, 3); 959 | return getByteAtOffset(seg, roff) << 16 | 960 | (getByteAtOffset(seg, roff + 1) & 0xFF) << 8 | 961 | getByteAtOffset(seg, roff + 2) & 0xFF; 962 | } 963 | 964 | @Override 965 | public int readUnsignedMedium() { 966 | checkRead(roff, 3); 967 | int value = (getByteAtOffset(seg, roff) << 16 | 968 | (getByteAtOffset(seg, roff + 1) & 0xFF) << 8 | 969 | getByteAtOffset(seg, roff + 2) & 0xFF) & 0xFFFFFF; 970 | roff += 3; 971 | return value; 972 | } 973 | 974 | @Override 975 | public int getUnsignedMedium(int roff) { 976 | checkGet(roff, 3); 977 | return (getByteAtOffset(seg, roff) << 16 | 978 | (getByteAtOffset(seg, roff + 1) & 0xFF) << 8 | 979 | getByteAtOffset(seg, roff + 2) & 0xFF) & 0xFFFFFF; 980 | } 981 | 982 | @Override 983 | public Buffer writeMedium(int value) { 984 | checkWrite(woff, 3, true); 985 | setByteAtOffset(wseg, woff, (byte) (value >> 16)); 986 | setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); 987 | setByteAtOffset(wseg, woff + 2, (byte) (value & 0xFF)); 988 | woff += 3; 989 | return this; 990 | } 991 | 992 | @Override 993 | public Buffer setMedium(int woff, int value) { 994 | checkSet(woff, 3); 995 | setByteAtOffset(wseg, woff, (byte) (value >> 16)); 996 | setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); 997 | setByteAtOffset(wseg, woff + 2, (byte) (value & 0xFF)); 998 | return this; 999 | } 1000 | 1001 | @Override 1002 | public Buffer writeUnsignedMedium(int value) { 1003 | checkWrite(woff, 3, true); 1004 | setByteAtOffset(wseg, woff, (byte) (value >> 16)); 1005 | setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); 1006 | setByteAtOffset(wseg, woff + 2, (byte) (value & 0xFF)); 1007 | woff += 3; 1008 | return this; 1009 | } 1010 | 1011 | @Override 1012 | public Buffer setUnsignedMedium(int woff, int value) { 1013 | checkSet(woff, 3); 1014 | setByteAtOffset(wseg, woff, (byte) (value >> 16)); 1015 | setByteAtOffset(wseg, woff + 1, (byte) (value >> 8 & 0xFF)); 1016 | setByteAtOffset(wseg, woff + 2, (byte) (value & 0xFF)); 1017 | return this; 1018 | } 1019 | 1020 | @Override 1021 | public int readInt() { 1022 | checkRead(roff, Integer.BYTES); 1023 | int value = getIntAtOffset(seg, roff); 1024 | roff += Integer.BYTES; 1025 | return value; 1026 | } 1027 | 1028 | @Override 1029 | public int getInt(int roff) { 1030 | checkGet(roff, Integer.BYTES); 1031 | return getIntAtOffset(seg, roff); 1032 | } 1033 | 1034 | @Override 1035 | public long readUnsignedInt() { 1036 | checkRead(roff, Integer.BYTES); 1037 | long value = getIntAtOffset(seg, roff) & 0xFFFFFFFFL; 1038 | roff += Integer.BYTES; 1039 | return value; 1040 | } 1041 | 1042 | @Override 1043 | public long getUnsignedInt(int roff) { 1044 | checkGet(roff, Integer.BYTES); 1045 | return getIntAtOffset(seg, roff) & 0xFFFFFFFFL; 1046 | } 1047 | 1048 | @Override 1049 | public Buffer writeInt(int value) { 1050 | checkWrite(woff, Integer.BYTES, true); 1051 | setIntAtOffset(wseg, woff, value); 1052 | woff += Integer.BYTES; 1053 | return this; 1054 | } 1055 | 1056 | @Override 1057 | public Buffer setInt(int woff, int value) { 1058 | try { 1059 | setIntAtOffset(wseg, woff, value); 1060 | return this; 1061 | } catch (IndexOutOfBoundsException e) { 1062 | throw checkWriteState(e); 1063 | } 1064 | } 1065 | 1066 | @Override 1067 | public Buffer writeUnsignedInt(long value) { 1068 | checkWrite(woff, Integer.BYTES, true); 1069 | setIntAtOffset(wseg, woff, (int) (value & 0xFFFFFFFFL)); 1070 | woff += Integer.BYTES; 1071 | return this; 1072 | } 1073 | 1074 | @Override 1075 | public Buffer setUnsignedInt(int woff, long value) { 1076 | try { 1077 | setIntAtOffset(wseg, woff, (int) (value & 0xFFFFFFFFL)); 1078 | return this; 1079 | } catch (IndexOutOfBoundsException e) { 1080 | throw checkWriteState(e); 1081 | } 1082 | } 1083 | 1084 | @Override 1085 | public float readFloat() { 1086 | checkRead(roff, Float.BYTES); 1087 | float value = getFloatAtOffset(seg, roff); 1088 | roff += Float.BYTES; 1089 | return value; 1090 | } 1091 | 1092 | @Override 1093 | public float getFloat(int roff) { 1094 | checkGet(roff, Float.BYTES); 1095 | return getFloatAtOffset(seg, roff); 1096 | } 1097 | 1098 | @Override 1099 | public Buffer writeFloat(float value) { 1100 | checkWrite(woff, Float.BYTES, true); 1101 | setFloatAtOffset(wseg, woff, value); 1102 | woff += Float.BYTES; 1103 | return this; 1104 | } 1105 | 1106 | @Override 1107 | public Buffer setFloat(int woff, float value) { 1108 | try { 1109 | setFloatAtOffset(wseg, woff, value); 1110 | return this; 1111 | } catch (IndexOutOfBoundsException e) { 1112 | throw checkWriteState(e); 1113 | } 1114 | } 1115 | 1116 | @Override 1117 | public long readLong() { 1118 | checkRead(roff, Long.BYTES); 1119 | long value = getLongAtOffset(seg, roff); 1120 | roff += Long.BYTES; 1121 | return value; 1122 | } 1123 | 1124 | @Override 1125 | public long getLong(int roff) { 1126 | checkGet(roff, Long.BYTES); 1127 | return getLongAtOffset(seg, roff); 1128 | } 1129 | 1130 | @Override 1131 | public Buffer writeLong(long value) { 1132 | checkWrite(woff, Long.BYTES, true); 1133 | setLongAtOffset(wseg, woff, value); 1134 | woff += Long.BYTES; 1135 | return this; 1136 | } 1137 | 1138 | @Override 1139 | public Buffer setLong(int woff, long value) { 1140 | try { 1141 | setLongAtOffset(wseg, woff, value); 1142 | return this; 1143 | } catch (IndexOutOfBoundsException e) { 1144 | throw checkWriteState(e); 1145 | } 1146 | } 1147 | 1148 | @Override 1149 | public double readDouble() { 1150 | checkRead(roff, Double.BYTES); 1151 | double value = getDoubleAtOffset(seg, roff); 1152 | roff += Double.BYTES; 1153 | return value; 1154 | } 1155 | 1156 | @Override 1157 | public double getDouble(int roff) { 1158 | checkGet(roff, Double.BYTES); 1159 | return getDoubleAtOffset(seg, roff); 1160 | } 1161 | 1162 | @Override 1163 | public Buffer writeDouble(double value) { 1164 | checkWrite(woff, Byte.BYTES, true); 1165 | setDoubleAtOffset(wseg, woff, value); 1166 | woff += Double.BYTES; 1167 | return this; 1168 | } 1169 | 1170 | @Override 1171 | public Buffer setDouble(int woff, double value) { 1172 | try { 1173 | setDoubleAtOffset(wseg, woff, value); 1174 | return this; 1175 | } catch (IndexOutOfBoundsException e) { 1176 | throw checkWriteState(e); 1177 | } 1178 | } 1179 | // 1180 | 1181 | @Override 1182 | protected Owned prepareSend() { 1183 | var roff = this.roff; 1184 | var woff = this.woff; 1185 | var readOnly = readOnly(); 1186 | int implicitCapacityLimit = this.implicitCapacityLimit; 1187 | MemorySegment transferSegment = seg; 1188 | MemorySegment base = this.base; 1189 | return new Owned() { 1190 | @Override 1191 | public MemSegBuffer transferOwnership(Drop drop) { 1192 | MemSegBuffer copy = new MemSegBuffer(base, transferSegment, control, drop); 1193 | copy.roff = roff; 1194 | copy.woff = woff; 1195 | copy.implicitCapacityLimit = implicitCapacityLimit; 1196 | if (readOnly) { 1197 | copy.makeReadOnly(); 1198 | } 1199 | return copy; 1200 | } 1201 | }; 1202 | } 1203 | 1204 | @Override 1205 | protected void makeInaccessible() { 1206 | base = CLOSED_SEGMENT; 1207 | seg = CLOSED_SEGMENT; 1208 | wseg = CLOSED_SEGMENT; 1209 | roff = 0; 1210 | woff = 0; 1211 | } 1212 | 1213 | private void checkRead(int index, int size) { 1214 | if (index < 0 || woff < index + size) { 1215 | throw readAccessCheckException(index); 1216 | } 1217 | } 1218 | 1219 | private void checkGet(int index, int size) { 1220 | if (index < 0 || seg.byteSize() < index + size) { 1221 | throw readAccessCheckException(index); 1222 | } 1223 | } 1224 | 1225 | private void checkWrite(int index, int size, boolean mayExpand) { 1226 | if (index < roff || wseg.byteSize() < index + size) { 1227 | handleWriteAccessBoundsFailure(index, size, mayExpand); 1228 | } 1229 | } 1230 | 1231 | private void checkSet(int index, int size) { 1232 | if (index < 0 || wseg.byteSize() < index + size) { 1233 | handleWriteAccessBoundsFailure(index, size, false); 1234 | } 1235 | } 1236 | 1237 | private RuntimeException checkWriteState(IndexOutOfBoundsException ioobe) { 1238 | if (seg == CLOSED_SEGMENT) { 1239 | return bufferIsClosed(this); 1240 | } 1241 | if (wseg != seg) { 1242 | return bufferIsReadOnly(this); 1243 | } 1244 | return ioobe; 1245 | } 1246 | 1247 | MemSegBuffer newConstChild() { 1248 | assert readOnly(); 1249 | Drop drop = unsafeGetDrop().fork(); 1250 | MemSegBuffer child = new MemSegBuffer(this, drop); 1251 | drop.attach(child); 1252 | return child; 1253 | } 1254 | 1255 | private RuntimeException readAccessCheckException(int index) { 1256 | if (seg == CLOSED_SEGMENT) { 1257 | throw bufferIsClosed(this); 1258 | } 1259 | return outOfBounds(index); 1260 | } 1261 | 1262 | private void handleWriteAccessBoundsFailure(int index, int size, boolean mayExpand) { 1263 | if (seg == CLOSED_SEGMENT) { 1264 | throw attachTrace(bufferIsClosed(this)); 1265 | } 1266 | if (wseg != seg) { 1267 | throw bufferIsReadOnly(this); 1268 | } 1269 | int capacity = capacity(); 1270 | if (mayExpand && index >= 0 && index <= capacity && woff + size <= implicitCapacityLimit && isOwned()) { 1271 | // Grow into next power-of-two, but not beyond the implicit limit. 1272 | int minimumGrowth = Math.min( 1273 | Math.max(roundToPowerOfTwo(capacity * 2), size), 1274 | implicitCapacityLimit) - capacity; 1275 | ensureWritable(size, minimumGrowth, false); 1276 | checkSet(index, size); // Verify writing is now possible, without recursing. 1277 | return; 1278 | } 1279 | throw outOfBounds(index); 1280 | } 1281 | 1282 | private IndexOutOfBoundsException outOfBounds(int index) { 1283 | return new IndexOutOfBoundsException( 1284 | "Index " + index + " is out of bounds: [read 0 to " + woff + ", write 0 to " + 1285 | seg.byteSize() + "]."); 1286 | } 1287 | 1288 | Object recoverableMemory() { 1289 | return base; 1290 | } 1291 | } 1292 | -------------------------------------------------------------------------------- /src/main/java/io/netty5/buffer/memseg/ReduceNativeMemoryUsage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.memseg; 17 | 18 | import io.netty5.buffer.internal.InternalBufferUtils; 19 | 20 | final class ReduceNativeMemoryUsage implements Runnable { 21 | private final long size; 22 | 23 | ReduceNativeMemoryUsage(long size) { 24 | this.size = size; 25 | } 26 | 27 | @Override 28 | public void run() { 29 | InternalBufferUtils.MEM_USAGE_NATIVE.add(-size); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "ReduceNativeMemoryUsage(by " + size + " bytes)"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/netty5/buffer/memseg/SegmentMemoryManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.memseg; 17 | 18 | import io.netty5.buffer.AllocationType; 19 | import io.netty5.buffer.AllocatorControl; 20 | import io.netty5.buffer.Buffer; 21 | import io.netty5.buffer.Drop; 22 | import io.netty5.buffer.MemoryManager; 23 | import io.netty5.buffer.StandardAllocationTypes; 24 | import io.netty5.buffer.internal.ArcDrop; 25 | import io.netty5.buffer.internal.InternalBufferUtils; 26 | import io.netty5.buffer.internal.WrappingAllocation; 27 | 28 | import java.lang.foreign.Arena; 29 | import java.lang.foreign.MemorySegment; 30 | 31 | import java.util.function.Function; 32 | 33 | public class SegmentMemoryManager implements MemoryManager { 34 | private static Buffer createHeapBuffer( 35 | long size, Function, Drop> adaptor, AllocatorControl control) { 36 | var segment = MemorySegment.ofArray(new byte[Math.toIntExact(size)]); 37 | var drop = adaptor.apply(InternalBufferUtils.NO_OP_DROP); 38 | return createBuffer(segment, drop, control); 39 | } 40 | 41 | private static Buffer createNativeBuffer( 42 | long size, Function, Drop> adaptor, AllocatorControl control) { 43 | Arena arena = Arena.openShared(); 44 | InternalBufferUtils.MEM_USAGE_NATIVE.add(size); 45 | var segment = MemorySegment.allocateNative(size, arena.session()); 46 | var drop = adaptor.apply(drop(arena, size)); 47 | return createBuffer(segment, drop, control); 48 | } 49 | 50 | @Override 51 | public Buffer allocateShared(AllocatorControl control, long size, Function, Drop> adaptor, 52 | AllocationType type) { 53 | if (type instanceof StandardAllocationTypes stype) { 54 | return switch (stype) { 55 | case ON_HEAP -> createHeapBuffer(size, adaptor, control); 56 | case OFF_HEAP -> createNativeBuffer(size, adaptor, control); 57 | }; 58 | } 59 | if (type instanceof WrappingAllocation allocation) { 60 | var seg = MemorySegment.ofArray(allocation.getArray()); 61 | return createBuffer(seg, adaptor.apply(InternalBufferUtils.NO_OP_DROP), control); 62 | } 63 | throw new IllegalArgumentException("Unknown allocation type: " + type); 64 | } 65 | 66 | @Override 67 | public Buffer allocateConstChild(Buffer readOnlyConstParent) { 68 | assert readOnlyConstParent.readOnly(); 69 | MemSegBuffer buf = (MemSegBuffer) readOnlyConstParent; 70 | return buf.newConstChild(); 71 | } 72 | 73 | private static Drop drop(Arena arena, long nativeMemoryReserved) { 74 | var drop = new MemorySessionCloseDrop(arena, nativeMemoryReserved); 75 | // Wrap in an ArcDrop because closing the session will close all associated memory segments. 76 | return ArcDrop.wrap(drop); 77 | } 78 | 79 | @Override 80 | public Object unwrapRecoverableMemory(Buffer buf) { 81 | var b = (MemSegBuffer) buf; 82 | return b.recoverableMemory(); 83 | } 84 | 85 | @Override 86 | public Buffer recoverMemory(AllocatorControl allocatorControl, Object recoverableMemory, Drop drop) { 87 | var segment = (MemorySegment) recoverableMemory; 88 | return createBuffer(segment, drop, allocatorControl); 89 | } 90 | 91 | private static MemSegBuffer createBuffer(MemorySegment segment, Drop drop, AllocatorControl control) { 92 | Drop concreteDrop = InternalBufferUtils.convert(drop); 93 | MemSegBuffer buffer = new MemSegBuffer(segment, segment, control, concreteDrop); 94 | concreteDrop.attach(buffer); 95 | return buffer; 96 | } 97 | 98 | @Override 99 | public Object sliceMemory(Object memory, int offset, int length) { 100 | var segment = (MemorySegment) memory; 101 | return segment.asSlice(offset, length); 102 | } 103 | 104 | @Override 105 | public void clearMemory(Object memory) { 106 | var segment = (MemorySegment) memory; 107 | segment.fill((byte) 0); 108 | } 109 | 110 | @Override 111 | public String implementationName() { 112 | return "MemorySegment"; 113 | } 114 | 115 | private record MemorySessionCloseDrop(Arena arena, long nativeMemoryReserved) implements Drop { 116 | @Override 117 | public void drop(Buffer obj) { 118 | arena.close(); 119 | InternalBufferUtils.MEM_USAGE_NATIVE.add(-nativeMemoryReserved); 120 | } 121 | 122 | @Override 123 | public Drop fork() { 124 | return this; 125 | } 126 | 127 | @Override 128 | public void attach(Buffer obj) { 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/io/netty5/buffer/memseg/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * Experimental {@code Buffer} implementation, based on the MemorySegment API from OpenJDK Panama Foreign. 19 | */ 20 | package io.netty5.buffer.memseg; 21 | -------------------------------------------------------------------------------- /src/main/java/module-info.java.disabled: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | import io.netty5.buffer.api.MemoryManager; 17 | import io.netty5.buffer.api.memseg.SegmentMemoryManager; 18 | 19 | module netty.incubator.buffer.memseg { 20 | requires io.netty.common; 21 | requires io.netty.buffer; 22 | 23 | // Optional dependencies, needed for some examples. 24 | requires static java.logging; 25 | 26 | // Permit reflective access to non-public members. 27 | // Also means we don't have to make all test methods etc. public for JUnit to access them. 28 | opens io.netty.buffer.api.memseg; 29 | 30 | provides MemoryManager with 31 | SegmentMemoryManager; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.netty.buffer.MemoryManager: -------------------------------------------------------------------------------- 1 | io.netty5.buffer.memseg.SegmentMemoryManager 2 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/memseg/benchmarks/MemSegBufAccessBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.memseg.benchmarks; 17 | 18 | import io.netty5.buffer.Buffer; 19 | import io.netty5.buffer.BufferAllocator; 20 | import org.openjdk.jmh.annotations.Benchmark; 21 | import org.openjdk.jmh.annotations.BenchmarkMode; 22 | import org.openjdk.jmh.annotations.Fork; 23 | import org.openjdk.jmh.annotations.Measurement; 24 | import org.openjdk.jmh.annotations.Mode; 25 | import org.openjdk.jmh.annotations.OutputTimeUnit; 26 | import org.openjdk.jmh.annotations.Param; 27 | import org.openjdk.jmh.annotations.Scope; 28 | import org.openjdk.jmh.annotations.Setup; 29 | import org.openjdk.jmh.annotations.State; 30 | import org.openjdk.jmh.annotations.TearDown; 31 | import org.openjdk.jmh.annotations.Warmup; 32 | 33 | import java.util.concurrent.TimeUnit; 34 | 35 | @Warmup(iterations = 5, time = 1500, timeUnit = TimeUnit.MILLISECONDS) 36 | @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) 37 | @Fork(1) 38 | @BenchmarkMode(Mode.AverageTime) 39 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 40 | @State(Scope.Benchmark) 41 | public class MemSegBufAccessBenchmark { 42 | public enum BBufType { 43 | DIRECT { 44 | @Override 45 | Buffer newBuffer() { 46 | return BufferAllocator.offHeapUnpooled().allocate(64); 47 | } 48 | }, 49 | HEAP { 50 | @Override 51 | Buffer newBuffer() { 52 | return BufferAllocator.onHeapUnpooled().allocate(64); 53 | } 54 | }, 55 | // COMPOSITE { 56 | // @Override 57 | // Buffer newBuffer() { 58 | // return Unpooled.wrappedBuffer(UNSAFE.newBuffer(), HEAP.newBuffer()); 59 | // } 60 | // }, 61 | // NIO { 62 | // @Override 63 | // Buffer newBuffer() { 64 | // return new NioFacade(BBuffer.allocateDirect(64)); 65 | // } 66 | // } 67 | ; 68 | abstract Buffer newBuffer(); 69 | } 70 | 71 | @Param 72 | public BBufType bufferType; 73 | 74 | @Param("8") 75 | public int batchSize; // applies only to readBatch benchmark 76 | 77 | @Setup 78 | public void setup() { 79 | buffer = bufferType.newBuffer(); 80 | buffer.writerOffset(batchSize); 81 | } 82 | 83 | private Buffer buffer; 84 | 85 | @TearDown 86 | public void tearDown() { 87 | buffer.close(); 88 | } 89 | 90 | @Benchmark 91 | public long setGetLong() { 92 | return buffer.setLong(0, 1).getLong(0); 93 | } 94 | 95 | @Benchmark 96 | public Buffer setLong() { 97 | return buffer.setLong(0, 1); 98 | } 99 | 100 | @Benchmark 101 | public int readBatch() { 102 | buffer.readerOffset(0); 103 | int result = 0; 104 | // WARNING! 105 | // Please do not replace this sum loop with a BlackHole::consume loop: 106 | // BlackHole::consume could prevent the JVM to perform certain optimizations 107 | // forcing ByteBuf::readByte to be executed in order. 108 | // The purpose of the benchmark is to mimic accesses on ByteBuf 109 | // as in a real (single-threaded) case ie without (compiler) memory barriers that would 110 | // disable certain optimizations or would make bounds checks (if enabled) 111 | // to happen on each access. 112 | for (int i = 0, size = batchSize; i < size; i++) { 113 | result += buffer.readByte(); 114 | } 115 | return result; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/memseg/benchmarks/MemorySegmentCloseBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.memseg.benchmarks; 17 | 18 | import org.openjdk.jmh.annotations.Benchmark; 19 | import org.openjdk.jmh.annotations.BenchmarkMode; 20 | import org.openjdk.jmh.annotations.Fork; 21 | import org.openjdk.jmh.annotations.Measurement; 22 | import org.openjdk.jmh.annotations.Mode; 23 | import org.openjdk.jmh.annotations.OutputTimeUnit; 24 | import org.openjdk.jmh.annotations.Param; 25 | import org.openjdk.jmh.annotations.Scope; 26 | import org.openjdk.jmh.annotations.Setup; 27 | import org.openjdk.jmh.annotations.State; 28 | import org.openjdk.jmh.annotations.TearDown; 29 | import org.openjdk.jmh.annotations.Warmup; 30 | 31 | import java.lang.foreign.Arena; 32 | import java.lang.foreign.MemorySegment; 33 | import java.util.concurrent.ExecutorService; 34 | import java.util.concurrent.Executors; 35 | import java.util.concurrent.TimeUnit; 36 | 37 | @Warmup(iterations = 10, time = 1) 38 | @Measurement(iterations = 10, time = 1) 39 | @Fork(value = 5, jvmArgsAppend = { "-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints" }) 40 | @BenchmarkMode(Mode.AverageTime) 41 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 42 | @State(Scope.Benchmark) 43 | public class MemorySegmentCloseBenchmark { 44 | @Param({"0", "10", "100"}) 45 | public int unrelatedThreads; 46 | 47 | @Param({"16"/*, "124", "1024"*/}) 48 | public int size; 49 | 50 | public ExecutorService unrelatedThreadPool; 51 | public byte[] array; 52 | 53 | @Setup 54 | public void setUp() { 55 | unrelatedThreadPool = unrelatedThreads > 0? Executors.newFixedThreadPool(unrelatedThreads) : null; 56 | array = new byte[size]; 57 | } 58 | 59 | @TearDown 60 | public void tearDown() throws InterruptedException { 61 | if (unrelatedThreadPool != null) { 62 | unrelatedThreadPool.shutdown(); 63 | unrelatedThreadPool.awaitTermination(1, TimeUnit.MINUTES); 64 | unrelatedThreadPool = null; 65 | } 66 | } 67 | 68 | @Benchmark 69 | public MemorySegment nativeConfined() { 70 | try (Arena arena = Arena.openConfined()) { 71 | return MemorySegment.allocateNative(size, arena.session()); 72 | } 73 | } 74 | 75 | @Benchmark 76 | public MemorySegment nativeShared() { 77 | try (Arena arena = Arena.openShared()) { 78 | return MemorySegment.allocateNative(size, arena.session()); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/memseg/benchmarks/MemorySegmentClosedByCleanerBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.memseg.benchmarks; 17 | 18 | import io.netty5.buffer.BufferAllocator; 19 | import io.netty5.buffer.MemoryManager; 20 | import io.netty5.buffer.memseg.SegmentMemoryManager; 21 | import io.netty5.buffer.Buffer; 22 | import org.openjdk.jmh.annotations.Benchmark; 23 | import org.openjdk.jmh.annotations.BenchmarkMode; 24 | import org.openjdk.jmh.annotations.Fork; 25 | import org.openjdk.jmh.annotations.Measurement; 26 | import org.openjdk.jmh.annotations.Mode; 27 | import org.openjdk.jmh.annotations.OutputTimeUnit; 28 | import org.openjdk.jmh.annotations.Param; 29 | import org.openjdk.jmh.annotations.Scope; 30 | import org.openjdk.jmh.annotations.Setup; 31 | import org.openjdk.jmh.annotations.State; 32 | import org.openjdk.jmh.annotations.Warmup; 33 | 34 | import java.util.concurrent.ThreadLocalRandom; 35 | import java.util.concurrent.TimeUnit; 36 | 37 | import static java.util.concurrent.CompletableFuture.completedFuture; 38 | 39 | @Warmup(iterations = 15, time = 1) 40 | @Measurement(iterations = 15, time = 1) 41 | @Fork(value = 5, jvmArgsAppend = { "-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints" }) 42 | @BenchmarkMode(Mode.AverageTime) 43 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 44 | @State(Scope.Benchmark) 45 | public class MemorySegmentClosedByCleanerBenchmark { 46 | private static final BufferAllocator heap; 47 | private static final BufferAllocator heapPooled; 48 | private static final BufferAllocator direct; 49 | private static final BufferAllocator directPooled; 50 | 51 | static { 52 | class Allocators { 53 | final BufferAllocator heap; 54 | final BufferAllocator pooledHeap; 55 | final BufferAllocator direct; 56 | final BufferAllocator pooledDirect; 57 | 58 | Allocators(BufferAllocator heap, BufferAllocator pooledHeap, 59 | BufferAllocator direct, BufferAllocator pooledDirect) { 60 | this.heap = heap; 61 | this.pooledHeap = pooledHeap; 62 | this.direct = direct; 63 | this.pooledDirect = pooledDirect; 64 | } 65 | } 66 | 67 | var allocs = MemoryManager.using(new SegmentMemoryManager(), () -> { 68 | return new Allocators(BufferAllocator.onHeapUnpooled(), BufferAllocator.onHeapPooled(), 69 | BufferAllocator.offHeapUnpooled(), BufferAllocator.offHeapPooled()); 70 | }); 71 | 72 | heap = allocs.heap; 73 | heapPooled = allocs.pooledHeap; 74 | direct = allocs.direct; 75 | directPooled = allocs.pooledDirect; 76 | } 77 | 78 | @Param({"heavy", "light"}) 79 | public String workload; 80 | public boolean isHeavy; 81 | 82 | @Setup 83 | public void setUp() { 84 | if ("heavy".equals(workload)) { 85 | isHeavy = true; 86 | } else if ("light".equals(workload)) { 87 | isHeavy = false; 88 | } else { 89 | throw new IllegalArgumentException("Unsupported workload: " + workload); 90 | } 91 | } 92 | 93 | @Benchmark 94 | public Buffer explicitCloseHeap() throws Exception { 95 | try (Buffer buf = process(heap.allocate(256))) { 96 | return buf; 97 | } 98 | } 99 | 100 | @Benchmark 101 | public Buffer explicitPooledCloseHeap() throws Exception { 102 | try (Buffer buf = process(heapPooled.allocate(256))) { 103 | return buf; 104 | } 105 | } 106 | 107 | @Benchmark 108 | public Buffer explicitCloseDirect() throws Exception { 109 | try (Buffer buf = process(direct.allocate(256))) { 110 | return buf; 111 | } 112 | } 113 | 114 | @Benchmark 115 | public Buffer explicitPooledCloseDirect() throws Exception { 116 | try (Buffer buf = process(directPooled.allocate(256))) { 117 | return buf; 118 | } 119 | } 120 | 121 | @Benchmark 122 | public Buffer cleanerClose() throws Exception { 123 | return process(direct.allocate(256)); 124 | } 125 | 126 | @Benchmark 127 | public Buffer cleanerClosePooled() throws Exception { 128 | return process(directPooled.allocate(256)); 129 | } 130 | 131 | private Buffer process(Buffer buffer) throws Exception { 132 | // Simulate some async network server thingy, processing the buffer. 133 | var tlr = ThreadLocalRandom.current(); 134 | if (isHeavy) { 135 | return completedFuture(buffer.send()).thenApplyAsync(send -> { 136 | try (Buffer buf = send.receive()) { 137 | while (buf.writableBytes() > 0) { 138 | buf.writeByte((byte) tlr.nextInt()); 139 | } 140 | return buf.send(); 141 | } 142 | }).thenApplyAsync(send -> { 143 | try (Buffer buf = send.receive()) { 144 | byte b = 0; 145 | while (buf.readableBytes() > 0) { 146 | b += buf.readByte(); 147 | } 148 | buf.fill(b); 149 | return buf.send(); 150 | } 151 | }).get().receive(); 152 | } else { 153 | while (buffer.writableBytes() > 0) { 154 | buffer.writeByte((byte) tlr.nextInt()); 155 | } 156 | byte b = 0; 157 | while (buffer.readableBytes() > 0) { 158 | b += buffer.readByte(); 159 | } 160 | buffer.fill(b); 161 | return buffer; 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/EchoIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests; 17 | 18 | import io.netty5.bootstrap.Bootstrap; 19 | import io.netty5.bootstrap.ServerBootstrap; 20 | import io.netty5.buffer.Buffer; 21 | import io.netty5.buffer.BufferAllocator; 22 | import io.netty5.buffer.DefaultBufferAllocators; 23 | import io.netty5.buffer.tests.examples.echo.EchoServerHandler; 24 | import io.netty5.channel.ChannelHandler; 25 | import io.netty5.channel.ChannelHandlerContext; 26 | import io.netty5.channel.ChannelInitializer; 27 | import io.netty5.channel.ChannelOption; 28 | import io.netty5.channel.ChannelPipeline; 29 | import io.netty5.channel.EventLoopGroup; 30 | import io.netty5.channel.MultithreadEventLoopGroup; 31 | import io.netty5.channel.nio.NioHandler; 32 | import io.netty5.channel.socket.SocketChannel; 33 | import io.netty5.channel.socket.nio.NioServerSocketChannel; 34 | import io.netty5.channel.socket.nio.NioSocketChannel; 35 | import io.netty5.handler.logging.LogLevel; 36 | import io.netty5.handler.logging.LoggingHandler; 37 | import org.junit.jupiter.api.Test; 38 | 39 | import java.net.InetSocketAddress; 40 | 41 | import static org.junit.jupiter.api.Assertions.assertEquals; 42 | 43 | public class EchoIT { 44 | // In this test we have a server and a client, where the server echos back anything it receives, 45 | // and our client sends a single message to the server, and then verifies that it receives it back. 46 | 47 | @Test 48 | void echoServerMustReplyWithSameData() throws Exception { 49 | EventLoopGroup bossGroup = new MultithreadEventLoopGroup(1, NioHandler.newFactory()); 50 | EventLoopGroup workerGroup = new MultithreadEventLoopGroup(NioHandler.newFactory()); 51 | final EchoServerHandler serverHandler = new EchoServerHandler(); 52 | try { 53 | ServerBootstrap server = new ServerBootstrap(); 54 | server.group(bossGroup, workerGroup) 55 | .channel(NioServerSocketChannel.class) 56 | .childOption(ChannelOption.BUFFER_ALLOCATOR, DefaultBufferAllocators.preferredAllocator()) 57 | .option(ChannelOption.SO_BACKLOG, 100) 58 | .handler(new LoggingHandler(LogLevel.INFO)) 59 | .childHandler(new ChannelInitializer() { 60 | @Override 61 | public void initChannel(SocketChannel ch) throws Exception { 62 | ChannelPipeline p = ch.pipeline(); 63 | p.addLast(new LoggingHandler(LogLevel.INFO)); 64 | p.addLast(serverHandler); 65 | } 66 | }); 67 | 68 | // Start the server. 69 | var bind = server.bind("localhost", 0).asStage().sync().getNow(); 70 | InetSocketAddress serverAddress = (InetSocketAddress) bind.localAddress(); 71 | 72 | // Configure the client. 73 | EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory()); 74 | try { 75 | Bootstrap b = new Bootstrap(); 76 | b.group(group) 77 | .channel(NioSocketChannel.class) 78 | .option(ChannelOption.TCP_NODELAY, true) 79 | .handler(new ChannelInitializer() { 80 | @Override 81 | public void initChannel(SocketChannel ch) throws Exception { 82 | ChannelPipeline p = ch.pipeline(); 83 | p.addLast(new LoggingHandler(LogLevel.INFO)); 84 | p.addLast(new EchoClientHandler()); 85 | } 86 | }); 87 | 88 | // Start the client. 89 | var channel = b.connect(serverAddress).asStage().sync().getNow(); 90 | 91 | // Wait until the connection is closed. 92 | channel.closeFuture().asStage().sync(); 93 | } finally { 94 | // Shut down the event loop to terminate all threads. 95 | group.shutdownGracefully(); 96 | } 97 | 98 | // Shut down the server. 99 | bind.close().asStage().sync(); 100 | } finally { 101 | // Shut down all event loops to terminate all threads. 102 | bossGroup.shutdownGracefully(); 103 | workerGroup.shutdownGracefully(); 104 | } 105 | } 106 | 107 | static class EchoClientHandler implements ChannelHandler { 108 | private static final int SIZE = 256; 109 | private final Buffer firstMessage; 110 | 111 | /** 112 | * Creates a client-side handler. 113 | */ 114 | EchoClientHandler() { 115 | firstMessage = BufferAllocator.onHeapUnpooled().allocate(SIZE); 116 | for (int i = 0; i < SIZE; i++) { 117 | firstMessage.writeByte((byte) i); 118 | } 119 | } 120 | 121 | @Override 122 | public void channelActive(ChannelHandlerContext ctx) { 123 | ctx.writeAndFlush(firstMessage); 124 | } 125 | 126 | @Override 127 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 128 | Buffer buf = (Buffer) msg; 129 | assertEquals(SIZE, buf.readableBytes()); 130 | for (int i = 0; i < SIZE; i++) { 131 | assertEquals((byte) i, buf.readByte()); 132 | } 133 | } 134 | 135 | @Override 136 | public void channelReadComplete(ChannelHandlerContext ctx) { 137 | ctx.close(); 138 | } 139 | 140 | @Override 141 | public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 142 | // Close the connection when an exception is raised. 143 | ctx.close(); 144 | throw new RuntimeException(cause); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/benchmarks/ByteIterationBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.benchmarks; 17 | 18 | import io.netty5.buffer.Buffer; 19 | import io.netty5.buffer.BufferAllocator; 20 | import io.netty5.buffer.CompositeBuffer; 21 | import org.openjdk.jmh.annotations.Benchmark; 22 | import org.openjdk.jmh.annotations.BenchmarkMode; 23 | import org.openjdk.jmh.annotations.Fork; 24 | import org.openjdk.jmh.annotations.Measurement; 25 | import org.openjdk.jmh.annotations.Mode; 26 | import org.openjdk.jmh.annotations.OutputTimeUnit; 27 | import org.openjdk.jmh.annotations.Param; 28 | import org.openjdk.jmh.annotations.Scope; 29 | import org.openjdk.jmh.annotations.Setup; 30 | import org.openjdk.jmh.annotations.State; 31 | import org.openjdk.jmh.annotations.TearDown; 32 | import org.openjdk.jmh.annotations.Warmup; 33 | 34 | import java.util.List; 35 | import java.util.concurrent.ThreadLocalRandom; 36 | import java.util.concurrent.TimeUnit; 37 | 38 | @Warmup(iterations = 10, time = 1) 39 | @Measurement(iterations = 10, time = 1) 40 | @Fork(value = 5, jvmArgsAppend = { "-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints" }) 41 | @BenchmarkMode(Mode.AverageTime) 42 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 43 | @State(Scope.Benchmark) 44 | public class ByteIterationBenchmark { 45 | private static final int SIZE = 4096; 46 | @Param({"heap", "direct", "composite-heap", "composite-direct"}) 47 | public String type; 48 | 49 | BufferAllocator allocator; 50 | private Buffer buf; 51 | 52 | @Setup 53 | public void setUp() { 54 | switch (type) { 55 | case "heap": 56 | allocator = BufferAllocator.onHeapUnpooled(); 57 | buf = allocator.allocate(SIZE); 58 | break; 59 | case "direct": 60 | allocator = BufferAllocator.offHeapUnpooled(); 61 | buf = allocator.allocate(SIZE); 62 | break; 63 | case "composite-heap": 64 | allocator = BufferAllocator.onHeapUnpooled(); 65 | try (var a = allocator.allocate(SIZE / 2); 66 | var b = allocator.allocate(SIZE / 2)) { 67 | buf = allocator.compose(List.of(a.send(), b.send())); 68 | } 69 | break; 70 | case "composite-direct": 71 | allocator = BufferAllocator.offHeapUnpooled(); 72 | try (var a = allocator.allocate(SIZE / 2); 73 | var b = allocator.allocate(SIZE / 2)) { 74 | buf = allocator.compose(List.of(a.send(), b.send())); 75 | } 76 | break; 77 | default: 78 | throw new IllegalArgumentException("Unknown buffer type: " + type + '.'); 79 | } 80 | ThreadLocalRandom tlr = ThreadLocalRandom.current(); 81 | while (buf.writableBytes() > 7) { 82 | buf.writeLong(tlr.nextLong()); 83 | } 84 | while (buf.writableBytes() > 0) { 85 | buf.writeByte((byte) tlr.nextInt()); 86 | } 87 | } 88 | 89 | @TearDown 90 | public void tearDown() { 91 | buf.close(); 92 | allocator.close(); 93 | } 94 | 95 | @Benchmark 96 | public long sum() { 97 | var itr = buf.openCursor(); 98 | long sum = 0; 99 | while (itr.readByte()) { 100 | sum += itr.getByte(); 101 | } 102 | return sum; 103 | } 104 | 105 | @Benchmark 106 | public long sumReverse() { 107 | var itr = buf.openReverseCursor(); 108 | long sum = 0; 109 | while (itr.readByte()) { 110 | sum += itr.getByte(); 111 | } 112 | return sum; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/benchmarks/SendBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.benchmarks; 17 | 18 | import io.netty5.buffer.Buffer; 19 | import io.netty5.buffer.BufferAllocator; 20 | import io.netty5.util.Send; 21 | import org.openjdk.jmh.annotations.Benchmark; 22 | import org.openjdk.jmh.annotations.BenchmarkMode; 23 | import org.openjdk.jmh.annotations.Fork; 24 | import org.openjdk.jmh.annotations.Measurement; 25 | import org.openjdk.jmh.annotations.Mode; 26 | import org.openjdk.jmh.annotations.OutputTimeUnit; 27 | import org.openjdk.jmh.annotations.Scope; 28 | import org.openjdk.jmh.annotations.State; 29 | import org.openjdk.jmh.annotations.Warmup; 30 | 31 | import java.util.concurrent.TimeUnit; 32 | import java.util.function.Function; 33 | 34 | import static java.util.concurrent.CompletableFuture.completedFuture; 35 | 36 | @Warmup(iterations = 10, time = 1) 37 | @Measurement(iterations = 10, time = 1) 38 | @Fork(value = 5, jvmArgsAppend = { "-XX:+UnlockDiagnosticVMOptions", "-XX:+DebugNonSafepoints" }) 39 | @BenchmarkMode(Mode.AverageTime) 40 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 41 | @State(Scope.Benchmark) 42 | public class SendBenchmark { 43 | private static final BufferAllocator NON_POOLED = BufferAllocator.onHeapUnpooled(); 44 | private static final BufferAllocator POOLED = BufferAllocator.onHeapPooled(); 45 | private static final Function, Send> BUFFER_BOUNCE = send -> { 46 | try (Buffer buf = send.receive()) { 47 | return buf.send(); 48 | } 49 | }; 50 | 51 | @Benchmark 52 | public Buffer sendNonPooled() throws Exception { 53 | try (Buffer buf = NON_POOLED.allocate(8)) { 54 | try (Buffer receive = completedFuture(buf.send()).thenApplyAsync(BUFFER_BOUNCE).get().receive()) { 55 | return receive; 56 | } 57 | } 58 | } 59 | 60 | @Benchmark 61 | public Buffer sendPooled() throws Exception { 62 | try (Buffer buf = POOLED.allocate(8)) { 63 | try (Buffer receive = completedFuture(buf.send()).thenApplyAsync(BUFFER_BOUNCE).get().receive()) { 64 | return receive; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/AsyncExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples; 17 | 18 | import io.netty5.buffer.Buffer; 19 | import io.netty5.buffer.BufferAllocator; 20 | 21 | import static java.lang.System.out; 22 | import static java.util.concurrent.CompletableFuture.completedFuture; 23 | 24 | public final class AsyncExample { 25 | public static void main(String[] args) throws Exception { 26 | try (BufferAllocator allocator = BufferAllocator.offHeapPooled(); 27 | Buffer startBuf = allocator.allocate(16)) { 28 | startBuf.writeLong(threadId()); 29 | 30 | completedFuture(startBuf.send()).thenApplyAsync(send -> { 31 | try (Buffer buf = send.receive()) { 32 | buf.writeLong(threadId()); 33 | return buf.send(); 34 | } 35 | }).thenAcceptAsync(send -> { 36 | try (Buffer buf = send.receive()) { 37 | out.println("First thread id was " + buf.readLong()); 38 | out.println("Then sent to " + buf.readLong()); 39 | out.println("And now in thread " + threadId()); 40 | } 41 | }).get(); 42 | } 43 | } 44 | 45 | private static long threadId() { 46 | return Thread.currentThread().threadId(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/ComposingAndSplittingExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples; 17 | 18 | import io.netty5.buffer.Buffer; 19 | import io.netty5.buffer.BufferAllocator; 20 | 21 | import java.util.List; 22 | import java.util.concurrent.ThreadLocalRandom; 23 | 24 | public final class ComposingAndSplittingExample { 25 | public static void main(String[] args) { 26 | try (BufferAllocator allocator = BufferAllocator.offHeapPooled(); 27 | Buffer buf = createBigBuffer(allocator)) { 28 | 29 | ThreadLocalRandom tlr = ThreadLocalRandom.current(); 30 | for (int i = 0; i < tlr.nextInt(4, 200); i++) { 31 | buf.writeByte((byte) tlr.nextInt()); 32 | } 33 | 34 | try (Buffer split = buf.split()) { 35 | split.send(); 36 | System.out.println("buf.capacity() = " + buf.capacity()); 37 | System.out.println("buf.readableBytes() = " + buf.readableBytes()); 38 | System.out.println("---"); 39 | System.out.println("split.capacity() = " + split.capacity()); 40 | System.out.println("split.readableBytes() = " + split.readableBytes()); 41 | } 42 | } 43 | } 44 | 45 | private static Buffer createBigBuffer(BufferAllocator allocator) { 46 | return allocator.compose(List.of( 47 | allocator.allocate(64).send(), 48 | allocator.allocate(64).send(), 49 | allocator.allocate(64).send(), 50 | allocator.allocate(64).send())); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/FileCopyExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples; 17 | 18 | import io.netty5.buffer.Buffer; 19 | import io.netty5.buffer.BufferAllocator; 20 | import io.netty5.buffer.CompositeBuffer; 21 | import io.netty5.util.Send; 22 | 23 | import java.nio.channels.FileChannel; 24 | import java.nio.file.Path; 25 | import java.util.concurrent.ArrayBlockingQueue; 26 | import java.util.concurrent.ExecutorService; 27 | import java.util.concurrent.Executors; 28 | 29 | import static java.nio.file.StandardOpenOption.CREATE; 30 | import static java.nio.file.StandardOpenOption.READ; 31 | import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; 32 | import static java.nio.file.StandardOpenOption.WRITE; 33 | 34 | public final class FileCopyExample { 35 | public static void main(String[] args) throws Exception { 36 | ExecutorService executor = Executors.newFixedThreadPool(2); 37 | ArrayBlockingQueue> queue = new ArrayBlockingQueue<>(8); 38 | try (BufferAllocator allocator = BufferAllocator.offHeapPooled(); 39 | var input = FileChannel.open(Path.of("/dev/urandom"), READ); 40 | var output = FileChannel.open(Path.of("random.bin"), CREATE, TRUNCATE_EXISTING, WRITE)) { 41 | Send done = CompositeBuffer.compose(allocator).send(); 42 | 43 | var reader = executor.submit(() -> { 44 | for (int i = 0; i < 1024; i++) { 45 | try (Buffer in = allocator.allocate(1024)) { 46 | System.out.println("in = " + in); 47 | try (var itr = in.forEachComponent()) { 48 | for (var c = itr.firstWritable(); c != null; c = c.nextWritable()) { 49 | var bb = c.writableBuffer(); 50 | while (bb.hasRemaining()) { 51 | input.read(bb); 52 | } 53 | } 54 | } 55 | System.out.println("Sending " + in.readableBytes() + " bytes."); 56 | queue.put(in.send()); 57 | } 58 | } 59 | queue.put(done); 60 | return null; 61 | }); 62 | 63 | var writer = executor.submit(() -> { 64 | Send send; 65 | while ((send = queue.take()) != done) { 66 | try (Buffer out = send.receive()) { 67 | System.out.println("Received " + out.readableBytes() + " bytes."); 68 | try (var itr = out.forEachComponent()) { 69 | for (var c = itr.firstReadable(); c != null; c = c.nextReadable()) { 70 | var bb = c.readableBuffer(); 71 | while (bb.hasRemaining()) { 72 | output.write(bb); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | output.force(true); 79 | return null; 80 | }); 81 | 82 | reader.get(); 83 | writer.get(); 84 | } finally { 85 | executor.shutdown(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/SendExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples; 17 | 18 | import io.netty5.buffer.Buffer; 19 | import io.netty5.buffer.BufferAllocator; 20 | import io.netty5.util.Send; 21 | 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.Future; 24 | 25 | import static java.util.concurrent.Executors.newFixedThreadPool; 26 | import static java.util.concurrent.Executors.newSingleThreadExecutor; 27 | 28 | public class SendExample { 29 | 30 | static final class Ex1 { 31 | public static void main(String[] args) throws Exception { 32 | ExecutorService executor = 33 | newSingleThreadExecutor(); 34 | BufferAllocator allocator = BufferAllocator.onHeapUnpooled(); 35 | 36 | var future = beginTask(executor, allocator); 37 | future.get(); 38 | 39 | allocator.close(); 40 | executor.shutdown(); 41 | } 42 | 43 | private static Future beginTask( 44 | ExecutorService executor, BufferAllocator allocator) { 45 | try (Buffer buf = allocator.allocate(32)) { 46 | // !!! pit-fall: buffer life-time ends before task completes 47 | return executor.submit(new Task(buf)); 48 | } 49 | } 50 | 51 | private static class Task implements Runnable { 52 | private final Buffer buf; 53 | 54 | Task(Buffer buf) { 55 | this.buf = buf; 56 | } 57 | 58 | @Override 59 | public void run() { 60 | // !!! danger: access out-side owning thread. 61 | while (buf.writableBytes() > 0) { 62 | buf.writeByte((byte) 42); 63 | } 64 | } 65 | } 66 | } 67 | 68 | static final class Ex2 { 69 | public static void main(String[] args) throws Exception { 70 | ExecutorService executor = newSingleThreadExecutor(); 71 | BufferAllocator allocator = BufferAllocator.onHeapUnpooled(); 72 | 73 | var future = beginTask(executor, allocator); 74 | future.get(); 75 | 76 | allocator.close(); 77 | executor.shutdown(); 78 | } 79 | 80 | private static Future beginTask( 81 | ExecutorService executor, BufferAllocator allocator) { 82 | try (Buffer buf = allocator.allocate(32)) { 83 | return executor.submit(new Task(buf.send())); 84 | } 85 | } 86 | 87 | private static class Task implements Runnable { 88 | private final Send send; 89 | 90 | Task(Send send) { 91 | this.send = send; 92 | } 93 | 94 | @Override 95 | public void run() { 96 | try (Buffer buf = send.receive()) { 97 | while (buf.writableBytes() > 0) { 98 | buf.writeByte((byte) 42); 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | static final class Ex3 { 106 | public static void main(String[] args) throws Exception { 107 | ExecutorService executor = newFixedThreadPool(4); 108 | BufferAllocator allocator = BufferAllocator.onHeapUnpooled(); 109 | 110 | try (Buffer buf = allocator.allocate(4096)) { 111 | var futA = executor.submit(new Task(buf.writerOffset(1024).split().send())); 112 | var futB = executor.submit(new Task(buf.writerOffset(1024).split().send())); 113 | var futC = executor.submit(new Task(buf.writerOffset(1024).split().send())); 114 | var futD = executor.submit(new Task(buf.send())); 115 | futA.get(); 116 | futB.get(); 117 | futC.get(); 118 | futD.get(); 119 | } 120 | 121 | allocator.close(); 122 | executor.shutdown(); 123 | } 124 | 125 | private static class Task implements Runnable { 126 | private final Send send; 127 | 128 | Task(Send send) { 129 | this.send = send; 130 | } 131 | 132 | @Override 133 | public void run() { 134 | try (Buffer buf = send.receive().writerOffset(0)) { 135 | while (buf.writableBytes() > 0) { 136 | buf.writeByte((byte) 42); 137 | } 138 | } 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/echo/EchoClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples.echo; 17 | 18 | import io.netty5.bootstrap.Bootstrap; 19 | import io.netty5.channel.Channel; 20 | import io.netty5.channel.ChannelInitializer; 21 | import io.netty5.channel.ChannelOption; 22 | import io.netty5.channel.ChannelPipeline; 23 | import io.netty5.channel.EventLoopGroup; 24 | import io.netty5.channel.MultithreadEventLoopGroup; 25 | import io.netty5.channel.nio.NioHandler; 26 | import io.netty5.channel.socket.SocketChannel; 27 | import io.netty5.channel.socket.nio.NioSocketChannel; 28 | import io.netty5.handler.logging.LogLevel; 29 | import io.netty5.handler.logging.LoggingHandler; 30 | import io.netty5.handler.ssl.SslContext; 31 | import io.netty5.handler.ssl.SslContextBuilder; 32 | import io.netty5.handler.ssl.util.InsecureTrustManagerFactory; 33 | 34 | /** 35 | * Sends one message when a connection is open and echoes back any received 36 | * data to the server. Simply put, the echo client initiates the ping-pong 37 | * traffic between the echo client and server by sending the first message to 38 | * the server. 39 | */ 40 | public final class EchoClient { 41 | 42 | static final boolean SSL = System.getProperty("ssl") != null; 43 | static final String HOST = System.getProperty("host", "127.0.0.1"); 44 | static final int PORT = Integer.parseInt(System.getProperty("port", "8007")); 45 | static final int SIZE = Integer.parseInt(System.getProperty("size", "256")); 46 | 47 | public static void main(String[] args) throws Exception { 48 | // Configure SSL.git 49 | final SslContext sslCtx; 50 | if (SSL) { 51 | sslCtx = SslContextBuilder.forClient() 52 | .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); 53 | } else { 54 | sslCtx = null; 55 | } 56 | 57 | // Configure the client. 58 | EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory()); 59 | try { 60 | Bootstrap b = new Bootstrap(); 61 | b.group(group) 62 | .channel(NioSocketChannel.class) 63 | .option(ChannelOption.TCP_NODELAY, true) 64 | .handler(new ChannelInitializer() { 65 | @Override 66 | public void initChannel(SocketChannel ch) throws Exception { 67 | ChannelPipeline p = ch.pipeline(); 68 | if (sslCtx != null) { 69 | p.addLast(sslCtx.newHandler(ch.bufferAllocator(), HOST, PORT)); 70 | } 71 | p.addLast(new LoggingHandler(LogLevel.INFO)); 72 | p.addLast(new EchoClientHandler()); 73 | } 74 | }); 75 | 76 | // Start the client. 77 | Channel channel = b.connect(HOST, PORT).asStage().sync().getNow(); 78 | 79 | // Wait until the connection is closed. 80 | channel.closeFuture().asStage().sync(); 81 | } finally { 82 | // Shut down the event loop to terminate all threads. 83 | group.shutdownGracefully(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/echo/EchoClientHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples.echo; 17 | 18 | import io.netty5.buffer.Buffer; 19 | import io.netty5.buffer.BufferAllocator; 20 | import io.netty5.channel.ChannelHandler; 21 | import io.netty5.channel.ChannelHandlerContext; 22 | 23 | /** 24 | * Handler implementation for the echo client. It initiates the ping-pong 25 | * traffic between the echo client and server by sending the first message to 26 | * the server. 27 | */ 28 | public class EchoClientHandler implements ChannelHandler { 29 | 30 | private final Buffer firstMessage; 31 | 32 | /** 33 | * Creates a client-side handler. 34 | */ 35 | public EchoClientHandler() { 36 | firstMessage = BufferAllocator.onHeapUnpooled().allocate(EchoClient.SIZE); 37 | for (int i = 0; i < firstMessage.capacity(); i ++) { 38 | firstMessage.writeByte((byte) i); 39 | } 40 | } 41 | 42 | @Override 43 | public void channelActive(ChannelHandlerContext ctx) { 44 | ctx.writeAndFlush(firstMessage); 45 | } 46 | 47 | @Override 48 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 49 | ctx.write(msg); 50 | } 51 | 52 | @Override 53 | public void channelReadComplete(ChannelHandlerContext ctx) { 54 | ctx.flush(); 55 | } 56 | 57 | @Override 58 | public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 59 | // Close the connection when an exception is raised. 60 | cause.printStackTrace(); 61 | ctx.close(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/echo/EchoServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples.echo; 17 | 18 | import io.netty5.bootstrap.ServerBootstrap; 19 | import io.netty5.channel.Channel; 20 | import io.netty5.channel.ChannelInitializer; 21 | import io.netty5.channel.ChannelOption; 22 | import io.netty5.channel.ChannelPipeline; 23 | import io.netty5.channel.EventLoopGroup; 24 | import io.netty5.channel.MultithreadEventLoopGroup; 25 | import io.netty5.channel.nio.NioHandler; 26 | import io.netty5.channel.socket.SocketChannel; 27 | import io.netty5.channel.socket.nio.NioServerSocketChannel; 28 | import io.netty5.handler.logging.LogLevel; 29 | import io.netty5.handler.logging.LoggingHandler; 30 | import io.netty5.handler.ssl.SslContext; 31 | import io.netty5.handler.ssl.SslContextBuilder; 32 | import io.netty5.handler.ssl.util.SelfSignedCertificate; 33 | 34 | /** 35 | * Echoes back any received data from a client. 36 | */ 37 | public final class EchoServer { 38 | 39 | static final boolean SSL = System.getProperty("ssl") != null; 40 | static final int PORT = Integer.parseInt(System.getProperty("port", "8007")); 41 | 42 | public static void main(String[] args) throws Exception { 43 | // Configure SSL. 44 | final SslContext sslCtx; 45 | if (SSL) { 46 | SelfSignedCertificate ssc = new SelfSignedCertificate(); 47 | sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); 48 | } else { 49 | sslCtx = null; 50 | } 51 | 52 | // Configure the server. 53 | EventLoopGroup bossGroup = new MultithreadEventLoopGroup(1, NioHandler.newFactory()); 54 | EventLoopGroup workerGroup = new MultithreadEventLoopGroup(NioHandler.newFactory()); 55 | final EchoServerHandler serverHandler = new EchoServerHandler(); 56 | try { 57 | ServerBootstrap b = new ServerBootstrap(); 58 | b.group(bossGroup, workerGroup) 59 | .channel(NioServerSocketChannel.class) 60 | .option(ChannelOption.SO_BACKLOG, 100) 61 | .handler(new LoggingHandler(LogLevel.INFO)) 62 | .childHandler(new ChannelInitializer() { 63 | @Override 64 | public void initChannel(SocketChannel ch) throws Exception { 65 | ChannelPipeline p = ch.pipeline(); 66 | if (sslCtx != null) { 67 | p.addLast(sslCtx.newHandler(ch.bufferAllocator())); 68 | } 69 | p.addLast(new LoggingHandler(LogLevel.INFO)); 70 | p.addLast(serverHandler); 71 | } 72 | }); 73 | 74 | // Start the server. 75 | Channel channel = b.bind(PORT).asStage().sync().getNow(); 76 | 77 | // Wait until the server socket is closed. 78 | channel.closeFuture().asStage().sync(); 79 | } finally { 80 | // Shut down all event loops to terminate all threads. 81 | bossGroup.shutdownGracefully(); 82 | workerGroup.shutdownGracefully(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/echo/EchoServerHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples.echo; 17 | 18 | import io.netty5.buffer.Buffer; 19 | import io.netty5.channel.ChannelHandler; 20 | import io.netty5.channel.ChannelHandlerContext; 21 | 22 | /** 23 | * Handler implementation for the echo server. 24 | */ 25 | public class EchoServerHandler implements ChannelHandler { 26 | @Override 27 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 28 | Buffer buf = (Buffer) msg; 29 | ctx.write(buf); 30 | } 31 | 32 | @Override 33 | public void channelReadComplete(ChannelHandlerContext ctx) { 34 | ctx.flush(); 35 | } 36 | 37 | @Override 38 | public boolean isSharable() { 39 | return true; 40 | } 41 | 42 | @Override 43 | public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 44 | // Close the connection when an exception is raised. 45 | cause.printStackTrace(); 46 | ctx.close(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/http/snoop/HttpSnoopClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples.http.snoop; 17 | 18 | import io.netty5.bootstrap.Bootstrap; 19 | import io.netty5.buffer.BufferAllocator; 20 | import io.netty5.buffer.DefaultBufferAllocators; 21 | import io.netty5.channel.Channel; 22 | import io.netty5.channel.ChannelOption; 23 | import io.netty5.channel.EventLoopGroup; 24 | import io.netty5.channel.MultithreadEventLoopGroup; 25 | import io.netty5.channel.nio.NioHandler; 26 | import io.netty5.channel.socket.nio.NioSocketChannel; 27 | import io.netty5.handler.codec.http.DefaultFullHttpRequest; 28 | import io.netty5.handler.codec.http.HttpHeaderNames; 29 | import io.netty5.handler.codec.http.HttpHeaderValues; 30 | import io.netty5.handler.codec.http.HttpMethod; 31 | import io.netty5.handler.codec.http.HttpRequest; 32 | import io.netty5.handler.codec.http.HttpVersion; 33 | import io.netty5.handler.ssl.SslContext; 34 | import io.netty5.handler.ssl.SslContextBuilder; 35 | import io.netty5.handler.ssl.util.InsecureTrustManagerFactory; 36 | 37 | import java.net.URI; 38 | 39 | /** 40 | * A simple HTTP client that prints out the content of the HTTP response to 41 | * {@link System#out} to test {@link HttpSnoopServer}. 42 | */ 43 | public final class HttpSnoopClient { 44 | 45 | static final String URL = System.getProperty("url", "http://127.0.0.1:8080/"); 46 | 47 | public static void main(String[] args) throws Exception { 48 | URI uri = new URI(URL); 49 | String scheme = uri.getScheme() == null? "http" : uri.getScheme(); 50 | String host = uri.getHost() == null? "127.0.0.1" : uri.getHost(); 51 | int port = uri.getPort(); 52 | if (port == -1) { 53 | if ("http".equalsIgnoreCase(scheme)) { 54 | port = 80; 55 | } else if ("https".equalsIgnoreCase(scheme)) { 56 | port = 443; 57 | } 58 | } 59 | 60 | if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) { 61 | System.err.println("Only HTTP(S) is supported."); 62 | return; 63 | } 64 | 65 | BufferAllocator allocator = DefaultBufferAllocators.preferredAllocator(); 66 | 67 | // Configure SSL context if necessary. 68 | final boolean ssl = "https".equalsIgnoreCase(scheme); 69 | final SslContext sslCtx; 70 | if (ssl) { 71 | sslCtx = SslContextBuilder.forClient() 72 | .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); 73 | } else { 74 | sslCtx = null; 75 | } 76 | 77 | // Configure the client. 78 | EventLoopGroup group = new MultithreadEventLoopGroup(NioHandler.newFactory()); 79 | try { 80 | Bootstrap b = new Bootstrap(); 81 | b.group(group) 82 | .option(ChannelOption.BUFFER_ALLOCATOR, allocator) 83 | .channel(NioSocketChannel.class) 84 | .handler(new HttpSnoopClientInitializer(sslCtx, allocator)); 85 | 86 | // Make the connection attempt. 87 | Channel ch = b.connect(host, port).asStage().sync().getNow(); 88 | 89 | // Prepare the HTTP request. 90 | HttpRequest request = new DefaultFullHttpRequest( 91 | HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath(), allocator.allocate(0)); 92 | request.headers().set(HttpHeaderNames.HOST, host); 93 | request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); 94 | request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP); 95 | 96 | // Set some example cookies. 97 | request.headers().addCookie("my-cookie", "foo"); 98 | request.headers().addCookie("another-cookie", "bar"); 99 | 100 | // Send the HTTP request. 101 | ch.writeAndFlush(request); 102 | 103 | // Wait for the server to close the connection. 104 | ch.closeFuture().asStage().sync(); 105 | } finally { 106 | // Shut down executor threads to exit. 107 | group.shutdownGracefully(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/http/snoop/HttpSnoopClientHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples.http.snoop; 17 | 18 | import io.netty5.channel.ChannelHandlerContext; 19 | import io.netty5.channel.SimpleChannelInboundHandler; 20 | import io.netty5.handler.codec.http.HttpContent; 21 | import io.netty5.handler.codec.http.HttpObject; 22 | import io.netty5.handler.codec.http.HttpResponse; 23 | import io.netty5.handler.codec.http.HttpUtil; 24 | import io.netty5.handler.codec.http.LastHttpContent; 25 | 26 | import static java.nio.charset.StandardCharsets.UTF_8; 27 | 28 | public class HttpSnoopClientHandler extends SimpleChannelInboundHandler { 29 | 30 | @Override 31 | public void messageReceived(ChannelHandlerContext ctx, HttpObject msg) { 32 | if (msg instanceof HttpResponse response) { 33 | 34 | System.err.println("STATUS: " + response.status()); 35 | System.err.println("VERSION: " + response.protocolVersion()); 36 | System.err.println(); 37 | 38 | if (!response.headers().isEmpty()) { 39 | for (CharSequence name: response.headers().names()) { 40 | for (CharSequence value: response.headers().values(name)) { 41 | System.err.println("HEADER: " + name + " = " + value); 42 | } 43 | } 44 | System.err.println(); 45 | } 46 | 47 | if (HttpUtil.isTransferEncodingChunked(response)) { 48 | System.err.println("CHUNKED CONTENT {"); 49 | } else { 50 | System.err.println("CONTENT {"); 51 | } 52 | } 53 | if (msg instanceof HttpContent content) { 54 | 55 | System.err.print(content.payload().toString(UTF_8)); 56 | System.err.flush(); 57 | 58 | if (content instanceof LastHttpContent) { 59 | System.err.println("} END OF CONTENT"); 60 | ctx.close(); 61 | } 62 | } 63 | } 64 | 65 | @Override 66 | public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 67 | cause.printStackTrace(); 68 | ctx.close(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/http/snoop/HttpSnoopClientInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples.http.snoop; 17 | 18 | import io.netty5.buffer.BufferAllocator; 19 | import io.netty5.channel.ChannelInitializer; 20 | import io.netty5.channel.ChannelPipeline; 21 | import io.netty5.channel.socket.SocketChannel; 22 | import io.netty5.handler.codec.http.HttpClientCodec; 23 | import io.netty5.handler.codec.http.HttpContentDecompressor; 24 | import io.netty5.handler.ssl.SslContext; 25 | 26 | public class HttpSnoopClientInitializer extends ChannelInitializer { 27 | 28 | private final SslContext sslCtx; 29 | private final BufferAllocator allocator; 30 | 31 | public HttpSnoopClientInitializer(SslContext sslCtx, BufferAllocator allocator) { 32 | this.sslCtx = sslCtx; 33 | this.allocator = allocator; 34 | } 35 | 36 | @Override 37 | public void initChannel(SocketChannel ch) { 38 | ChannelPipeline p = ch.pipeline(); 39 | 40 | // Enable HTTPS if necessary. 41 | if (sslCtx != null) { 42 | // `initChannel` runs before the channel options have been applied, so we need to configure 43 | // the handler with an allocator that we get passed via the constructor. 44 | p.addLast(sslCtx.newHandler(allocator)); 45 | } 46 | 47 | p.addLast(new HttpClientCodec()); 48 | 49 | // Remove the following line if you don't want automatic content decompression. 50 | p.addLast(new HttpContentDecompressor()); 51 | 52 | // Uncomment the following line if you don't want to handle HttpContents. 53 | //p.addLast(new HttpObjectAggregator(1048576)); 54 | 55 | p.addLast(new HttpSnoopClientHandler()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/http/snoop/HttpSnoopServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples.http.snoop; 17 | 18 | import io.netty5.bootstrap.ServerBootstrap; 19 | import io.netty5.buffer.BufferAllocator; 20 | import io.netty5.buffer.DefaultBufferAllocators; 21 | import io.netty5.channel.Channel; 22 | import io.netty5.channel.ChannelOption; 23 | import io.netty5.channel.EventLoopGroup; 24 | import io.netty5.channel.MultithreadEventLoopGroup; 25 | import io.netty5.channel.nio.NioHandler; 26 | import io.netty5.channel.socket.nio.NioServerSocketChannel; 27 | import io.netty5.handler.logging.LogLevel; 28 | import io.netty5.handler.logging.LoggingHandler; 29 | import io.netty5.handler.ssl.SslContext; 30 | import io.netty5.handler.ssl.SslContextBuilder; 31 | import io.netty5.handler.ssl.util.SelfSignedCertificate; 32 | 33 | /** 34 | * An HTTP server that sends back the content of the received HTTP request 35 | * in a pretty plaintext form. 36 | */ 37 | public final class HttpSnoopServer { 38 | 39 | static final boolean SSL = System.getProperty("ssl") != null; 40 | static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080")); 41 | 42 | public static void main(String[] args) throws Exception { 43 | // Configure SSL. 44 | final SslContext sslCtx; 45 | if (SSL) { 46 | SelfSignedCertificate ssc = new SelfSignedCertificate(); 47 | sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); 48 | } else { 49 | sslCtx = null; 50 | } 51 | 52 | // Configure the server. 53 | EventLoopGroup bossGroup = new MultithreadEventLoopGroup(1, NioHandler.newFactory()); 54 | EventLoopGroup workerGroup = new MultithreadEventLoopGroup(NioHandler.newFactory()); 55 | try { 56 | BufferAllocator allocator = DefaultBufferAllocators.preferredAllocator(); 57 | ServerBootstrap b = new ServerBootstrap(); 58 | b.group(bossGroup, workerGroup) 59 | .channel(NioServerSocketChannel.class) 60 | .childOption(ChannelOption.BUFFER_ALLOCATOR, allocator) 61 | .handler(new LoggingHandler(LogLevel.INFO)) 62 | .childHandler(new HttpSnoopServerInitializer(sslCtx, allocator)); 63 | 64 | Channel ch = b.bind(PORT).asStage().sync().getNow(); 65 | 66 | System.err.println("Open your web browser and navigate to " + 67 | (SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/'); 68 | 69 | ch.closeFuture().asStage().sync(); 70 | } finally { 71 | bossGroup.shutdownGracefully(); 72 | workerGroup.shutdownGracefully(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/http/snoop/HttpSnoopServerHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples.http.snoop; 17 | 18 | import io.netty5.buffer.Buffer; 19 | import io.netty5.channel.ChannelFutureListeners; 20 | import io.netty5.channel.ChannelHandlerContext; 21 | import io.netty5.channel.SimpleChannelInboundHandler; 22 | import io.netty5.handler.codec.DecoderResult; 23 | import io.netty5.handler.codec.http.DefaultFullHttpResponse; 24 | import io.netty5.handler.codec.http.FullHttpResponse; 25 | import io.netty5.handler.codec.http.HttpContent; 26 | import io.netty5.handler.codec.http.HttpHeaderNames; 27 | import io.netty5.handler.codec.http.HttpHeaderValues; 28 | import io.netty5.handler.codec.http.HttpObject; 29 | import io.netty5.handler.codec.http.HttpRequest; 30 | import io.netty5.handler.codec.http.HttpUtil; 31 | import io.netty5.handler.codec.http.LastHttpContent; 32 | import io.netty5.handler.codec.http.QueryStringDecoder; 33 | import io.netty5.handler.codec.http.headers.HttpCookiePair; 34 | import io.netty5.handler.codec.http.headers.HttpHeaders; 35 | 36 | import java.util.Iterator; 37 | import java.util.List; 38 | import java.util.Map; 39 | import java.util.Map.Entry; 40 | 41 | import static io.netty5.handler.codec.http.HttpResponseStatus.BAD_REQUEST; 42 | import static io.netty5.handler.codec.http.HttpResponseStatus.CONTINUE; 43 | import static io.netty5.handler.codec.http.HttpResponseStatus.OK; 44 | import static io.netty5.handler.codec.http.HttpVersion.HTTP_1_1; 45 | import static java.nio.charset.StandardCharsets.UTF_8; 46 | 47 | public class HttpSnoopServerHandler extends SimpleChannelInboundHandler { 48 | 49 | private HttpRequest request; 50 | /** Buffer that stores the response content */ 51 | private final StringBuilder buf = new StringBuilder(); 52 | 53 | @Override 54 | public void channelReadComplete(ChannelHandlerContext ctx) { 55 | ctx.flush(); 56 | } 57 | 58 | @Override 59 | protected void messageReceived(ChannelHandlerContext ctx, Object msg) { 60 | if (msg instanceof HttpRequest) { 61 | HttpRequest request = this.request = (HttpRequest) msg; 62 | 63 | if (HttpUtil.is100ContinueExpected(request)) { 64 | send100Continue(ctx); 65 | } 66 | 67 | buf.setLength(0); 68 | buf.append("WELCOME TO THE WILD WILD WEB SERVER\r\n"); 69 | buf.append("===================================\r\n"); 70 | 71 | buf.append("VERSION: ").append(request.protocolVersion()).append("\r\n"); 72 | buf.append("HOSTNAME: ").append(request.headers().get(HttpHeaderNames.HOST, "unknown")).append("\r\n"); 73 | buf.append("REQUEST_URI: ").append(request.uri()).append("\r\n\r\n"); 74 | 75 | HttpHeaders headers = request.headers(); 76 | if (!headers.isEmpty()) { 77 | for (Entry h: headers) { 78 | CharSequence key = h.getKey(); 79 | CharSequence value = h.getValue(); 80 | buf.append("HEADER: ").append(key).append(" = ").append(value).append("\r\n"); 81 | } 82 | buf.append("\r\n"); 83 | } 84 | 85 | QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri()); 86 | Map> params = queryStringDecoder.parameters(); 87 | if (!params.isEmpty()) { 88 | for (Entry> p: params.entrySet()) { 89 | String key = p.getKey(); 90 | List vals = p.getValue(); 91 | for (String val : vals) { 92 | buf.append("PARAM: ").append(key).append(" = ").append(val).append("\r\n"); 93 | } 94 | } 95 | buf.append("\r\n"); 96 | } 97 | 98 | appendDecoderResult(buf, request); 99 | } 100 | 101 | if (msg instanceof HttpContent) { 102 | HttpContent httpContent = (HttpContent) msg; 103 | 104 | Buffer content = httpContent.payload(); 105 | if (content.readableBytes() > 0) { 106 | buf.append("CONTENT: "); 107 | buf.append(content.toString(UTF_8)); 108 | buf.append("\r\n"); 109 | appendDecoderResult(buf, request); 110 | } 111 | 112 | if (msg instanceof LastHttpContent) { 113 | buf.append("END OF CONTENT\r\n"); 114 | 115 | LastHttpContent trailer = (LastHttpContent) msg; 116 | if (!trailer.trailingHeaders().isEmpty()) { 117 | buf.append("\r\n"); 118 | for (CharSequence name: trailer.trailingHeaders().names()) { 119 | for (CharSequence value: trailer.trailingHeaders().values(name)) { 120 | buf.append("TRAILING HEADER: "); 121 | buf.append(name).append(" = ").append(value).append("\r\n"); 122 | } 123 | } 124 | buf.append("\r\n"); 125 | } 126 | 127 | if (!writeResponse(trailer, ctx)) { 128 | // If keep-alive is off, close the connection once the content is fully written. 129 | ctx.writeAndFlush(ctx.bufferAllocator().allocate(0)).addListener(ctx, ChannelFutureListeners.CLOSE); 130 | } 131 | } 132 | } 133 | } 134 | 135 | private static void appendDecoderResult(StringBuilder buf, HttpObject o) { 136 | DecoderResult result = o.decoderResult(); 137 | if (result.isSuccess()) { 138 | return; 139 | } 140 | 141 | buf.append(".. WITH DECODER FAILURE: "); 142 | buf.append(result.cause()); 143 | buf.append("\r\n"); 144 | } 145 | 146 | private boolean writeResponse(HttpObject currentObj, ChannelHandlerContext ctx) { 147 | // Decide whether to close the connection or not. 148 | boolean keepAlive = HttpUtil.isKeepAlive(request); 149 | // Build the response object. 150 | FullHttpResponse response = new DefaultFullHttpResponse( 151 | HTTP_1_1, currentObj.decoderResult().isSuccess()? OK : BAD_REQUEST, 152 | ctx.bufferAllocator().copyOf(buf.toString().getBytes(UTF_8))); 153 | 154 | response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8"); 155 | 156 | if (keepAlive) { 157 | // Add 'Content-Length' header only for a keep-alive connection. 158 | response.headers().set(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(response.payload().readableBytes())); 159 | // Add keep alive header as per: 160 | // - https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection 161 | response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); 162 | } 163 | 164 | // Encode the cookie. 165 | Iterator cookies = request.headers().getCookies().iterator(); 166 | if (cookies.hasNext()) { 167 | // Reset the cookies if necessary. 168 | do { 169 | HttpCookiePair cookie = cookies.next(); 170 | response.headers().addSetCookie(cookie.name(), cookie.value()); 171 | } while (cookies.hasNext()); 172 | } else { 173 | // Browser sent no cookie. Add some. 174 | response.headers().addSetCookie("key1", "value1"); 175 | response.headers().addSetCookie("key2", "value2"); 176 | } 177 | 178 | // Write the response. 179 | ctx.write(response); 180 | 181 | return keepAlive; 182 | } 183 | 184 | private static void send100Continue(ChannelHandlerContext ctx) { 185 | FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE, ctx.bufferAllocator().allocate(0)); 186 | ctx.write(response); 187 | } 188 | 189 | @Override 190 | public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 191 | cause.printStackTrace(); 192 | ctx.close(); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/test/java/io/netty5/buffer/tests/examples/http/snoop/HttpSnoopServerInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty5.buffer.tests.examples.http.snoop; 17 | 18 | import io.netty5.buffer.BufferAllocator; 19 | import io.netty5.channel.ChannelInitializer; 20 | import io.netty5.channel.ChannelPipeline; 21 | import io.netty5.channel.socket.SocketChannel; 22 | import io.netty5.handler.codec.http.HttpRequestDecoder; 23 | import io.netty5.handler.codec.http.HttpResponseEncoder; 24 | import io.netty5.handler.ssl.SslContext; 25 | 26 | public class HttpSnoopServerInitializer extends ChannelInitializer { 27 | 28 | private final SslContext sslCtx; 29 | private final BufferAllocator allocator; 30 | 31 | public HttpSnoopServerInitializer(SslContext sslCtx, BufferAllocator allocator) { 32 | this.sslCtx = sslCtx; 33 | this.allocator = allocator; 34 | } 35 | 36 | @Override 37 | public void initChannel(SocketChannel ch) { 38 | ChannelPipeline p = ch.pipeline(); 39 | if (sslCtx != null) { 40 | p.addLast(sslCtx.newHandler(allocator)); 41 | } 42 | p.addLast(new HttpRequestDecoder()); 43 | // Uncomment the following line if you don't want to handle HttpChunks. 44 | //p.addLast(new HttpObjectAggregator(1048576)); 45 | p.addLast(new HttpResponseEncoder()); 46 | // Remove the following line if you don't want automatic content compression. 47 | //p.addLast(new HttpContentCompressor()); 48 | p.addLast(new HttpSnoopServerHandler()); 49 | } 50 | } 51 | --------------------------------------------------------------------------------