1
1
mirror of https://github.com/MarginaliaSearch/MarginaliaSearch.git synced 2025-10-05 21:22:39 +02:00

(native) Add fallbacks and configuration options for building on systems lacking liburing

This commit is contained in:
Viktor Lofgren
2025-08-14 23:36:13 +02:00
parent aee262e5f6
commit e3b957063d
9 changed files with 135 additions and 54 deletions

View File

@@ -3,18 +3,20 @@
CXXFLAGS=-O3 -march=native -std=c++14 -fPIC `pkg-config --cflags liburing`
LDFLAGS=
# Weird hack to get liburing to link on one particular debian server
LIBURING_PATH=`pkg-config liburing --keep-system-libs --libs-only-L | cut -c 3- | tr -d \ `/liburing.so
# Weird hack to get liburing to link in prod
# don't @ me
LIBURING_PATH=`./findliburing.sh`
CXX=c++
HEADERS=src/config.h
SOURCES=src/sort.cc src/unix.cc src/uring.cc
all: resources/libcpp.so
resources/libcpp.so: ${SOURCES} resources/liburing.so
resources/libcpp.so: ${SOURCES} ${HEADERS} resources/liburing.so
${CXX} -shared ${CXXFLAGS} ${SOURCES} resources/liburing.so -o resources/libcpp.so
resources/liburing.so:
resources/liburing.so: findliburing.sh
cp ${LIBURING_PATH} resources/liburing.so
clean:
rm -rf resources/{libcpp,liburing}.so

View File

@@ -23,7 +23,7 @@ apply from: "$rootProject.projectDir/srcsets.gradle"
// with a shellscript as gradle's c++ tasks are kind of insufferable
tasks.register('compileCpp', Exec) {
inputs.files('Makefile', 'src/sort.cc', 'src/unix.cc', 'src/uring.cc')
inputs.files('Makefile', 'src/sort.cc', 'src/unix.cc', 'src/uring.cc', 'src/config.h')
outputs.files('resources/libcpp.so', 'resources/liburing.so')
commandLine 'make', 'all'

View File

@@ -0,0 +1,10 @@
#!/bin/bash
# Compatibility shim with the linux deployment used in production which *will* not link to liburing using
# any sort of sane compiler or pkg-config flags
set -e
URING_PATH=$(pkg-config liburing --keep-system-libs --libs-only-L | cut -c 3- | tr -d \ )
if [ ! -z "${URING_PATH}" ]; then
echo ${URING_PATH}/liburing.so
fi

View File

@@ -21,12 +21,16 @@ import static java.lang.foreign.ValueLayout.*;
* isAvailable will be false. This flag must be checked before calling
* any of the native functions.
* */
@SuppressWarnings("preview")
public class IoUring {
private static boolean useIoUring = !Boolean.getBoolean("system.disableIoUring");
public final MethodHandle uringInit;
public final MethodHandle uringClose;
private final MethodHandle uringReadBuffered;
private final MethodHandle uringReadDirect;
private final MethodHandle uringReadSubstitute;
public final MethodHandle uringReadAndPoll;
public final MethodHandle uringJustPoll;
@@ -40,39 +44,67 @@ public class IoUring {
private IoUring(Path libFile) {
SymbolLookup libraryLookup = SymbolLookup.libraryLookup(libFile, Arena.global());
var nativeLinker = Linker.nativeLinker();
MemorySegment handle = libraryLookup.findOrThrow("uring_read_buffered");
uringReadBuffered = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(JAVA_INT, JAVA_INT, ADDRESS, JAVA_INT, ADDRESS, ADDRESS, ADDRESS));
MemorySegment handle;
handle = libraryLookup.findOrThrow("uring_read_direct");
uringReadDirect = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(JAVA_INT, JAVA_INT, ADDRESS, JAVA_INT, ADDRESS, ADDRESS, ADDRESS));
useIoUring = useIoUring && libraryLookup.find("initialize_uring").isPresent();
if (useIoUring) {
System.err.println("io_uring enabled");
}
else {
System.err.println("io_uring disabled");
}
handle = libraryLookup.findOrThrow("uring_read_submit_and_poll");
uringReadAndPoll = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(
JAVA_INT,
ADDRESS, // io_uring* ring
ADDRESS, // long* result_ids
JAVA_INT, // int in_flight_requests
JAVA_INT, // int read_count
ADDRESS, // long* read_batch_ids
ADDRESS, // int* read_fds
ADDRESS, // void** read_buffers
ADDRESS, // unsigned int** read_sizes
ADDRESS // long* read_offsets
));
handle = libraryLookup.findOrThrow("uring_poll");
if (useIoUring) {
uringJustPoll = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(
JAVA_INT,
ADDRESS, // io_uring* ring
ADDRESS // long* result_ids
));
handle = libraryLookup.findOrThrow("uring_read_buffered");
uringReadBuffered = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(JAVA_INT, JAVA_INT, ADDRESS, JAVA_INT, ADDRESS, ADDRESS, ADDRESS));
handle = libraryLookup.findOrThrow("initialize_uring");
uringInit = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(ADDRESS, JAVA_INT));
handle = libraryLookup.findOrThrow("uring_read_direct");
uringReadDirect = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(JAVA_INT, JAVA_INT, ADDRESS, JAVA_INT, ADDRESS, ADDRESS, ADDRESS));
handle = libraryLookup.findOrThrow("close_uring");
uringClose = nativeLinker.downcallHandle(handle, FunctionDescriptor.ofVoid(ADDRESS));
handle = libraryLookup.findOrThrow("uring_read_submit_and_poll");
uringReadAndPoll = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(
JAVA_INT,
ADDRESS, // io_uring* ring
ADDRESS, // long* result_ids
JAVA_INT, // int in_flight_requests
JAVA_INT, // int read_count
ADDRESS, // long* read_batch_ids
ADDRESS, // int* read_fds
ADDRESS, // void** read_buffers
ADDRESS, // unsigned int** read_sizes
ADDRESS // long* read_offsets
));
handle = libraryLookup.findOrThrow("uring_poll");
uringJustPoll = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(
JAVA_INT,
ADDRESS, // io_uring* ring
ADDRESS // long* result_ids
));
handle = libraryLookup.findOrThrow("initialize_uring");
uringInit = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(ADDRESS, JAVA_INT));
handle = libraryLookup.findOrThrow("close_uring");
uringClose = nativeLinker.downcallHandle(handle, FunctionDescriptor.ofVoid(ADDRESS));
handle = libraryLookup.findOrThrow("substitute_uring_read");
uringReadSubstitute = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT, ADDRESS, ADDRESS, ADDRESS));
}
else {
uringInit = null;
uringClose = null;
uringJustPoll = null;
uringReadAndPoll = null;
uringReadDirect = null;
uringReadBuffered = null;
handle = libraryLookup.findOrThrow("substitute_uring_read");
uringReadSubstitute = nativeLinker.downcallHandle(handle, FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT, ADDRESS, ADDRESS, ADDRESS));
}
}
static {
@@ -119,20 +151,25 @@ public class IoUring {
}
public static UringQueue uringOpen(int fd, int queueSize) {
try {
return new UringQueue((MemorySegment) instance.uringInit.invoke(queueSize), fd);
if (useIoUring) {
try {
return new UringQueue((MemorySegment) instance.uringInit.invoke(queueSize), fd);
} catch (Throwable t) {
throw new RuntimeException("Failed to invoke native function", t);
}
}
catch (Throwable t) {
throw new RuntimeException("Failed to invoke native function", t);
else {
return new UringQueue(null, fd);
}
}
public static void uringClose(UringQueue ring) {
try {
instance.uringClose.invoke(ring.pointer());
}
catch (Throwable t) {
throw new RuntimeException("Failed to invoke native function", t);
if (useIoUring) {
try {
instance.uringClose.invoke(ring.pointer());
} catch (Throwable t) {
throw new RuntimeException("Failed to invoke native function", t);
}
}
}
@@ -140,11 +177,7 @@ public class IoUring {
if (offsets.isEmpty()) {
throw new IllegalArgumentException("Empty offset list in uringRead");
}
if (offsets.size() == 1) {
if (LinuxSystemCalls.readAt(fd, dest.getFirst(), offsets.getFirst()) > 0)
return 1;
else return -1;
}
try {
MemorySegment bufferList = Arena.ofAuto().allocate(8L * offsets.size(), 8);
MemorySegment sizeList = Arena.ofAuto().allocate(4L * offsets.size(), 8);
@@ -160,11 +193,19 @@ public class IoUring {
sizeList.setAtIndex(JAVA_INT, i, (int) buffer.byteSize());
offsetList.setAtIndex(JAVA_LONG, i, offsets.get(i));
}
if (direct) {
return (Integer) instance.uringReadDirect.invoke(fd, ring.pointer(), dest.size(), bufferList, sizeList, offsetList);
if (useIoUring
&& offsets.size() > 5) // fall back to sequential pread operations if the list is too small
{
if (direct) {
return (Integer) instance.uringReadDirect.invoke(fd, ring.pointer(), dest.size(), bufferList, sizeList, offsetList);
} else {
return (Integer) instance.uringReadBuffered.invoke(fd, ring.pointer(), dest.size(), bufferList, sizeList, offsetList);
}
}
else {
return (Integer) instance.uringReadBuffered.invoke(fd, ring.pointer(), dest.size(), bufferList, sizeList, offsetList);
// do a sequential pread operation as a fallback
return (Integer) instance.uringReadSubstitute.invoke(fd, dest.size(), bufferList, sizeList, offsetList);
}
}
catch (Throwable t) {

View File

@@ -18,7 +18,6 @@ import static java.lang.foreign.ValueLayout.*;
* isAvailable will be false. This flag must be checked before calling
* any of the native functions.
* */
@SuppressWarnings("preview")
public class LinuxSystemCalls {
private final MethodHandle openDirect;
private final MethodHandle openBuffered;

View File

@@ -24,7 +24,6 @@ import static java.lang.foreign.ValueLayout.JAVA_LONG;
* isAvailable will be false. This flag must be checked before calling
* any of the native functions.
* */
@SuppressWarnings("preview")
public class NativeAlgos {
private final MethodHandle qsortHandle;
private final MethodHandle qsort128Handle;

View File

@@ -0,0 +1,4 @@
// Add a line like this to disable io_uring and fall back to sequential pread operations
// for e.g. development on non-Linux OS:es
// #define NO_IO_URING

View File

@@ -1,11 +1,18 @@
#include "config.h"
#include <algorithm>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#ifndef NO_IO_URING
#include <liburing.h>
#endif
#include <string.h>
extern "C" {
#ifndef NO_IO_URING
io_uring* initialize_uring(int queue_size) {
io_uring* ring = (io_uring*) malloc(sizeof(io_uring));
if (!ring) return NULL;
@@ -231,4 +238,23 @@ int uring_read_direct(int fd, io_uring* ring, int n, void** buffers, unsigned in
return n;
}
#endif
int substitute_uring_read(int fd, int n, void** buffers, unsigned int* sizes, long* offsets) {
for (int i = 0; i < n; i++) {
int rv = pread(fd, buffers[i], sizes[i], offsets[i]);
if (rv == -1) {
perror("pread");
return -1;
}
if (rv != sizes[i]) {
fprintf(stderr, "Unexpected number of bytes read");
return -1;
}
}
return n;
}
}

View File

@@ -27,13 +27,13 @@ class UringQueueTest {
}
@Test
public void testSunnyDay() throws IOException {
public void testSunnyDay() {
int fd = LinuxSystemCalls.openBuffered(file);
List<MemorySegment> segments = new ArrayList<>();
List<Long> offsets = new ArrayList<>();
for (int i = 0; i < 4; i++) {
for (int i = 0; i < 8; i++) {
segments.add(Arena.ofAuto().allocate(32));
offsets.add(32L*i);
}