{"id":13743441,"url":"https://github.com/dreamlike-ocean/PanamaUring","last_synced_at":"2025-05-09T01:30:53.827Z","repository":{"id":65279497,"uuid":"544087332","full_name":"dreamlike-ocean/PanamaUring","owner":"dreamlike-ocean","description":"使用panama api为java提供io_uring的绑定而无需使用jni绑定，同时统一文件IO和网络IO的模型，提供一套易用的异步IO API","archived":false,"fork":false,"pushed_at":"2025-04-22T13:55:51.000Z","size":1794,"stargazers_count":112,"open_issues_count":0,"forks_count":14,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-22T14:51:26.164Z","etag":null,"topics":["asynchronous","asyncio","ffi","io-uring","java","liburing","panama"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dreamlike-ocean.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-10-01T16:01:05.000Z","updated_at":"2025-04-22T13:55:54.000Z","dependencies_parsed_at":"2023-02-12T02:02:04.974Z","dependency_job_id":"5f066bc5-23e9-4fea-8109-a452d2d3a503","html_url":"https://github.com/dreamlike-ocean/PanamaUring","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dreamlike-ocean%2FPanamaUring","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dreamlike-ocean%2FPanamaUring/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dreamlike-ocean%2FPanamaUring/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dreamlike-ocean%2FPanamaUring/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dreamlike-ocean","download_url":"https://codeload.github.com/dreamlike-ocean/PanamaUring/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253174230,"owners_count":21865836,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous","asyncio","ffi","io-uring","java","liburing","panama"],"created_at":"2024-08-03T05:00:46.932Z","updated_at":"2025-05-09T01:30:53.803Z","avatar_url":"https://github.com/dreamlike-ocean.png","language":"Java","funding_links":[],"categories":["Libraries","网络编程"],"sub_categories":["Java"],"readme":"# Panama uring Java\n[中文版本](README.md) | [English Version](README_EN.md)\n\n这是一个探索性质的项目，使用Java的[新ffi机制](https://openjdk.org/jeps/424)为Java引入io_uring。\n\n主要目的在于提供一个与基础read/write原语签名类似的 **Linux异步文件I/O** API，以补齐虚拟线程读写文件会导致pin住载体线程的短板，同时也提供了异步socket api。\n\n本项目来自于[liburing](https://github.com/axboe/liburing)的启发，使用Java重新实现liburing并进行高阶封装\n\nmaven坐标为\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.dreamlike-ocean\u003c/groupId\u003e\n    \u003cartifactId\u003epanama-uring\u003c/artifactId\u003e\n    \u003cversion\u003e${lastest}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n```shell\ngit clone https://github.com/dreamlike-ocean/PanamaUring.git\n```\n\n测试运行 由于依赖了[ClassFile api](https://openjdk.org/jeps/484)所以需要使用jdk24\n\n```shell\nmvn clean test -am -pl panama-uring\n```\n\n### 支持的特性\n\n**注意**：若无提及则均为one shot模式\n\n#### AsyncFile\n\n- [x] 异步读取\n- [x] 异步写入\n- [x] IOSQE_BUFFER_SELECT模式的异步读取\n- [x] 异步fsync\n\n#### AsyncSocket\n\n- [x] 异步connect （ipv4，ipv6，uds）\n- [x] IOSQE_BUFFER_SELECT模式的异步recv\n- [x] 异步write/send\n- [x] mutlishot异步recv\n- [x] zc tx支持\n\n#### AsyncServerSocket\n\n- [x] 异步accept\n- [x] multishot的异步accept\n\n#### IO_Uring\n\n- [x] 任意数量的IOSQE_IO_LINK\n- [x] 异步版本的splice api以及基于此的异步版本sendfile\n- [x] 内存安全的cancel实现\n\n#### 其余的异步操作\n\n- [x] 异步的文件监听 inotify\n- [x] 异步的eventfd读写\n- [x] 异步的pipefd\n- [x] 异步的Poll操作\n- [x] 异步的madvise操作\n\n#### 其他Native封装\n\n- [x] 完整的Epoll绑定\n- [x] 完整的eventFd绑定\n- [x] 完整的unistd绑定\n- [x] mmap绑定\n\n#### 其余小玩意\n\n- [x] Panama FFI的声明式运行时绑定 [点我看文档](./panama-generator/README.md)\n- [x] 对于kotlin coroutine的支持，支持取消情况下的内存安全\n- [x] kotlin coroutine情况下支持socket先进行poll后进行recv操作\n\n### 构建/运行指南\n\n下面本人使用的构建工具以及运行环境：\n\n- Maven 3.8.4\n- OpenJDK 24\n- Linux \u003e= 5.10 越新越好\n\n构建非常简单\n\n```shell\nmvn clean package -DskipTests\n```\n\n### 设计细节\n\n#### 所有权\n\n我在代码中引入了OwnershipResource这样一个类表示某个api会“拿走”这个资源的所有权，然后在异步操作之后“归还”给调用者，设计思路来自于[monoio的实践](https://github.com/bytedance/monoio/blob/master/docs/zh/why-async-rent.md)\n\n以异步read为例子,调用这个api时会把调用者的Buffer“拿走”，在io_uring内部获取到cqe之后再交还给用户\n\n在大部分情况下，如果io_uring的cqe告诉我们出现错误（res为负）那么CancelableFuture这个future仍是success的但是BufferResult中result为负，仍旧会把这个buffer的所有权“归还”给用户\n\n``` java\n    default CancelableFuture\u003cBufferResult\u003cOwnershipMemory\u003e\u003e asyncRead(OwnershipMemory buffer, int len, int offset) {\n        return (CancelableFuture\u003cBufferResult\u003cOwnershipMemory\u003e\u003e) owner()\n                .asyncOperation(sqe -\u003e Instance.LIB_URING.io_uring_prep_read(sqe, readFd(), MemorySegment.ofAddress(buffer.resource().address()), len, offset))\n                .thenApply(cqe -\u003e new BufferResult\u003c\u003e(buffer, cqe.getRes()))\n                .whenComplete(buffer::DropWhenException);\n    }\n```\n\n#### 取消\n\nBuffer的所有权只能在CancelableFuture完成后才能被“归还”给用户，如果想要做一个超时取消机制，那么就会遇到一些内存安全问题，问题来自于io_uring的取消机制是尽力取消，你的取消和异步操作本质上是并发的\n\n试想这样一个场景，内核正在写入buffer但是你推送了一个取消操作就认为取消成功了，没有查看取消结果，那么就出现了数据读取丢失的问题，甚至你直接把这块buffer复用了就又叠加一个竞态问题\n\n所以正常的取消应该\n\n``` java\n    var cancelableReadFuture = eventFd.asyncRead(memory, (int) ValueLayout.JAVA_LONG.byteSize(), 0);\n    eventLoop.submitScheduleTask(1, TimeUnit.SECONDS, () -\u003e {\n        cancelableReadFuture.ioUringCancel(true)\n                .thenAccept(count -\u003e Assert.assertEquals(1L, (long) count));\n    });\n    Integer res = cancelableReadFuture.get().syscallRes();\n    if(Libc.Error_H.ECANCELED == -res) {\n       //处理资源销毁等\n    } else {\n       //处理取消失败 事件已经完成的情况\n    }\n```\n\n#### 线程模型\n\n由于io_uring无锁sq和cq特性，所以在处理sqe填充和提交时最好是一个原子操作，所以这里按照常规的IO设计，对应的sqe操作会调度到对应的EventLoop上进行操作\n\n举个例子\n\n```java\n    public CancelToken asyncOperation(Consumer\u003cIoUringSqe\u003e sqeFunction, Consumer\u003cIoUringCqe\u003e repeatableCallback, boolean neeSubmit) {\n    Runnable r = fillSqeFunction;\n    if (inEventLoop()) {\n        runWithCatchException(r);\n    } else {\n        execute(r);\n    }\n    return cancelToken;\n}\n```\n\n#### wakeup\n\n当EventLoop阻塞在wait cqe时，我们想要在任意线程里面唤醒它，最容易想到的就是使用 `IORING_OP_NOP`这个OP， 但是这个也存在我们之前说的并发问题。\n\n所以抄了一把jdk的selector实现，我会让io_uring监听一个eventfd，需要唤醒时我们只要增加一个eventfd的计数即可\n\n\u003e 注意这里的eventfd只是一个普通的fd，并非使用`io_uring_register_eventfd`这个方法来监听cqe完成事件的\n\nwakeup的实现核心就这些。\n\n然后我们来谈谈细节：\n\n由于只支持one shot模式的监听，即submit一次sqe只会产生一次可读事件\n\n所以需要读取到事件之后 再注册一次监听，类似于一个“异步递归”\n\n```java\nprivate void multiShotReadEventfd() {\n    prep_read(wakeUpFd.getFd(), 0, wakeUpReadBuffer, (__) -\u003e {\n        // 轮询到了直接再注册一个可读事件\n        wakeUpFd.read(wakeUpReadBuffer);\n        multiShotReadEventfd();\n    });\n    submit();\n}\n```\n\n#### IOSQE_IO_LINK\n\n这里要保证两点\n\n- 原子化——EventLoop机制保证\n- 保证范围内的fd都属于同一个io_uring实现\n\n\n```java\n    public void testLinked() throws Exception {\n    IoUringEventLoop eventLoop = new IoUringEventLoop(params -\u003e {\n        params.setSq_entries(4);\n        params.setFlags(0);\n    });\n    ArrayBlockingQueue\u003cInteger\u003e queue = new ArrayBlockingQueue\u003c\u003e(3);\n    try (eventLoop) {\n        IoUringNoOp ioUringNoOp = new IoUringNoOp(eventLoop);\n        eventLoop.start();\n        eventLoop.linkedScope(() -\u003e {\n            var tmp = ioUringNoOp;\n            AtomicReference\u003cIoUringSqe\u003e t = new AtomicReference\u003c\u003e();\n            eventLoop.asyncOperation(sqe -\u003e {\n                Instance.LIB_URING.io_uring_prep_nop(sqe);\n                t.set(sqe);\n            }).thenAccept(cqe -\u003e queue.add(cqe.getRes()));\n            Assert.assertTrue(t.get().isLinked());\n\n            eventLoop.asyncOperation(sqe -\u003e {\n                Instance.LIB_URING.io_uring_prep_nop(sqe);\n                t.set(sqe);\n            }).thenAccept(cqe -\u003e queue.add(cqe.getRes()));\n            Assert.assertTrue(t.get().isLinked());\n        }, () -\u003e {\n            AtomicReference\u003cIoUringSqe\u003e t = new AtomicReference\u003c\u003e();\n            eventLoop.asyncOperation(sqe -\u003e {\n                Instance.LIB_URING.io_uring_prep_nop(sqe);\n                t.set(sqe);\n            }).thenAccept(cqe -\u003e queue.add(cqe.getRes()));\n            Assert.assertFalse(t.get().isLinked());\n        });\n    }\n    eventLoop.join();\n    Assert.assertEquals(3, queue.size());\n    for (Integer i : queue) {\n        Assert.assertEquals(Integer.valueOf(0), i);\n    }\n}\n```\n\n还要支持捕获任意数量的IoUringOperator实现类\n\n这里使用了一个针对于lambda实现的小技巧 当然你也可以通过top.dreamlike.panama.uring.skipSameEventLoopCheck这个Property关掉检查（默认关闭）\n\n```java\n    public static boolean inSameEventLoop(IoUringEventLoop eventLoop, Object o) {\n    if (isSkipSameEventLoopCheck) {\n        return true;\n    }\n\n    Class\u003c?\u003e aClass = o.getClass();\n    for (Field field : aClass.getDeclaredFields()) {\n        if (!IoUringOperator.class.isAssignableFrom(field.getType())) {\n            continue;\n        }\n        field.setAccessible(true);\n        var loop = ((IoUringOperator) LambdaHelper.runWithThrowable(() -\u003e field.get(o))).owner();\n        if (loop != eventLoop) {\n            return false;\n        }\n    }\n    return true;\n}\n```\n\n#### 虚拟线程支持\n\n当前做了一个特殊的实现 `VTIoUringEventLoop` 其使用虚拟线程作为EventLoop实现，原理如下：\n\n首先java虚拟线程底层有一组read poller来不断poll事件然后恢复对应线程执行，那么我们实际上就可以把一些fd直接挂到这个poller上，请它来poll一些fd,已知iouring发布cqe时支持自动向某个eventfd发送消息（io_uring_register_eventfd） 那么我就可以把这个eventfd挂到jdk的poller上 然后开启一个虚拟线程跑对应的EventLoop，还可以复用对应的ForkJoin线程池来处理cqe回调和各种操作，直接去“借用”JDK内部的虚拟线程池化算力\n\n具体的Poll实现可以参考 `sun.nio.ch.Poller::poll` 是一个内部的静态方法，我通过一些hack的手段强行打开的\n\n#### Netty支持\n\n当前也做了对Netty Epoll和io_uring transport的支持，可以将PanamaUring的EventLoop挂在netty的EventLoop上\n\n其核心原理与虚拟线程的支持一致，都为evenfd + poller，而netty的poller实现是通过借用Netty的4.2的ioHandle与ioHandler模型实现的","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdreamlike-ocean%2FPanamaUring","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdreamlike-ocean%2FPanamaUring","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdreamlike-ocean%2FPanamaUring/lists"}