Git Product home page Git Product logo

panamauring's Introduction

Panama uring Java

这是一个探索性质的项目,使用Java的新ffi为Java引入io_uring。

主要目的在于提供一个与基础read/write原语签名类似的 Linux异步文件I/O API,以补齐虚拟线程读写文件会导致pin住载体线程的短板。

这个项目并非与netty的io_uring一样会从系统调用开始处理,而是直接修改liburing 源码,在此基础上封装调用,即它最底层只是一层对liburing的封装。

maven坐标为

 <dependency>
    <groupId>io.github.dreamlike-ocean</groupId>
    <artifactId>panama-uring-linux-x86_64</artifactId>
    <version>3.0.2</version>
</dependency>

目前阶段参考价值大于实用价值,在jdk21之后我会做进一步的API适配,(垃圾java毁我人生,这东西jdk21稳定不了),Panama API仍在变动之中

由于依赖了liburing的代码所以需要一并clone liburing仓库

git clone --recurse-submodules https://github.com/dreamlike-ocean/PanamaUring.git

支持的特性

注意:若无提及则均为one shot模式

AsyncFile

  • 异步读取
  • 异步写入
  • IOSQE_BUFFER_SELECT模式的异步读取
  • 异步fsync
  • 通过flock和定时任务实现的文件锁

AsyncSocket

  • 异步connect (ipv4,ipv6)
  • IOSQE_BUFFER_SELECT模式的异步recv
  • 异步write
  • mutlishot异步recv

AsyncServerSocket

  • 异步accept
  • multishot的异步accept

IO_Uring

  • 任意数量的IOSQE_IO_LINK
  • 异步版本的splice api以及基于此的异步版本sendfile

其余的异步fd

  • 异步的文件监听 inotify
  • 异步的eventfd读写
  • 异步的pipefd
  • 基于Epoll的Async socket read,Async socket write,Async socket connect,Async socket accept

其他Native封装

  • 完整的Epoll绑定
  • 完整的eventFd绑定
  • 完整的unistd绑定

其余小玩意

  • 基于EventFd将IO Uring与Epoll连接在一起,socket api走Epoll驱动,收割cqe也由Epoll驱动,等价于Epoll监听IO Uring
  • Panama FFI的声明式运行时绑定 点我看文档

构建/运行指南

初衷就是最小依赖,所以只依赖于jctools和slfj4,前者用于EventLoop的task queue,后者则是日志门面。jctools可以替换为juc的BlockingQueue的任意实现。

下面本人使用的构建工具以及运行环境:

注意 jdk版本不能升级也不能降级,Panama api可能不一致

  • Maven 3.8.4
  • OpenJDK 21
  • Linux >= 5.10
  • makefile GNU Make 4.3

构建非常简单

mvn clean package -DskipTests

设计细节

线程模型

由于io_uring的双环特性,其实推荐单线程获取sqe,然后同一线程再submit。

目前获取io_uring实例直接使用默认参数获取,所以使用的是io_uring_get_sqe这个函数获取sqe,这个方法会导致从环上摘下一个sqe,而且封装的EventLoop带有一个定期submit的功能,所以要求io_uring_get_sqe和sqe参数填充这多个操作必须是原子的

进而需要把这几个操作打包为一个操作切换到EventLoop上面执行

举个例子

public CompletableFuture<byte[]> readSelected(int offset, int length) {
    CompletableFuture<byte[]> future = new CompletableFuture<>();
    eventLoop.runOnEventLoop(() -> {
        if (!uring.prep_selected_read(fd, offset, length, future)) {
            future.completeExceptionally(new Exception("没有空闲的sqe"));
        }
    });
    return future;
}

wakeup

当EventLoop阻塞在wait cqe时,我们想要在任意线程里面唤醒它,最容易想到的就是使用 IORING_OP_NOP这个OP, 但是这个也存在我们之前说的并发问题。

所以抄了一把jdk的selector实现,我会让io_uring监听一个eventfd,需要唤醒时我们只要增加一个eventfd的计数即可

注意这里的eventfd只是一个普通的fd,并非使用io_uring_register_eventfd这个方法来监听cqe完成事件的

wakeup的实现核心就这些。

然后我们来谈谈细节:

由于只支持one shot模式的监听,即submit一次sqe只会产生一次可读事件

所以需要读取到事件之后 再注册一次监听

private void multiShotReadEventfd() {
    prep_read(wakeUpFd.getFd(), 0, wakeUpReadBuffer, (__) -> {
        // 轮询到了直接再注册一个可读事件
        wakeUpFd.read(wakeUpReadBuffer);
        multiShotReadEventfd();
    });
    submit();
}

IOSQE_IO_LINK

这里要保证两点

  • 原子化——EventLoop机制保证
  • 保证范围内的fd都属于同一个io_uring实现

比如说 asyncFile1和asyncFile2都必须是同一个io_uring

eventLoop.submitLinkedOpSafe(() -> {
            asyncFile1.read();
            asyncFile2.write();
        });

还要支持捕获任意数量的AsyncFile/AsyncSocket/AsyncServerSocket

这里使用了一个针对于lambda实现的小技巧

private boolean checkCaptureContainAsyncFd(Object ops) {
    try {
        for (Field field : ops.getClass().getDeclaredFields()) {
            if (!AsyncFd.class.isAssignableFrom(field.getType())) {
                continue;
            }
            field.setAccessible(true);
            IOUringEventLoop eventLoop = ((AsyncFd) field.get(ops)).fetchEventLoop();
            if (eventLoop != this) {
                return false;
            }
        }
    } catch (Throwable t) {
        throw new AssertionError("should not reach here", t);
    }
        return true;
        }

Epoll

为什么这里面会有Epoll?

因为最早的实现是,网络IO走epoll不变,文件IO走io_uring,epoll监听io_uring cqe完成事件的eventfd,当epoll轮询到这个eventfd时再去收割cqe。

但是目前我完整实现了这个EventLoop请看EpollUringEventLoop ,同时提供了基于Epoll的socket基础操作

panamauring's People

Contributors

dreamlike-ocean avatar qrwells avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

panamauring's Issues

io_uring异常唤醒

bug:
可能是eventfd的实现有误 导致无write这个fd也会wakeup eventloop

jdk22适配-PanamaUring适配

PanamaUring过去依赖于jextract生成绑定,数目巨大且噪音很大
PanamaGenertor可以帮助改善这个场景

过去的PanamaUring设计有些粗糙,需要重新思考各类关系后构建新的架构

invokedynamic惰性初始化对应的native call生成

对于每一个的生成不再使用static final的形式完成,这样会影响类初始化时间,所以使用惰性的方案,将初始化延迟到每一个函数调用前,摊平整体的生成时间,这里使用ConstantCallSite直接折叠调用。
这种也可以减少了代码里面基于threadlocal奇技淫巧的使用。

     public static void main(String[] args) throws Throwable {
        ByteBuddy buddy = new ByteBuddy();
        InvokeDynamic invokeDynamic = InvokeDynamic.bootstrap(
                ClassfileApiDemo.class.getMethod("bootstrap", MethodHandles.Lookup.class,String.class, MethodType.class, Object[].class)
        ).withMethodArguments();
        DynamicType.Unloaded<Object> unloaded = buddy.subclass(Object.class)
                .defineField("targetString", String.class, Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL)
                .implement(IntFunction.class)
                .method(named("apply"))
                .intercept(invokeDynamic)
                .invokable(MethodDescription::isTypeInitializer)
                .intercept(FieldAccessor.ofField("targetString").setsValue("dreamlike"))
                .make();
        unloaded.saveIn(new File("invokeDynamic"));
        Class<?> loaded = unloaded
                .load(ClassfileApiDemo.class.getClassLoader()).getLoaded();

        IntFunction<String> intFunction = (IntFunction<String>) loaded.newInstance();
        System.out.println(intFunction.apply(123));
    }


    public static CallSite bootstrap(MethodHandles.Lookup lookup, String methodName, MethodType methodType, Object... args) throws NoSuchMethodException, IllegalAccessException, NoSuchFieldException {
        MethodHandle intToString = MethodHandles.lookup().findStatic(String.class, "valueOf", MethodType.methodType(String.class, int.class));
        MethodHandle concatMh = MethodHandles.lookup().findVirtual(String.class, "concat", MethodType.methodType(String.class, String.class));
        Class<?> aClass = lookup.lookupClass();
        String targetString = (String) aClass.getField("targetString").get(null);
        intToString = MethodHandles.filterReturnValue(
                intToString,
                MethodHandles.insertArguments(concatMh, 1, targetString)
                );
        intToString = intToString.asType(methodType);
        return new ConstantCallSite(intToString);
    }

AsyncServerSocket multi shot accept接口实现问题

目前只提供Flow类型的接口,正确性没有被验证,原型阶段的单纯Comsumer入参过于原始,需要调用方自己处理cancel操作
综合以上问题未来提供更好的接口,且要作为基于epoll实现的async socket的标准api

使用SmallRye Mutiny改造全部的异步接口 and jdk21适配 and 应用瘦身

当前项目中存在one shot和multi shot两种模式的异步效应
前者的异步接口为completefuture和conumer,非常不统一且存在大量的接口形成时间远早于cancel接口,因此api方面风格不够统一
对于multi shot模式则是使用自定义的reactive flow实现,虽然是jdk接口但是实现的不够通用且不够健壮
综上,我更倾向于使用成熟的异步库来帮助实现更统一的接口风格
参考 https://smallrye.io/smallrye-mutiny/2.3.1/

C abi ffi生成器适配

  1. 通过java bean的字段进行struct生成,通过字节码工程继承getter和setter方法来处理
  2. 生成对应的ffi绑定,通过interface签名来生成

multi shot补齐

补齐multi shot功能
提供accept和recv的multi shot

  • multi shot accept
  • multi shot recv

更少的拷贝

1,提供一组不安全但是包含少量安全抽象的api来减少目前使用的从堆外拷贝到堆内byte[]操作 提高整体的性能
2,实现socket fd的zero copy这个op

native call 返回指针时错误取值问题

if (returnPointer) {
            //先调整返回值类型对应的memorySegment长度
            methodHandle = MethodHandles.filterReturnValue(
                    methodHandle,
                    MethodHandles.insertArguments(
                            NativeGeneratorHelper.REINTERPRET_MH,
                            1,
                            returnLayout.byteSize()
                    )
            );

            //绑定当前的StructProxyGenerator
            //绑定的结果是 (MemorySegment) -> ${returnType}
            MethodHandle returnEnhance = StructProxyGenerator.ENHANCE_MH
                    .asType(StructProxyGenerator.ENHANCE_MH.type().changeReturnType(method.getReturnType()))
                    .bindTo(structProxyGenerator)
                    .bindTo(method.getReturnType());
            //来把MemorySegment返回值转换为java bean
            methodHandle = MethodHandles.filterReturnValue(
                    methodHandle,
                    returnEnhance
            );
        }

NativeGeneratorHelper.REINTERPRET_MH, 不应该直接REINTERPRET而是读出来地址值转化为MemorySegment 之后再REINTERPRET最后作为底层MemorySegment 绑定到proxy上

run example failed

The. so file reports an error and cannot run on my host. Can you provide a c or cpp file? Let me compile it manually.

Connected to the target VM, address: '127.0.0.1:8381', transport: 'socket'
Exception in thread "main" java.lang.UnsatisfiedLinkError: C:\Users\23630\AppData\Local\Temp\liburing10936049237759482352.so: %1 不是有效的 Win32 应用程序。
at java.base/jdk.internal.loader.NativeLibraries.load(Native Method)
at java.base/jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open(NativeLibraries.java:331)
at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:197)
at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:139)
at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2404)
at java.base/java.lang.Runtime.load0(Runtime.java:785)
at java.base/java.lang.System.load(System.java:2011)
at top.dreamlike.async.uring.IOUring.(IOUring.java:493)
at top.dreamlike.async.uring.IOUringEventLoop.(IOUringEventLoop.java:39)
at iouring.FileOp.main(FileOp.java:17)
Disconnected from the target VM, address: '127.0.0.1:8381', transport: 'socket'

ring_message支持

io_uring_prep_msg_ring
环间消息(Ring messages)
环间消息是一种在两个 io_uring 之间传递消息的方法。可以用来唤醒某个正在阻塞等待的 io_uring,或者单纯地在两个 io_uring 之间发送数据。

环间消息的最简单的玩法是在两个环之间传输 8 字节的数据。io_uring 不会修改或者关心数据内容,你可以传递任何 8 字节的东西,例如一个指针。一个使用场景是网络后端应用,它们需要将接收到的连接(connections)分发到各个线程中,或者是将耗时的工作卸载到另一个线程。io_uring_prep_msg_ring() 可以用来初始化环间消息的 SQE。从 Linux 5.18 起可用。

Hello, I have also worked on a similar thing

I found your repo searching GitHub for io_uring API calls

It seems we are interested in similar things -- I also have started experimenting with io_uring bindings using Panama
I looked at your code, maybe my code also is helpful for you? 🙂

    @Test
    void testReadAndPrintFile() throws Throwable {
        MemorySegment ring = session.allocate(io_uring.$LAYOUT());
        int ret = liburing.io_uring_queue_init(8, ring, 0);
        if (ret < 0) {
            System.out.printf("queue_init: %d", ret);
            return;
        }

        ret = IoUringExample.submit_read_request("src/test/resources/test.txt", ring.address());
        if (ret < 0) {
            System.out.printf("submit_read_request: %d", ret);
            return;
        }

        IoUringExample.get_completion_and_print(ring.address());
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.