View Javadoc
1   /*
2    * Copyright 2024 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.netty.channel.uring;
17  
18  import io.netty.channel.DefaultFileRegion;
19  import io.netty.channel.FileRegion;
20  import io.netty.channel.unix.FileDescriptor;
21  import io.netty.util.internal.logging.InternalLogger;
22  import io.netty.util.internal.logging.InternalLoggerFactory;
23  
24  import java.io.IOException;
25  import java.nio.channels.WritableByteChannel;
26  
27  final class IoUringFileRegion implements FileRegion {
28      private static final InternalLogger logger = InternalLoggerFactory.getInstance(IoUringFileRegion.class);
29  
30      private static final short SPLICE_TO_PIPE = 1;
31      private static final short SPLICE_TO_SOCKET = 2;
32  
33      final DefaultFileRegion fileRegion;
34      private FileDescriptor[] pipe;
35      private int transferred;
36      private int pipeLen = -1;
37  
38      IoUringFileRegion(DefaultFileRegion fileRegion) {
39          this.fileRegion = fileRegion;
40      }
41  
42      void open() throws IOException {
43          fileRegion.open();
44          if (pipe == null) {
45              pipe = FileDescriptor.pipe();
46          }
47      }
48  
49      IoUringIoOps splice(int fd) {
50          if (pipeLen == -1) {
51              return spliceToPipe();
52          }
53          return spliceToSocket(fd, pipeLen);
54      }
55  
56      IoUringIoOps spliceToPipe() {
57          int fileRegionFd = Native.getFd(fileRegion);
58          int len = (int) (count() - transferred());
59          int offset =  (int) (position() + transferred());
60          return IoUringIoOps.newSplice(
61                  fileRegionFd, offset,
62                  pipe[1].intValue(), -1L,
63                  len, Native.SPLICE_F_MOVE, SPLICE_TO_PIPE);
64      }
65  
66      private IoUringIoOps spliceToSocket(int socket, int len) {
67          return IoUringIoOps.newSplice(
68                  pipe[0].intValue(), -1L,
69                  socket, -1L,
70                  len, Native.SPLICE_F_MOVE, SPLICE_TO_SOCKET);
71      }
72  
73      /**
74       * Handle splice result
75       *
76       * @param result    the result
77       * @param data      the data that was submitted as part of the SPLICE.
78       * @return          the number of bytes that should be considered to be transferred.
79       */
80      int handleResult(int result, short data) {
81          assert result >= 0;
82          if (data == SPLICE_TO_PIPE) {
83              // This is the result for spliceToPipe
84              transferred += result;
85              pipeLen = result;
86              return 0;
87          }
88          if (data == SPLICE_TO_SOCKET) {
89              // This is the result for spliceToSocket
90              pipeLen -= result;
91              assert pipeLen >= 0;
92              if (pipeLen == 0) {
93                  if (transferred() >= count()) {
94                      // We transferred the whole file
95                      return -1;
96                  }
97                  pipeLen = -1;
98              }
99              return result;
100         }
101         throw new IllegalArgumentException("Unknown data: " + data);
102     }
103 
104     @Override
105     public long position() {
106         return fileRegion.position();
107     }
108 
109     @Override
110     public long transfered() {
111         return transferred;
112     }
113 
114     @Override
115     public long transferred() {
116         return transferred;
117     }
118 
119     @Override
120     public long count() {
121         return fileRegion.count();
122     }
123 
124     @Override
125     public long transferTo(WritableByteChannel target, long position) {
126         throw new UnsupportedOperationException();
127     }
128 
129     @Override
130     public FileRegion retain() {
131         fileRegion.retain();
132         return this;
133     }
134 
135     @Override
136     public FileRegion retain(int increment) {
137         fileRegion.retain(increment);
138         return this;
139     }
140 
141     @Override
142     public FileRegion touch() {
143         fileRegion.touch();
144         return this;
145     }
146 
147     @Override
148     public FileRegion touch(Object hint) {
149         fileRegion.touch(hint);
150         return this;
151     }
152 
153     @Override
154     public int refCnt() {
155         return fileRegion.refCnt();
156     }
157 
158     @Override
159     public boolean release() {
160         if (fileRegion.release()) {
161             closePipeIfNeeded();
162             return true;
163         }
164         return false;
165     }
166 
167     @Override
168     public boolean release(int decrement) {
169         if (fileRegion.release(decrement)) {
170             closePipeIfNeeded();
171             return true;
172         }
173         return false;
174     }
175 
176     private void closePipeIfNeeded() {
177         if (pipe != null) {
178             closeSilently(pipe[0]);
179             closeSilently(pipe[1]);
180         }
181     }
182 
183     private static void closeSilently(FileDescriptor fd) {
184         try {
185             fd.close();
186         } catch (IOException e) {
187             logger.debug("Error while closing a pipe", e);
188         }
189     }
190 }