Skip to content
Snippets Groups Projects
Commit 744796fb authored by lizhigang's avatar lizhigang
Browse files

Add slice copy support for socket file type.


Splice can be used to speed up file copy operations
by avoiding moving any memory between kernel/user space.
But splice copy can not support from socket file to
regular/socket file directly. So use pipe file as proxy,
socket/regular --> pipe --> socket/regular. Even it needs
2 times splice syscall, it is still much faster
than user space copy.

Please refer to the comparison data below for the time
consumption of executing FileUtils.copy in
FileUtilsTest:testCopy_SocketToFile_FileToSocket

file-->socket(unit ms):
32KB: user_cp:0.604, splice_cp:0.113, reduce 80%
32MB: user_cp:236, splice_cp:68, reduce 71%

socket-->file(unit ms):
32KB: user_cp:0.611, splice_cp:0.240, reduce 61%
32MB: user_cp:284, splice_cp:88, reduce 69%

The network environment for this test is localhost,
and the data in real scenarios depends more
on the actual network environment.

Test: atest FrameworksCoreTests:android.os.FileUtilsTest

Change-Id: I0636647934a889ce95bdf4df0db428cb370148fd
Signed-off-by: default avatarlizhigang <lizhigang.1220@bytedance.com>
parent c9f4111c
No related branches found
No related tags found
No related merge requests found
......@@ -25,6 +25,7 @@ import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
import static android.system.OsConstants.EINVAL;
import static android.system.OsConstants.ENOSYS;
import static android.system.OsConstants.F_OK;
import static android.system.OsConstants.EIO;
import static android.system.OsConstants.O_ACCMODE;
import static android.system.OsConstants.O_APPEND;
import static android.system.OsConstants.O_CREAT;
......@@ -37,6 +38,7 @@ import static android.system.OsConstants.SPLICE_F_MORE;
import static android.system.OsConstants.SPLICE_F_MOVE;
import static android.system.OsConstants.S_ISFIFO;
import static android.system.OsConstants.S_ISREG;
import static android.system.OsConstants.S_ISSOCK;
import static android.system.OsConstants.W_OK;
import android.annotation.NonNull;
......@@ -459,6 +461,8 @@ public final class FileUtils {
}
} else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
return copyInternalSplice(in, out, count, signal, executor, listener);
} else if (S_ISSOCK(st_in.st_mode) || S_ISSOCK(st_out.st_mode)) {
return copyInternalSpliceSocket(in, out, count, signal, executor, listener);
}
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
......@@ -509,6 +513,86 @@ public final class FileUtils {
}
return progress;
}
/**
* Requires one of input or output to be a socket file.
*
* @hide
*/
@VisibleForTesting
public static long copyInternalSpliceSocket(FileDescriptor in, FileDescriptor out, long count,
CancellationSignal signal, Executor executor, ProgressListener listener)
throws ErrnoException {
long progress = 0;
long checkpoint = 0;
long countToRead = count;
long countInPipe = 0;
long t;
FileDescriptor[] pipes = Os.pipe();
while (countToRead > 0 || countInPipe > 0) {
if (countToRead > 0) {
t = Os.splice(in, null, pipes[1], null, Math.min(countToRead, COPY_CHECKPOINT_BYTES),
SPLICE_F_MOVE | SPLICE_F_MORE);
if (t < 0) {
// splice error
Slog.e(TAG, "splice error, fdIn --> pipe, copy size:" + count +
", copied:" + progress +
", read:" + (count - countToRead) +
", in pipe:" + countInPipe);
break;
} else if (t == 0) {
// end of input, input count larger than real size
Slog.w(TAG, "Reached the end of the input file. The size to be copied exceeds the actual size, copy size:" + count +
", copied:" + progress +
", read:" + (count - countToRead) +
", in pipe:" + countInPipe);
countToRead = 0;
} else {
countInPipe += t;
countToRead -= t;
}
}
if (countInPipe > 0) {
t = Os.splice(pipes[0], null, out, null, Math.min(countInPipe, COPY_CHECKPOINT_BYTES),
SPLICE_F_MOVE | SPLICE_F_MORE);
// The data is already in the pipeline, so the return value will not be zero.
// If it is 0, it means an error has occurred. So here use t<=0.
if (t <= 0) {
Slog.e(TAG, "splice error, pipe --> fdOut, copy size:" + count +
", copied:" + progress +
", read:" + (count - countToRead) +
", in pipe: " + countInPipe);
throw new ErrnoException("splice, pipe --> fdOut", EIO);
} else {
progress += t;
checkpoint += t;
countInPipe -= t;
}
}
if (checkpoint >= COPY_CHECKPOINT_BYTES) {
if (signal != null) {
signal.throwIfCanceled();
}
if (executor != null && listener != null) {
final long progressSnapshot = progress;
executor.execute(() -> {
listener.onProgress(progressSnapshot);
});
}
checkpoint = 0;
}
}
if (executor != null && listener != null) {
final long progressSnapshot = progress;
executor.execute(() -> {
listener.onProgress(progressSnapshot);
});
}
return progress;
}
/**
* Requires both input and output to be a regular file.
......
......@@ -29,6 +29,7 @@ import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.F_OK;
import static android.system.OsConstants.O_APPEND;
import static android.system.OsConstants.O_CREAT;
......@@ -37,6 +38,7 @@ import static android.system.OsConstants.O_RDWR;
import static android.system.OsConstants.O_TRUNC;
import static android.system.OsConstants.O_WRONLY;
import static android.system.OsConstants.R_OK;
import static android.system.OsConstants.SOCK_STREAM;
import static android.system.OsConstants.W_OK;
import static android.system.OsConstants.X_OK;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
......@@ -54,6 +56,7 @@ import static org.junit.Assert.fail;
import android.content.Context;
import android.os.FileUtils.MemoryPipe;
import android.provider.DocumentsContract.Document;
import android.system.Os;
import android.util.DataUnit;
import androidx.test.InstrumentationRegistry;
......@@ -70,6 +73,8 @@ import org.junit.runner.RunWith;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
......@@ -77,6 +82,7 @@ import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.net.InetSocketAddress;
@RunWith(AndroidJUnit4.class)
public class FileUtilsTest {
......@@ -249,6 +255,84 @@ public class FileUtilsTest {
assertArrayEquals(expected, actual);
}
//TODO(ravenwood) Remove the _$noRavenwood suffix and add @RavenwoodIgnore instead
@Test
public void testCopy_SocketToFile_FileToSocket$noRavenwood() throws Exception {
for (int size : DATA_SIZES ) {
final File src = new File(mTarget, "src");
final File dest = new File(mTarget, "dest");
byte[] expected = new byte[size];
byte[] actual = new byte[size];
new Random().nextBytes(expected);
// write test data in to src file
writeFile(src, expected);
// start server, get data from client and save to dest file (socket --> file)
FileDescriptor srvSocketFd = Os.socket(AF_INET, SOCK_STREAM, 0);
Os.bind(srvSocketFd, new InetSocketAddress("localhost", 0));
Os.listen(srvSocketFd, 5);
InetSocketAddress localSocketAddress = (InetSocketAddress) Os.getsockname(srvSocketFd);
final Thread srv = new Thread(new Runnable() {
public void run() {
try {
InetSocketAddress peerAddress = new InetSocketAddress();
FileDescriptor srvConnFd = Os.accept(srvSocketFd, peerAddress);
// read file size
byte[] rcvFileSizeByteArray = new byte[8];
Os.read(srvConnFd, rcvFileSizeByteArray, 0, rcvFileSizeByteArray.length);
long rcvFileSize = 0;
for (int i = 0; i < 8; i++) {
rcvFileSize <<= 8;
rcvFileSize |= (rcvFileSizeByteArray[i] & 0xFF);
}
FileOutputStream fileOutputStream = new FileOutputStream(dest);
// copy data from socket to file
FileUtils.copy(srvConnFd, fileOutputStream.getFD(), rcvFileSize, null, null, null);
fileOutputStream.close();
Os.close(srvConnFd);
Os.close(srvSocketFd);
} catch (Exception e) {
e.printStackTrace();
}
}
});
srv.start();
// start client, get data from dest file and send to server (file --> socket)
FileDescriptor clientFd = Os.socket(AF_INET, SOCK_STREAM, 0);
Os.connect(clientFd, localSocketAddress.getAddress(), localSocketAddress.getPort());
FileInputStream fileInputStream = new FileInputStream(src);
long sndFileSize = src.length();
// send the file size to server
byte[] sndFileSizeByteArray = new byte[8];
for (int i = 7; i >= 0; i--) {
sndFileSizeByteArray[i] = (byte)(sndFileSize & 0xFF);
sndFileSize >>= 8;
}
Os.write(clientFd, sndFileSizeByteArray, 0, sndFileSizeByteArray.length);
// copy data from file to socket
FileUtils.copy(fileInputStream.getFD(), clientFd, src.length(), null, null, null);
fileInputStream.close();
Os.close(clientFd);
srv.join();
// read test data from dest file
actual = readFile(dest);
assertArrayEquals(expected, actual);
}
}
@Test
public void testIsFilenameSafe() throws Exception {
assertTrue(FileUtils.isFilenameSafe(new File("foobar")));
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment