mirror of
https://gitee.com/fantix/kloop.git
synced 2024-09-27 06:30:00 +00:00
Compare commits
32 commits
Author | SHA1 | Date | |
---|---|---|---|
|
f055323af3 | ||
|
bfc3fb451a | ||
|
479d8be584 | ||
|
d659f7d533 | ||
|
652efd90a4 | ||
|
3c2d02a3f9 | ||
|
761f741d5d | ||
|
74f0062154 | ||
|
a1094281ec | ||
|
081a2877b8 | ||
|
665647013b | ||
|
47636fbf04 | ||
|
db84313627 | ||
|
08d3d8ff9a | ||
|
a8f639fdce | ||
|
19803cd298 | ||
|
9bdf0d1834 | ||
|
a935db4858 | ||
|
eca322da58 | ||
|
6ca4161a46 | ||
|
85799a1624 | ||
|
9cf03082c8 | ||
|
91691e5996 | ||
|
f3c43bf145 | ||
|
dd4a38a610 | ||
|
3b4b6c5c67 | ||
|
64b105657a | ||
|
bb7c378c95 | ||
|
67c2c9aa2a | ||
|
9fc44a51c8 | ||
|
97f02d40bc | ||
|
04b59cc52e |
53 changed files with 5016 additions and 34 deletions
32
.github/workflows/build.yml
vendored
Normal file
32
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
name: 构建
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: 获取源代码
|
||||
uses: actions/checkout@v3
|
||||
- name: 安装 Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: 安装依赖关系
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install build
|
||||
- name: 构建软件包
|
||||
run: python -m build
|
||||
- name: 上传源码包
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kLoop 源码包
|
||||
path: dist/*.tar.gz
|
||||
- name: 上传 wheel 安装包
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: kLoop wheel 安装包
|
||||
path: dist/*.whl
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -6,4 +6,5 @@
|
|||
/tests/**/__pycache__/
|
||||
/src/**/*.c
|
||||
/src/**/*.so
|
||||
/src/kloop.egg-info/
|
||||
/src/kLoop.egg-info/
|
||||
/resolver/target/
|
||||
|
|
4
LICENSE
4
LICENSE
|
@ -1,5 +1,5 @@
|
|||
版权所有 (c) 2022 王川 http://fantix.pro
|
||||
Copyright (c) 2022 Fantix King http://fantix.pro
|
||||
版权所有 (c) 2022 王川 https://fantix.pro
|
||||
Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
|
||||
|
||||
木兰宽松许可证
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
recursive-include src *.pyx *.pxd
|
||||
include README*.md
|
||||
include Makefile
|
||||
recursive-include docs *.png *.md
|
||||
recursive-include src *.pyx *.pxd *.h
|
||||
include resolver/Cargo.toml
|
||||
include resolver/Cargo.lock
|
||||
recursive-include resolver *.rs
|
||||
graft tests
|
||||
global-exclude *.py[cod] *.c
|
||||
|
||||
|
|
23
Makefile
Normal file
23
Makefile
Normal file
|
@ -0,0 +1,23 @@
|
|||
.PHONY: dist dev build clean
|
||||
.DEFAULT_GOAL := dev
|
||||
|
||||
|
||||
# Make distribution tarballs
|
||||
dist:
|
||||
pip install -U build
|
||||
python -m build
|
||||
|
||||
|
||||
# Incrementally build for development
|
||||
dev:
|
||||
KLOOP_DEBUG=1 python setup.py develop
|
||||
|
||||
|
||||
# Always build for development
|
||||
build:
|
||||
KLOOP_FORCE=1 KLOOP_DEBUG=1 python setup.py develop
|
||||
|
||||
|
||||
# Clean up everything including the Rust build cache
|
||||
clean:
|
||||
python setup.py clean --all
|
76
README.md
76
README.md
|
@ -1,5 +1,73 @@
|
|||
# kLoop
|
||||
# <img src="docs/kloop@2x.png" height="96px" align="left"> kLoop - *asyncio on Linux kernel*
|
||||
|
||||
kLoop is an implementation of the Python asyncio event loop written in Cython,
|
||||
using io_uring and kTLS features of the Linux kernel, open-sourced and released
|
||||
under the MulanPSL - 2.0 license.
|
||||
[![中文](https://img.shields.io/badge/Zh-中文-informational?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAFKADAAQAAAABAAAAEAAAAABHXVY9AAABc0lEQVQ4EaWSOy8EURTHd+wDEY94JVtgg9BI1B6dQqHiE1CrRasT30DpC2hVQimiFkJWVsSj2U1EsmQH4/ff3CO3WDuDk/zmf8/jnntm7qRSMRZFUQ4WYSimNFmaRlsgq8F83K6WuALyva4mixbc+kfJcGqa7CqU4AjaocNpG5oHsx7qB3EqQRC8K4g/gazAMbFTBdbgL1Zh0w2EbnMVHdMrd4LZNotZmIZJKMAemC2z0MS6oDlYhzOQ6c3yGR5Fec4OGPvEHCmn3np+kfyT51+QH8afcbFLTfjgFVS9tZrpwC4v1k9M39w3NTQrBxSM4127SAmNoBt0Ma3QyHRwGUIYdQUh0+c0wZsLPKKH8AwvoHgNlmABZLtwBdqnP0DD9IEG2If6N0oz5SbYSfW4PYhvgNmUxU1JZGEEAsUyjPmB7lhBA1Xe7NMWpuzXa39fnC7lN1b/mZttSNLQv9XXZs2US9LwzjU5R+/d+n/CBx9I2uELeXrRajeDqHwAAAAASUVORK5CYII=)](README.zh.md)
|
||||
[![build](https://img.shields.io/github/workflow/status/fantix/kloop/构建?label=build&logo=github)](https://github.com/fantix/kloop/actions/workflows/build.yml)
|
||||
[![downloads](https://img.shields.io/pypi/dm/kloop?logo=pypi&logoColor=white)](https://pypi.python.org/pypi/gino)
|
||||
[![code quality](https://img.shields.io/codacy/grade/f2e97d6eb2554e87b3cd15aae8f6b1e0?logo=codacy)](https://app.codacy.com/gh/fantix/kloop/dashboard)
|
||||
[![license](https://img.shields.io/badge/license-MulanPSL--2.0-success?logo=opensourceinitiative&logoColor=white)](http://license.coscl.org.cn/MulanPSL2/)
|
||||
|
||||
kLoop is an implementation of the Python
|
||||
[asyncio](https://docs.python.org/3/library/asyncio.html) event loop written
|
||||
in [Cython](https://cython.org/), using
|
||||
[io_uring](https://unixism.net/loti/what_is_io_uring.html) and
|
||||
[kTLS](https://www.kernel.org/doc/html/latest/networking/tls-offload.html)
|
||||
features of the Linux kernel, therefore called k(ernel)Loop.
|
||||
|
||||
kLoop is open-sourced and released under the
|
||||
[MulanPSL - 2.0 license](http://license.coscl.org.cn/MulanPSL2).
|
||||
|
||||
**⚠️WARNING: THIS PROJECT IS IN PROOF-OF-CONCEPT STAGE!⚠️**
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* **Minimum syscalls** - all I/O calls are done in the kernel thanks to
|
||||
io_uring, and the only remaining syscall to `io_uring_enter()` is also
|
||||
optimized to be only called when necessary using the `SQPOLL` feature. That
|
||||
means most of the overhead of syscalls is gone;
|
||||
* **No GIL in the main-loop** - the hot-path is written in Cython without GIL,
|
||||
that means it's compiled into pure C code without Python structures, saving
|
||||
some memory and execution time. On the other hand, GIL is only taken before
|
||||
Python callbacks, so it's slightly more friendly to multithreading, which is
|
||||
still not recommended though.
|
||||
* **TLS offloading** - all symmetric-key encryption and decryption work is
|
||||
offloaded to the NIC if supported, or to the thread pool of io_uring
|
||||
otherwise. This allows us to free up CPU for more I/O, or leverage all the
|
||||
CPU cores even if the application is running single-threaded.
|
||||
* **Asynchronous DNS resolving** - we blended in the Rust
|
||||
[trust-dns](https://github.com/bluejekyll/trust-dns/) library with a custom
|
||||
I/O runtime bridging to io_uring in C (including reading the
|
||||
`/etc/resolv.conf` and `/etc/hosts` files), providing flexible APIs to manage
|
||||
the concurrency, cache and configuration in Python.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
* Python >= 3.10
|
||||
* Linux >= 5.11 (enable kTLS with `modprobe tls`)
|
||||
* OpenSSL >= 3.0 (kTLS receive offloading on TLS 1.3 requires the latest
|
||||
development version)
|
||||
|
||||
Development and testing is done on Ubuntu 22.04.
|
||||
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
<img src="docs/architecture.en.png" width="620px" alt="architecture.png">
|
||||
|
||||
Looks like the Lucky Charms factory, says @aaronbrighton ...
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
### Ubuntu 22.04
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install gcc libssl-dev python3-dev python3.10-venv
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
python3 -m venv path/to/env
|
||||
source path/to/env/bin/activate
|
||||
pip install cython
|
||||
KLOOP_DEBUG=1 python setup.py develop # or just run `make`
|
||||
```
|
||||
|
|
67
README.zh.md
Normal file
67
README.zh.md
Normal file
|
@ -0,0 +1,67 @@
|
|||
# <img src="docs/kloop@2x.png" height="80px" align="left"> kLoop:*Linux 内核上的 asyncio*
|
||||
|
||||
[![English](https://img.shields.io/badge/英文-English-informational?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAQCAYAAADnEwSWAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAG6ADAAQAAAABAAAAEAAAAACiF0fSAAABJUlEQVQ4EWP8//8/MwMDAysQEw0YGRl/EK0YWSHQsgogJgX8RNZPCpuJFMWUqqWrZSw4XBsCFL+FQ+4/DnEUYWC8gNKCEDB+X8MlgIKVWCJMD64ADwOorwmIHyNhdyDbFYgPAfFXIAaBV0CcCTIG5DNsLo0GKnDEYc8+oGsvQ+UEgLQMkrpYIDsSiJGjRxTInwY07wmuYCxDMgCdmQUUgFmGLheNLoDEzwG5gBFJgFQmNr3ZQEPsgfgImmEquHy2BajwHZpiGPcmjAGk0aPgCDCIp4HkgcHWA6RsQGwoEMEVZ9VATZdgqkig7yKp/YjEBjORIxJdjhw+3tIFVzCGAoPBEo9ta4E+f4NHHqsULstqsKpGCJ4BMkm2jNrBiHAOFhZdLQMA8pKhkQYZiokAAAAASUVORK5CYII=)](README.md)
|
||||
[![构建](https://img.shields.io/github/workflow/status/fantix/kloop/构建?label=构建&logo=github)](https://github.com/fantix/kloop/actions/workflows/build.yml)
|
||||
[![下载](https://img.shields.io/pypi/dm/kloop?logo=pypi&logoColor=white&label=下载)](https://pypi.python.org/pypi/gino)
|
||||
[![质量](https://img.shields.io/codacy/grade/f2e97d6eb2554e87b3cd15aae8f6b1e0?logo=codacy&label=质量)](https://app.codacy.com/gh/fantix/kloop/dashboard)
|
||||
[![许可](https://img.shields.io/badge/许可-木兰PSLv2-success?logo=opensourceinitiative&logoColor=white)](http://license.coscl.org.cn/MulanPSL2/)
|
||||
|
||||
kLoop 是一个 Python
|
||||
[asyncio](https://docs.python.org/3/library/asyncio.html)
|
||||
事件循环的实现,主要用 [Cython](https://cython.org/) 编写,重点使用了 Linux 内核的
|
||||
[io_uring](https://unixism.net/loti/what_is_io_uring.html) 和
|
||||
[kTLS](https://www.kernel.org/doc/html/latest/networking/tls-offload.html)
|
||||
功能,故称作 k(ernel)Loop。
|
||||
|
||||
您可在[木兰宽松许可证, 第2版](http://license.coscl.org.cn/MulanPSL2)允许的范围内使用
|
||||
kLoop 的源代码或发行版。
|
||||
|
||||
**⚠️警告:项目仍在概念验证当中,满地都是坑!⚠️**
|
||||
|
||||
|
||||
## 主要特性
|
||||
|
||||
* **系统调用次数非常少**:得益于 io_uring,所有 I/O 调用都由内核完成;因为启用了
|
||||
`SQPOLL`,唯一的系统调用 `io_uring_enter()`
|
||||
也仅在必要时才会用到,因此节省下了几乎所有系统调用的额外开销;
|
||||
* **主循环主体不占有 GIL**:虽然使用了 Cython 编写,但大部分最核心的代码都被编译为不需要
|
||||
Python 结构的普通 C 代码,一方面节省内存开销和处理时间,另一方面不持有 GIL,仅在有
|
||||
Python 回调时才上 GIL 锁,对多线程稍微友好一点,但还是推荐单线程跑;
|
||||
* **内核网卡代工 TLS**:如果硬件支持,TLS 通讯的全部对称加密解密皆由网卡完成,空出 CPU
|
||||
来做更多的 I/O;即使网卡不支持,io_uring
|
||||
也可以在内核中开多线程来执行对称加解密,不受应用端单线程的限制,充分利用多核 CPU;
|
||||
* **纯异步 DNS 解析**:混编进了 Rust 写的
|
||||
[trust-dns](https://github.com/bluejekyll/trust-dns/),I/O 层直接走
|
||||
io_uring(包括加载 `/etc/resolv.conf` 和 `/etc/hosts` 文件),在 C 和 Rust
|
||||
之间互相调用完成 DNS 解析,并且提供了更加灵活的 Python 接口来控制并发、缓存和配置文件。
|
||||
|
||||
|
||||
## 环境需求
|
||||
|
||||
* Python >= 3.10
|
||||
* Linux >= 5.11 (用 `modprobe tls` 命令来启用 kTLS 模块)
|
||||
* OpenSSL >= 3.0(TLS 1.3 支持 kTLS 收包代工需要最新的开发版本)
|
||||
|
||||
目前主要是在 Ubuntu 22.04 上开发测试的。
|
||||
|
||||
|
||||
## 架构图
|
||||
|
||||
<img src="docs/architecture.zh.png" width="620px" alt="架构图.png">
|
||||
|
||||
@aaronbrighton 说像 Lucky Charms 卡通麦片工厂……
|
||||
|
||||
|
||||
## 开发
|
||||
|
||||
### Ubuntu 22.04
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install gcc libssl-dev python3-dev python3.10-venv
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
python3 -m venv path/to/env
|
||||
source path/to/env/bin/activate
|
||||
pip install cython
|
||||
KLOOP_DEBUG=1 python setup.py develop # 或者直接执行 `make`
|
||||
```
|
BIN
docs/architecture.en.png
Normal file
BIN
docs/architecture.en.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 294 KiB |
BIN
docs/architecture.zh.png
Normal file
BIN
docs/architecture.zh.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 293 KiB |
BIN
docs/kloop@2x.png
Normal file
BIN
docs/kloop@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
|
@ -1,3 +1,7 @@
|
|||
[tool.black]
|
||||
line-length = 79
|
||||
target-version = ["py310"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42", "Cython>=0.29"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
|
645
resolver/Cargo.lock
generated
Normal file
645
resolver/Cargo.lock
generated
Normal file
|
@ -0,0 +1,645 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
|
||||
|
||||
[[package]]
|
||||
name = "enum-as-inner"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "432f044e9077ad6666f3446df0489a71ac3ed0336ea54edf6b85e00cd7562283"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"match_cfg",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipconfig"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98"
|
||||
dependencies = [
|
||||
"socket2",
|
||||
"widestring",
|
||||
"winapi",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.124"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-cache"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_cfg"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resolv-conf"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
|
||||
dependencies = [
|
||||
"hostname",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resolver"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"libc",
|
||||
"log",
|
||||
"resolv-conf",
|
||||
"trust-dns-proto",
|
||||
"trust-dns-resolver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-proto"
|
||||
version = "0.21.2"
|
||||
source = "git+https://github.com/bluejekyll/trust-dns#170e3f7d7dfbc2b6c48318cb5c996a8cc6db77a1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if",
|
||||
"data-encoding",
|
||||
"enum-as-inner",
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna",
|
||||
"ipnet",
|
||||
"lazy_static",
|
||||
"rand",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-resolver"
|
||||
version = "0.21.2"
|
||||
source = "git+https://github.com/bluejekyll/trust-dns#170e3f7d7dfbc2b6c48318cb5c996a8cc6db77a1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"ipconfig",
|
||||
"lazy_static",
|
||||
"lru-cache",
|
||||
"parking_lot",
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"trust-dns-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
19
resolver/Cargo.toml
Normal file
19
resolver/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "resolver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "kloop_resolver"
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.124"
|
||||
log = "0.4.16"
|
||||
resolv-conf = { version = "0.7.0", features = ["system"] }
|
||||
trust-dns-proto = { git = "https://github.com/bluejekyll/trust-dns", default-features = false }
|
||||
trust-dns-resolver = { git = "https://github.com/bluejekyll/trust-dns", default-features = false, features = ["system-config"]}
|
||||
futures-util = "0.3.21"
|
||||
futures-io = "0.3.5"
|
||||
futures-executor = "0.3.5"
|
||||
async-trait = "0.1.43"
|
61
resolver/src/kloop.rs
Normal file
61
resolver/src/kloop.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
kLoop is licensed under Mulan PSL v2.
|
||||
You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
You may obtain a copy of Mulan PSL v2 at:
|
||||
http://license.coscl.org.cn/MulanPSL2
|
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
|
||||
use core::marker;
|
||||
use std::task::Waker;
|
||||
use libc;
|
||||
|
||||
use crate::resolve::KLoopResolver;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct CResolver {
|
||||
_data: [u8; 0],
|
||||
_marker: marker::PhantomData<(*mut u8, marker::PhantomPinned)>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct CResolve {
|
||||
_data: [u8; 0],
|
||||
_marker: marker::PhantomData<(*mut u8, marker::PhantomPinned)>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct UDPAction {
|
||||
_data: [u8; 0],
|
||||
_marker: marker::PhantomData<(*mut u8, marker::PhantomPinned)>,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn resolver_set(c_resolver: *const CResolver, resolver: *mut KLoopResolver);
|
||||
// pub fn resolve_set_poller(resolve: *const CResolve, poller: *mut Poller);
|
||||
pub fn resolve_prep_addr(resolve: *const CResolve) -> *mut libc::sockaddr;
|
||||
pub fn resolve_done_cb(resolve: *const CResolve) -> libc::c_int;
|
||||
pub fn udp_bind(addr: *const libc::sockaddr, addrlen: libc::socklen_t) -> libc::c_int;
|
||||
pub fn udp_action_init(fd: libc::c_int, resolver: *const CResolver) -> libc::c_ulong;
|
||||
pub fn udp_send_poll(
|
||||
send: libc::c_ulong,
|
||||
data: *const u8,
|
||||
datalen: libc::size_t,
|
||||
addr: *const libc::sockaddr,
|
||||
addrlen: libc::socklen_t,
|
||||
waker: *mut Waker,
|
||||
) -> libc::c_int;
|
||||
pub fn udp_recv_poll(
|
||||
recv: libc::c_ulong,
|
||||
data: *mut u8,
|
||||
datalen: libc::size_t,
|
||||
waker: *mut Waker,
|
||||
) -> libc::c_int;
|
||||
pub fn udp_get_addr(action: libc::c_ulong) -> *const libc::sockaddr;
|
||||
pub fn udp_action_free(action: libc::c_ulong);
|
||||
}
|
18
resolver/src/lib.rs
Normal file
18
resolver/src/lib.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
kLoop is licensed under Mulan PSL v2.
|
||||
You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
You may obtain a copy of Mulan PSL v2 at:
|
||||
http://license.coscl.org.cn/MulanPSL2
|
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
|
||||
mod kloop;
|
||||
mod resolve;
|
||||
mod runtime;
|
||||
|
||||
pub use resolve::{resolver_init, resolver_lookup_ip};
|
183
resolver/src/resolve.rs
Normal file
183
resolver/src/resolve.rs
Normal file
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
kLoop is licensed under Mulan PSL v2.
|
||||
You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
You may obtain a copy of Mulan PSL v2 at:
|
||||
http://license.coscl.org.cn/MulanPSL2
|
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
|
||||
use futures_executor::{LocalPool, LocalSpawner};
|
||||
use futures_util::task::{LocalSpawnExt, SpawnError};
|
||||
use std;
|
||||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::net::IpAddr;
|
||||
use std::rc::Rc;
|
||||
|
||||
use libc;
|
||||
use trust_dns_resolver::name_server::{GenericConnection, GenericConnectionProvider};
|
||||
use trust_dns_resolver::system_conf::parse_resolv_conf;
|
||||
use trust_dns_resolver::{AsyncResolver, Hosts};
|
||||
|
||||
use crate::kloop::{resolve_done_cb, resolve_prep_addr, resolver_set, CResolve, CResolver};
|
||||
use crate::runtime::{KLoopHandle, KLoopRuntime};
|
||||
|
||||
type KLoopConnection = GenericConnection;
|
||||
|
||||
type KLoopConnectionProvider = GenericConnectionProvider<KLoopRuntime>;
|
||||
|
||||
thread_local! {
|
||||
pub static CURRENT_RESOLVER: Rc<RefCell<Option<*mut KLoopResolver>>> = Rc::new(RefCell::new(None));
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KLoopResolver {
|
||||
resolver: AsyncResolver<KLoopConnection, KLoopConnectionProvider>,
|
||||
pub c_resolver: *const CResolver,
|
||||
pool: LocalPool,
|
||||
spawner: LocalSpawner,
|
||||
}
|
||||
|
||||
impl KLoopResolver {
|
||||
fn new(
|
||||
resolv_conf: &[u8],
|
||||
hosts_conf: &[u8],
|
||||
c_resolver: *const CResolver,
|
||||
) -> io::Result<Self> {
|
||||
let (config, mut options) = parse_resolv_conf(resolv_conf)?;
|
||||
options.use_hosts_file = false;
|
||||
let conn_provider = GenericConnectionProvider::new(KLoopHandle);
|
||||
let mut resolver = AsyncResolver::new_with_conn(config, options, conn_provider).unwrap();
|
||||
resolver.set_hosts(Some(Hosts::default().read_hosts_conf(hosts_conf)?));
|
||||
let pool = LocalPool::new();
|
||||
let spawner = pool.spawner();
|
||||
Ok(Self {
|
||||
resolver,
|
||||
c_resolver,
|
||||
pool,
|
||||
spawner,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn spawn<Fut>(&self, future: Fut) -> Result<(), SpawnError>
|
||||
where
|
||||
Fut: Future<Output = ()> + 'static,
|
||||
{
|
||||
self.spawner.spawn_local(future)
|
||||
}
|
||||
|
||||
fn run_until_stalled(&mut self) {
|
||||
loop {
|
||||
self.pool.run_until_stalled();
|
||||
if !self.pool.try_run_one() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn lookup_ip(&self, resolve: *mut CResolve, host: &str, port: libc::in_port_t) -> () {
|
||||
match self.resolver.lookup_ip(host).await {
|
||||
Ok(result) => {
|
||||
for ip in result.into_iter() {
|
||||
match ip {
|
||||
IpAddr::V4(ip) => unsafe {
|
||||
let out = resolve_prep_addr(resolve) as *mut libc::sockaddr_in;
|
||||
if out.is_null() {
|
||||
println!("resolve_prep_addr returned NULL");
|
||||
break;
|
||||
}
|
||||
(*out).sin_family = libc::AF_INET as libc::sa_family_t;
|
||||
(*out).sin_addr = libc::in_addr {
|
||||
s_addr: u32::from_ne_bytes(ip.octets()),
|
||||
};
|
||||
(*out).sin_port = port.to_be();
|
||||
(*out).sin_zero = [0; 8];
|
||||
},
|
||||
IpAddr::V6(ip) => unsafe {
|
||||
let out = resolve_prep_addr(resolve) as *mut libc::sockaddr_in6;
|
||||
if out.is_null() {
|
||||
println!("resolve_prep_addr returned NULL");
|
||||
break;
|
||||
}
|
||||
(*out).sin6_family = libc::AF_INET6 as libc::sa_family_t;
|
||||
(*out).sin6_addr = libc::in6_addr {
|
||||
s6_addr: ip.octets(),
|
||||
};
|
||||
(*out).sin6_port = port.to_be();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("lookup_ip error: {:?}", e);
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
resolve_done_cb(resolve);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn resolver_init(
|
||||
c_resolver: *const CResolver,
|
||||
resolv_conf_data: *const u8,
|
||||
resolv_conf_data_size: libc::size_t,
|
||||
hosts_conf_data: *const u8,
|
||||
hosts_conf_data_size: libc::size_t,
|
||||
) -> libc::c_int {
|
||||
let resolv_conf =
|
||||
unsafe { std::slice::from_raw_parts(resolv_conf_data, resolv_conf_data_size) };
|
||||
let hosts_conf = unsafe { std::slice::from_raw_parts(hosts_conf_data, hosts_conf_data_size) };
|
||||
let resolver = match KLoopResolver::new(resolv_conf, hosts_conf, c_resolver) {
|
||||
Ok(resolver) => resolver,
|
||||
Err(e) => return 0,
|
||||
};
|
||||
let rv = Box::into_raw(Box::new(resolver));
|
||||
unsafe {
|
||||
resolver_set(c_resolver, rv);
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn resolver_lookup_ip(
|
||||
resolver: *mut KLoopResolver,
|
||||
resolve: *mut CResolve,
|
||||
host_raw: *const u8,
|
||||
length: libc::size_t,
|
||||
port: libc::in_port_t,
|
||||
) -> libc::c_int {
|
||||
let host = match std::str::from_utf8(unsafe { std::slice::from_raw_parts(host_raw, length) }) {
|
||||
Ok(host) => host,
|
||||
_ => return 0,
|
||||
};
|
||||
let r = || unsafe { resolver.as_mut() }.unwrap();
|
||||
|
||||
let fut = r().lookup_ip(resolve, host, port);
|
||||
if let Err(e) = r().spawn(fut) {
|
||||
println!("spawn error: {:?}", e);
|
||||
return 0;
|
||||
}
|
||||
CURRENT_RESOLVER.with(|current| {
|
||||
*current.borrow_mut() = Some(resolver);
|
||||
r().run_until_stalled();
|
||||
*current.borrow_mut() = None;
|
||||
});
|
||||
1
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn resolver_run_until_stalled(resolver: *mut KLoopResolver) {
|
||||
CURRENT_RESOLVER.with(|current| {
|
||||
*current.borrow_mut() = Some(resolver);
|
||||
unsafe { resolver.as_mut() }.unwrap().run_until_stalled();
|
||||
*current.borrow_mut() = None;
|
||||
});
|
||||
}
|
270
resolver/src/runtime.rs
Normal file
270
resolver/src/runtime.rs
Normal file
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
kLoop is licensed under Mulan PSL v2.
|
||||
You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
You may obtain a copy of Mulan PSL v2 at:
|
||||
http://license.coscl.org.cn/MulanPSL2
|
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
|
||||
use crate::kloop;
|
||||
use crate::resolve::CURRENT_RESOLVER;
|
||||
use async_trait::async_trait;
|
||||
use futures_io::{AsyncRead, AsyncWrite};
|
||||
use libc::{sockaddr, socklen_t};
|
||||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use std::time::Duration;
|
||||
use std::{io, mem};
|
||||
use trust_dns_proto::error::ProtoError;
|
||||
use trust_dns_proto::tcp::{Connect, DnsTcpStream};
|
||||
use trust_dns_proto::udp::UdpSocket;
|
||||
use trust_dns_proto::Time;
|
||||
use trust_dns_resolver::name_server::{RuntimeProvider, Spawn};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct KLoopTimer {}
|
||||
|
||||
#[async_trait]
|
||||
impl Time for KLoopTimer {
|
||||
async fn delay_for(duration: Duration) {
|
||||
println!("TODO: delay_for: {:?}", duration);
|
||||
}
|
||||
|
||||
async fn timeout<F: 'static + Future + Send>(
|
||||
duration: Duration,
|
||||
future: F,
|
||||
) -> io::Result<F::Output> {
|
||||
println!("TODO: timeout: {:?}", duration);
|
||||
Ok(future.await)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KLoopTcp {}
|
||||
|
||||
#[async_trait]
|
||||
impl Connect for KLoopTcp {
|
||||
async fn connect_with_bind(
|
||||
addr: SocketAddr,
|
||||
bind_addr: Option<SocketAddr>,
|
||||
) -> io::Result<Self> {
|
||||
println!("TODO: connect_with_bind: {:?} {:?}", addr, bind_addr);
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for KLoopTcp {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
println!("TODO: poll_read");
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for KLoopTcp {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
println!("TODO: poll_write");
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
println!("TODO: poll_flush");
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
println!("TODO: poll_close");
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsTcpStream for KLoopTcp {
|
||||
type Time = KLoopTimer;
|
||||
}
|
||||
|
||||
pub struct KLoopUdp {
|
||||
fd: libc::c_int,
|
||||
send: libc::c_ulong,
|
||||
recv: libc::c_ulong,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UdpSocket for KLoopUdp {
|
||||
type Time = KLoopTimer;
|
||||
|
||||
async fn connect(addr: SocketAddr) -> io::Result<Self> {
|
||||
println!("TODO: KLoopUdp: connect({})", addr);
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn connect_with_bind(addr: SocketAddr, bind_addr: SocketAddr) -> io::Result<Self> {
|
||||
println!("TODO: KLoopUdp: connect_with_bind({}, {})", addr, bind_addr);
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn bind(addr: SocketAddr) -> io::Result<Self> {
|
||||
let (addr_ptr, addr_len) = socket_addr_as_ptr(addr);
|
||||
let fd = unsafe { kloop::udp_bind(addr_ptr, addr_len) };
|
||||
CURRENT_RESOLVER.with(|resolver| {
|
||||
let resolver = resolver.borrow().unwrap();
|
||||
let resolver = unsafe { resolver.as_ref() }.unwrap();
|
||||
let resolver = resolver.c_resolver;
|
||||
let send = unsafe { kloop::udp_action_init(fd, resolver) };
|
||||
let recv = unsafe { kloop::udp_action_init(fd, resolver) };
|
||||
Ok(KLoopUdp { fd, send, recv })
|
||||
})
|
||||
}
|
||||
|
||||
fn poll_recv_from(
|
||||
&self,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<io::Result<(usize, SocketAddr)>> {
|
||||
let waker = Box::new(cx.waker().clone());
|
||||
match unsafe {
|
||||
kloop::udp_recv_poll(
|
||||
self.recv,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len(),
|
||||
Box::into_raw(waker),
|
||||
)
|
||||
} {
|
||||
res if res > 0 => {
|
||||
Poll::Ready(unsafe {
|
||||
ptr_to_socket_addr(kloop::udp_get_addr(self.recv))
|
||||
}.map(|addr| (res as usize, addr)))
|
||||
}
|
||||
_ => Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_send_to(
|
||||
&self,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
target: SocketAddr,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
let waker = Box::new(cx.waker().clone());
|
||||
let (addr, addrlen) = socket_addr_as_ptr(target);
|
||||
match unsafe {
|
||||
kloop::udp_send_poll(
|
||||
self.send,
|
||||
buf.as_ptr(),
|
||||
buf.len(),
|
||||
addr,
|
||||
addrlen,
|
||||
Box::into_raw(waker),
|
||||
)
|
||||
} {
|
||||
res if res > 0 => {
|
||||
Poll::Ready(Ok(res as usize))
|
||||
}
|
||||
res => {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for KLoopUdp {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
kloop::udp_action_free(self.send);
|
||||
kloop::udp_action_free(self.recv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn socket_addr_as_ptr(addr: SocketAddr) -> (*const sockaddr, socklen_t) {
|
||||
match addr {
|
||||
SocketAddr::V4(ref a) => (
|
||||
a as *const _ as *const _,
|
||||
mem::size_of_val(a) as libc::socklen_t,
|
||||
),
|
||||
SocketAddr::V6(ref a) => (
|
||||
a as *const _ as *const _,
|
||||
mem::size_of_val(a) as libc::socklen_t,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsafe fn ptr_to_socket_addr(
|
||||
addr: *const libc::sockaddr,
|
||||
) -> io::Result<SocketAddr> {
|
||||
match (*addr).sa_family as libc::c_int {
|
||||
libc::AF_INET => {
|
||||
let addr: &libc::sockaddr_in = &*(addr as *const libc::sockaddr_in);
|
||||
let ip = Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes());
|
||||
let port = u16::from_be(addr.sin_port);
|
||||
Ok(SocketAddr::V4(SocketAddrV4::new(ip, port)))
|
||||
}
|
||||
libc::AF_INET6 => {
|
||||
let addr: &libc::sockaddr_in6 = &*(addr as *const libc::sockaddr_in6);
|
||||
let ip = Ipv6Addr::from(addr.sin6_addr.s6_addr);
|
||||
let port = u16::from_be(addr.sin6_port);
|
||||
Ok(SocketAddr::V6(SocketAddrV6::new(
|
||||
ip,
|
||||
port,
|
||||
addr.sin6_flowinfo,
|
||||
addr.sin6_scope_id,
|
||||
)))
|
||||
}
|
||||
_ => Err(io::ErrorKind::InvalidInput.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct KLoopHandle;
|
||||
|
||||
impl Spawn for KLoopHandle {
|
||||
fn spawn_bg<F>(&mut self, future: F)
|
||||
where
|
||||
F: Future<Output = Result<(), ProtoError>> + Send + 'static,
|
||||
{
|
||||
CURRENT_RESOLVER.with(|resolver| {
|
||||
let r = resolver.borrow().unwrap();
|
||||
let r = unsafe { r.as_mut() }.unwrap();
|
||||
r.spawn(async {
|
||||
future.await.unwrap_or_else(|e| {
|
||||
println!("spawn_bg error: {:?}", e);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct KLoopRuntime;
|
||||
|
||||
impl RuntimeProvider for KLoopRuntime {
|
||||
type Handle = KLoopHandle;
|
||||
type Timer = KLoopTimer;
|
||||
type Udp = KLoopUdp;
|
||||
type Tcp = KLoopTcp;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn waker_wake(waker: *mut Waker) {
|
||||
let waker = unsafe { Box::from_raw(waker) };
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn waker_forget(waker: *mut Waker) {
|
||||
unsafe { Box::from_raw(waker) };
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
[metadata]
|
||||
name = kLoop
|
||||
version = 0.0.1
|
||||
version = 0.1.0
|
||||
author = Fantix King
|
||||
author_email = fantix.king@gmail.com
|
||||
description = An asyncio event loop using Linux io_uring and kTLS.
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/fantix/kloop
|
||||
url = https://gitee.com/fantix/kloop
|
||||
project_urls =
|
||||
Bug Tracker = https://github.com/fantix/kloop/issues
|
||||
Bug Tracker = https://gitee.com/fantix/kloop/issues
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
License :: OSI Approved
|
||||
|
@ -18,7 +18,7 @@ classifiers =
|
|||
package_dir =
|
||||
= src
|
||||
packages = find:
|
||||
python_requires = >=3.8
|
||||
python_requires = >=3.10
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
|
|
138
setup.py
138
setup.py
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2022 Fantix King http://fantix.pro
|
||||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
|
@ -9,16 +9,134 @@
|
|||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
from Cython.Build import cythonize
|
||||
from Cython.Distutils import Extension
|
||||
import os
|
||||
import subprocess
|
||||
import sysconfig
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
ext_modules=cythonize(
|
||||
[
|
||||
Extension("kloop.uring", ["src/kloop/uring.pyx"]),
|
||||
Extension("kloop.ktls", ["src/kloop/ktls.pyx"]),
|
||||
],
|
||||
language_level="3",
|
||||
from Cython.Build import cythonize
|
||||
from Cython.Distutils import build_ext
|
||||
from Cython.Distutils import Extension
|
||||
from distutils import dir_util
|
||||
from distutils import log
|
||||
from distutils.command.clean import clean
|
||||
|
||||
|
||||
class build_ext_with_resolver(build_ext):
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options(
|
||||
"build",
|
||||
("debug", "debug"),
|
||||
("force", "force"),
|
||||
)
|
||||
if self.debug is None:
|
||||
self.debug = os.getenv("KLOOP_DEBUG", "0") == "1"
|
||||
if self.force is None:
|
||||
self.force = os.getenv("KLOOP_FORCE", "0") == "1"
|
||||
|
||||
for ext in self.distribution.ext_modules:
|
||||
if ext.cython_directives:
|
||||
ext.cython_directives["language_level"] = "3"
|
||||
else:
|
||||
ext.cython_directives = {"language_level": "3"}
|
||||
if ext.cython_compile_time_env:
|
||||
ext.cython_compile_time_env["DEBUG"] = self.debug
|
||||
else:
|
||||
ext.cython_compile_time_env = {"DEBUG": self.debug}
|
||||
if self.debug:
|
||||
if "-O0" not in ext.extra_compile_args:
|
||||
ext.extra_compile_args.append("-O0")
|
||||
if ext.name == "kloop.loop":
|
||||
resolver = (
|
||||
f"resolver/target/{'debug' if self.debug else 'release'}"
|
||||
f"/libkloop_resolver.a"
|
||||
)
|
||||
if resolver not in ext.extra_link_args:
|
||||
ext.extra_link_args.append(resolver)
|
||||
if resolver not in ext.depends:
|
||||
ext.depends.append(resolver)
|
||||
|
||||
super().finalize_options()
|
||||
|
||||
def run(self):
|
||||
if self.force:
|
||||
cmd = ["cargo", "clean", "-p", "resolver"]
|
||||
if not self.debug:
|
||||
cmd.append("-r")
|
||||
self.announce(f"Running: {cmd}", log.INFO)
|
||||
subprocess.check_call(cmd, cwd="resolver")
|
||||
|
||||
cmd = ["cargo", "build"]
|
||||
if not self.debug:
|
||||
cmd.append("-r")
|
||||
self.announce(f"Running: {cmd}", log.INFO)
|
||||
subprocess.check_call(cmd, cwd="resolver")
|
||||
super().run()
|
||||
|
||||
|
||||
class clean_with_resolver(clean):
|
||||
def run(self):
|
||||
super().run()
|
||||
for d in self.distribution.package_dir.values():
|
||||
self._clean_dir(d)
|
||||
cmd = ["cargo", "clean"]
|
||||
if not self.all:
|
||||
cmd.extend(["-p", "resolver"])
|
||||
self.announce(f"Running: {cmd}", log.INFO)
|
||||
if not self.dry_run:
|
||||
subprocess.check_call(cmd, cwd="resolver")
|
||||
|
||||
def _clean_dir(self, path):
|
||||
for f in os.listdir(path):
|
||||
name, ext = os.path.splitext(f)
|
||||
real_f = os.path.join(path, f)
|
||||
is_dir = os.path.isdir(real_f) and not os.path.islink(real_f)
|
||||
if name == "__pycache__" or ext in {".egg-info", ".so", ".c"}:
|
||||
if is_dir:
|
||||
dir_util.remove_tree(real_f, dry_run=self.dry_run)
|
||||
else:
|
||||
self.announce(f"removing {real_f!r}", log.INFO)
|
||||
if not self.dry_run:
|
||||
os.remove(real_f)
|
||||
elif is_dir:
|
||||
self._clean_dir(real_f)
|
||||
|
||||
|
||||
setup(
|
||||
cmdclass={
|
||||
"build_ext": build_ext_with_resolver,
|
||||
"clean": clean_with_resolver,
|
||||
},
|
||||
ext_modules=[
|
||||
Extension(
|
||||
"kloop.loop",
|
||||
["src/kloop/loop.pyx"],
|
||||
),
|
||||
Extension(
|
||||
"kloop.tls",
|
||||
["src/kloop/tls.pyx"],
|
||||
libraries=[
|
||||
lib.strip().removeprefix("-l")
|
||||
for lib in sysconfig.get_config_var("OPENSSL_LIBS").split()
|
||||
],
|
||||
include_dirs=[
|
||||
d.strip().removeprefix("-I")
|
||||
for d in sysconfig.get_config_var("OPENSSL_INCLUDES").split()
|
||||
],
|
||||
library_dirs=[
|
||||
d.strip().removeprefix("-L")
|
||||
for d in sysconfig.get_config_var("OPENSSL_LDFLAGS").split()
|
||||
if d.strip().startswith("-L")
|
||||
],
|
||||
extra_link_args=[
|
||||
d.strip()
|
||||
for d in sysconfig.get_config_var("OPENSSL_LDFLAGS").split()
|
||||
if not d.strip().startswith("-L")
|
||||
],
|
||||
runtime_library_dirs=(lambda x: [x] if x else [])(
|
||||
sysconfig.get_config_var("OPENSSL_RPATH")
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2022 Fantix King http://fantix.pro
|
||||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
|
@ -8,3 +8,5 @@
|
|||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
from .loop import KLoop, KLoopPolicy
|
||||
|
|
19
src/kloop/fileio.pxd
Normal file
19
src/kloop/fileio.pxd
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef struct FileReader:
|
||||
Loop* loop
|
||||
const char* path
|
||||
int fd, cancelled
|
||||
char* data
|
||||
size_t size, offset
|
||||
RingCallback ring_cb
|
||||
RingCallback done_cb
|
116
src/kloop/fileio.pyx
Normal file
116
src/kloop/fileio.pyx
Normal file
|
@ -0,0 +1,116 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef size_t FILE_READ_BUF_SIZE = 4096
|
||||
|
||||
|
||||
cdef int file_reader_openat_cb(RingCallback* cb) nogil except 0:
|
||||
cdef:
|
||||
int fd = cb.res
|
||||
FileReader* fr = <FileReader*>cb.data
|
||||
|
||||
if fd < 0:
|
||||
fr.done_cb.res = fd
|
||||
return fr.done_cb.callback(&fr.done_cb)
|
||||
if fr.cancelled:
|
||||
fr.done_cb.res = -libc.ECANCELED
|
||||
return fr.done_cb.callback(&fr.done_cb)
|
||||
fr.fd = fd
|
||||
fr.data = <char*>PyMem_RawMalloc(FILE_READ_BUF_SIZE)
|
||||
if fr.data == NULL:
|
||||
fr.done_cb.res = -errno.ENOMEM
|
||||
return fr.done_cb.callback(&fr.done_cb)
|
||||
fr.size = FILE_READ_BUF_SIZE
|
||||
fr.offset = 0
|
||||
fr.ring_cb.callback = file_reader_read_cb
|
||||
if ring_sq_submit_read(
|
||||
&fr.loop.ring.sq,
|
||||
fd,
|
||||
fr.data,
|
||||
FILE_READ_BUF_SIZE,
|
||||
0,
|
||||
&fr.ring_cb,
|
||||
):
|
||||
return 1
|
||||
else:
|
||||
fr.done_cb.res = -errno.EAGAIN
|
||||
return fr.done_cb.callback(&fr.done_cb)
|
||||
|
||||
|
||||
cdef int file_reader_read_cb(RingCallback* cb) nogil except 0:
|
||||
cdef:
|
||||
int read = cb.res
|
||||
FileReader* fr = <FileReader*>cb.data
|
||||
size_t size = fr.size
|
||||
size_t offset = fr.offset
|
||||
|
||||
if read < 0:
|
||||
fr.done_cb.res = read
|
||||
return fr.done_cb.callback(&fr.done_cb)
|
||||
if fr.cancelled:
|
||||
fr.done_cb.res = -libc.ECANCELED
|
||||
return fr.done_cb.callback(&fr.done_cb)
|
||||
offset += read
|
||||
if read > 0 and offset == size:
|
||||
size += FILE_READ_BUF_SIZE
|
||||
if PyMem_RawRealloc(fr.data, size) == NULL:
|
||||
fr.done_cb.res = -errno.ENOMEM
|
||||
return fr.done_cb.callback(&fr.done_cb)
|
||||
fr.size = size
|
||||
fr.offset = offset
|
||||
if ring_sq_submit_read(
|
||||
&fr.loop.ring.sq,
|
||||
fr.fd,
|
||||
fr.data + offset,
|
||||
FILE_READ_BUF_SIZE,
|
||||
offset,
|
||||
&fr.ring_cb,
|
||||
):
|
||||
return 1
|
||||
else:
|
||||
fr.done_cb.res = -errno.EAGAIN
|
||||
return fr.done_cb.callback(&fr.done_cb)
|
||||
else:
|
||||
fr.done_cb.res = 1
|
||||
fr.offset = offset
|
||||
return fr.done_cb.callback(&fr.done_cb)
|
||||
|
||||
|
||||
cdef int file_reader_start(
|
||||
FileReader* fr, Loop* loop, const char* path
|
||||
) nogil:
|
||||
fr.loop = loop
|
||||
fr.done_cb.res = 0
|
||||
fr.fd = 0
|
||||
fr.data = NULL
|
||||
fr.cancelled = 0
|
||||
fr.ring_cb.callback = file_reader_openat_cb
|
||||
fr.ring_cb.data = <void*>fr
|
||||
return ring_sq_submit_openat(
|
||||
&loop.ring.sq,
|
||||
0, # dfd
|
||||
path, # path
|
||||
0, # flags
|
||||
0, # mode
|
||||
&fr.ring_cb
|
||||
)
|
||||
|
||||
|
||||
cdef int file_reader_done(FileReader* fr) nogil:
|
||||
cdef int fd = fr.fd
|
||||
fr.done_cb.res = 0
|
||||
if fr.data != NULL:
|
||||
PyMem_RawFree(fr.data)
|
||||
fr.data = NULL
|
||||
if fd != 0:
|
||||
fr.fd = 0
|
||||
return ring_sq_submit_close(&fr.loop.ring.sq, fd, NULL)
|
||||
return 1
|
37
src/kloop/handle.pxd
Normal file
37
src/kloop/handle.pxd
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef unsigned char CANCELLED_MASK = 1
|
||||
cdef unsigned char SCHEDULED_MASK = 1 << 1
|
||||
|
||||
|
||||
cdef struct Callback:
|
||||
unsigned char mask
|
||||
PyObject* handle
|
||||
long long when
|
||||
|
||||
|
||||
cdef class Handle:
|
||||
cdef:
|
||||
Callback cb
|
||||
object callback
|
||||
object args
|
||||
KLoopImpl loop
|
||||
object source_traceback
|
||||
object repr
|
||||
object context
|
||||
|
||||
cdef run(self)
|
||||
|
||||
|
||||
cdef class TimerHandle(Handle):
|
||||
cdef:
|
||||
bint scheduled
|
152
src/kloop/handle.pyx
Normal file
152
src/kloop/handle.pyx
Normal file
|
@ -0,0 +1,152 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef class Handle:
|
||||
def __init__(self, callback, args, loop, context=None):
|
||||
if context is None:
|
||||
context = contextvars.copy_context()
|
||||
self.context = context
|
||||
self.loop = loop
|
||||
self.callback = callback
|
||||
self.args = args
|
||||
self.repr = None
|
||||
self.cb.handle = <PyObject*>self
|
||||
# if self._loop.get_debug():
|
||||
# self._source_traceback = format_helpers.extract_stack(
|
||||
# sys._getframe(1))
|
||||
# else:
|
||||
# self._source_traceback = None
|
||||
|
||||
def _repr_info(self):
|
||||
info = [self.__class__.__name__]
|
||||
if self.cb.mask & CANCELLED_MASK:
|
||||
info.append('cancelled')
|
||||
if self.callback is not None:
|
||||
info.append(_format_callback_source(self.callback, self.args))
|
||||
# if self._source_traceback:
|
||||
# frame = self._source_traceback[-1]
|
||||
# info.append(f'created at {frame[0]}:{frame[1]}')
|
||||
return info
|
||||
|
||||
def __repr__(self):
|
||||
# if self._repr is not None:
|
||||
# return self._repr
|
||||
info = self._repr_info()
|
||||
return '<{}>'.format(' '.join(info))
|
||||
|
||||
def cancel(self):
|
||||
if self.cb.mask & CANCELLED_MASK == 0:
|
||||
self.cb.mask |= CANCELLED_MASK
|
||||
# if self._loop.get_debug():
|
||||
# # Keep a representation in debug mode to keep callback and
|
||||
# # parameters. For example, to log the warning
|
||||
# # "Executing <Handle...> took 2.5 second"
|
||||
# self._repr = repr(self)
|
||||
self.callback = None
|
||||
self.args = None
|
||||
|
||||
def cancelled(self):
|
||||
return self.cb.mask & CANCELLED_MASK == 1
|
||||
|
||||
cdef run(self):
|
||||
try:
|
||||
self.context.run(self.callback, *self.args)
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except BaseException as exc:
|
||||
cb = _format_callback_source(self.callback, self.args)
|
||||
msg = f'Exception in callback {cb}'
|
||||
context = {
|
||||
'message': msg,
|
||||
'exception': exc,
|
||||
'handle': self,
|
||||
}
|
||||
if self.source_traceback:
|
||||
context['source_traceback'] = self.source_traceback
|
||||
self.loop.call_exception_handler(context)
|
||||
self = None # Needed to break cycles when an exception occurs.
|
||||
|
||||
|
||||
cdef class TimerHandle(Handle):
|
||||
"""Object returned by timed callback registration methods."""
|
||||
|
||||
def __init__(self, when, callback, args, loop, context=None):
|
||||
assert when is not None
|
||||
super().__init__(callback, args, loop, context)
|
||||
if self.source_traceback:
|
||||
del self.source_traceback[-1]
|
||||
self.cb.when = when
|
||||
|
||||
# def _repr_info(self):
|
||||
# info = super()._repr_info()
|
||||
# pos = 2 if self._cancelled else 1
|
||||
# info.insert(pos, f'when={self._when}')
|
||||
# return info
|
||||
|
||||
def cancel(self):
|
||||
if self.cb.mask & (CANCELLED_MASK | SCHEDULED_MASK) == SCHEDULED_MASK:
|
||||
self.loop.loop.timer_cancelled_count += 1
|
||||
super().cancel()
|
||||
|
||||
def when(self):
|
||||
return self.cb.when
|
||||
|
||||
|
||||
def _get_function_source(func):
|
||||
func = inspect.unwrap(func)
|
||||
if inspect.isfunction(func):
|
||||
code = func.__code__
|
||||
return (code.co_filename, code.co_firstlineno)
|
||||
if isinstance(func, functools.partial):
|
||||
return _get_function_source(func.func)
|
||||
if isinstance(func, functools.partialmethod):
|
||||
return _get_function_source(func.func)
|
||||
return None
|
||||
|
||||
|
||||
def _format_callback_source(func, args):
|
||||
func_repr = _format_callback(func, args, None)
|
||||
source = _get_function_source(func)
|
||||
if source:
|
||||
func_repr += f' at {source[0]}:{source[1]}'
|
||||
return func_repr
|
||||
|
||||
|
||||
def _format_args_and_kwargs(args, kwargs):
|
||||
"""Format function arguments and keyword arguments.
|
||||
|
||||
Special case for a single parameter: ('hello',) is formatted as ('hello').
|
||||
"""
|
||||
# use reprlib to limit the length of the output
|
||||
items = []
|
||||
if args:
|
||||
items.extend(reprlib.repr(arg) for arg in args)
|
||||
if kwargs:
|
||||
items.extend(f'{k}={reprlib.repr(v)}' for k, v in kwargs.items())
|
||||
return '({})'.format(', '.join(items))
|
||||
|
||||
|
||||
def _format_callback(func, args, kwargs, suffix=''):
|
||||
if isinstance(func, functools.partial):
|
||||
suffix = _format_args_and_kwargs(args, kwargs) + suffix
|
||||
return _format_callback(func.func, func.args, func.keywords, suffix)
|
||||
|
||||
if hasattr(func, '__qualname__') and func.__qualname__:
|
||||
func_repr = func.__qualname__
|
||||
elif hasattr(func, '__name__') and func.__name__:
|
||||
func_repr = func.__name__
|
||||
else:
|
||||
func_repr = repr(func)
|
||||
|
||||
func_repr += _format_args_and_kwargs(args, kwargs)
|
||||
if suffix:
|
||||
func_repr += suffix
|
||||
return func_repr
|
15
src/kloop/heapq.pxd
Normal file
15
src/kloop/heapq.pxd
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef struct HeapQueue:
|
||||
Callback** array
|
||||
int size
|
||||
int tail
|
184
src/kloop/heapq.pyx
Normal file
184
src/kloop/heapq.pyx
Normal file
|
@ -0,0 +1,184 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef int HEAP_BLOCK_SIZE = 1024
|
||||
|
||||
|
||||
cdef void siftup(HeapQueue* heap, int pos, int endpos) nogil:
|
||||
cdef:
|
||||
int childpos, limit = endpos >> 1
|
||||
Callback** array = heap.array
|
||||
|
||||
while pos < limit:
|
||||
childpos = 2 * pos + 1
|
||||
if childpos + 1 < endpos:
|
||||
if array[childpos].when >= array[childpos + 1].when:
|
||||
childpos += 1
|
||||
array[childpos], array[pos] = array[pos], array[childpos]
|
||||
pos = childpos
|
||||
siftdown(heap, pos, endpos)
|
||||
|
||||
|
||||
cdef void siftdown(HeapQueue* heap, int pos, int size) nogil:
|
||||
cdef:
|
||||
int parentpos
|
||||
Callback** array = heap.array
|
||||
long long new_when = array[pos].when
|
||||
while pos > 0:
|
||||
parentpos = (pos - 1) >> 1
|
||||
if new_when >= array[parentpos].when:
|
||||
break
|
||||
new_when = array[pos].when
|
||||
array[pos], array[parentpos] = array[parentpos], array[pos]
|
||||
pos = parentpos
|
||||
|
||||
|
||||
cdef int heapq_init(HeapQueue* heap) nogil except 0:
|
||||
heap.array = <Callback**>PyMem_RawMalloc(
|
||||
sizeof(Callback*) * HEAP_BLOCK_SIZE
|
||||
)
|
||||
if heap.array == NULL:
|
||||
with gil:
|
||||
raise MemoryError
|
||||
heap.size = HEAP_BLOCK_SIZE
|
||||
heap.tail = 0
|
||||
return 1
|
||||
|
||||
|
||||
cdef void heapq_uninit(HeapQueue* heap) nogil:
|
||||
cdef:
|
||||
int i = 0, tail = heap.tail
|
||||
Callback** array = heap.array
|
||||
|
||||
if array == NULL:
|
||||
return
|
||||
if i < tail:
|
||||
with gil:
|
||||
while i < tail:
|
||||
Py_DECREF(<object>array[i].handle)
|
||||
PyMem_RawFree(array)
|
||||
heap.array = NULL
|
||||
|
||||
|
||||
cdef heapq_push_py(HeapQueue* heap, Handle handle):
|
||||
cdef Callback* callback = &handle.cb
|
||||
Py_INCREF(handle)
|
||||
with nogil:
|
||||
heapq_push(heap, callback, 1)
|
||||
|
||||
|
||||
cdef int heapq_push(
|
||||
HeapQueue* heap, Callback* callback, int keep
|
||||
) nogil except 0:
|
||||
cdef:
|
||||
int size = heap.size, tail = heap.tail
|
||||
Callback** array = heap.array
|
||||
|
||||
if tail == size:
|
||||
size += HEAP_BLOCK_SIZE
|
||||
array = <Callback**>PyMem_RawRealloc(array, sizeof(Callback*) * size)
|
||||
if array == NULL:
|
||||
with gil:
|
||||
raise MemoryError
|
||||
heap.size = size
|
||||
array[tail] = callback
|
||||
size = heap.tail = tail + 1
|
||||
if keep:
|
||||
siftdown(heap, tail, size)
|
||||
return 1
|
||||
|
||||
|
||||
cdef Handle heapq_pop_py(HeapQueue* heap):
|
||||
cdef:
|
||||
Handle handle
|
||||
Callback* callback
|
||||
|
||||
with nogil:
|
||||
callback = heapq_pop(heap)
|
||||
if callback == NULL:
|
||||
return None
|
||||
else:
|
||||
handle = <Handle>callback.handle
|
||||
Py_DECREF(handle)
|
||||
return handle
|
||||
|
||||
|
||||
cdef Callback* heapq_pop(HeapQueue* heap) nogil:
|
||||
cdef:
|
||||
Callback* rv
|
||||
Callback** array = heap.array
|
||||
int size = heap.size, tail = heap.tail
|
||||
|
||||
if tail == 0:
|
||||
return NULL
|
||||
|
||||
tail = heap.tail = tail - 1
|
||||
rv = array[tail]
|
||||
|
||||
if tail == 0:
|
||||
if size > HEAP_BLOCK_SIZE:
|
||||
size = HEAP_BLOCK_SIZE
|
||||
if PyMem_RawRealloc(array, sizeof(Callback*) * size) != NULL:
|
||||
heap.size = size
|
||||
return rv
|
||||
|
||||
rv, array[0] = array[0], rv
|
||||
if tail > 1:
|
||||
siftup(heap, 0, tail)
|
||||
if size - tail >= HEAP_BLOCK_SIZE * 2:
|
||||
size -= HEAP_BLOCK_SIZE
|
||||
if PyMem_RawRealloc(array, sizeof(Callback*) * size) != NULL:
|
||||
heap.size = size
|
||||
return rv
|
||||
|
||||
|
||||
cdef inline int keep_top_bit(int n) nogil:
|
||||
cdef int i = 0
|
||||
while n > 1:
|
||||
n >>= 1
|
||||
i += 1
|
||||
return n << i
|
||||
|
||||
|
||||
cdef inline void heapq_cache_friendly_heapify(HeapQueue* heap, int tail) nogil:
|
||||
cdef:
|
||||
int m = tail >> 1, mhalf = m >> 1
|
||||
int leftmost = keep_top_bit(m + 1) - 1
|
||||
int i = leftmost - 1, j
|
||||
|
||||
while i >= mhalf:
|
||||
j = i
|
||||
while True:
|
||||
siftup(heap, j, tail)
|
||||
if not (j & 1):
|
||||
break
|
||||
j >>= 1
|
||||
i -= 1
|
||||
|
||||
i = m - 1
|
||||
while i >= leftmost:
|
||||
j = i
|
||||
while True:
|
||||
siftup(heap, j, tail)
|
||||
if not (j & 1):
|
||||
break
|
||||
j >>= 1
|
||||
i -= 1
|
||||
|
||||
|
||||
cdef void heapq_heapify(HeapQueue* heap) nogil:
|
||||
cdef int tail = heap.tail, i = (tail >> 1) - 1
|
||||
if tail > 2500:
|
||||
heapq_cache_friendly_heapify(heap, tail)
|
||||
else:
|
||||
while i >= 0:
|
||||
siftup(heap, i, tail)
|
||||
i -= 1
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2022 Fantix King http://fantix.pro
|
||||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
|
@ -7,4 +7,3 @@
|
|||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
27
src/kloop/includes/atomic.pxd
Normal file
27
src/kloop/includes/atomic.pxd
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef extern from "<stdatomic.h>" nogil:
|
||||
ctypedef enum MemoryOrder "memory_order":
|
||||
relaxed "memory_order_relaxed"
|
||||
acquire "memory_order_acquire"
|
||||
release "memory_order_release"
|
||||
seq_cst "memory_order_seq_cst"
|
||||
|
||||
ctypedef unsigned uint "atomic_uint"
|
||||
|
||||
unsigned load_explicit "atomic_load_explicit" (
|
||||
uint* object, MemoryOrder order
|
||||
)
|
||||
void store_explicit "atomic_store_explicit" (
|
||||
uint* object, unsigned desired, MemoryOrder order
|
||||
)
|
||||
void thread_fence "atomic_thread_fence" (MemoryOrder order)
|
79
src/kloop/includes/libc.pxd
Normal file
79
src/kloop/includes/libc.pxd
Normal file
|
@ -0,0 +1,79 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef extern from "sys/syscall.h" nogil:
|
||||
int SYS_io_uring_setup
|
||||
int SYS_io_uring_enter
|
||||
int SYS_io_uring_register
|
||||
|
||||
cdef extern from "unistd.h" nogil:
|
||||
int syscall(int number, ...)
|
||||
|
||||
cdef extern from "signal.h" nogil:
|
||||
int _NSIG
|
||||
|
||||
cdef extern from "sys/socket.h" nogil:
|
||||
enum:
|
||||
SOCK_DGRAM
|
||||
SOCK_STREAM
|
||||
|
||||
ctypedef int socklen_t
|
||||
ctypedef int sa_family_t
|
||||
ctypedef int in_port_t
|
||||
|
||||
int SOL_TLS
|
||||
|
||||
struct sockaddr:
|
||||
sa_family_t sa_family
|
||||
|
||||
struct msghdr:
|
||||
void* msg_name # Optional address
|
||||
socklen_t msg_namelen # Size of address
|
||||
iovec* msg_iov # Scatter/gather array
|
||||
size_t msg_iovlen # Number of elements in msg_iov
|
||||
void* msg_control # ancillary data, see below
|
||||
size_t msg_controllen # ancillary data buffer len
|
||||
int msg_flags # flags on received message
|
||||
|
||||
struct cmsghdr:
|
||||
socklen_t cmsg_len # data byte count, including header
|
||||
int cmsg_level # originating protocol
|
||||
int cmsg_type # protocol-specific type
|
||||
|
||||
size_t CMSG_LEN(size_t length)
|
||||
cmsghdr* CMSG_FIRSTHDR(msghdr* msgh)
|
||||
unsigned char* CMSG_DATA(cmsghdr* cmsg)
|
||||
size_t CMSG_SPACE(size_t length)
|
||||
|
||||
int socket(int domain, int type, int protocol)
|
||||
int setsockopt(
|
||||
int socket,
|
||||
int level,
|
||||
int option_name,
|
||||
const void* option_value,
|
||||
socklen_t option_len,
|
||||
)
|
||||
int bind(int sockfd, const sockaddr* addr, socklen_t addrlen)
|
||||
|
||||
|
||||
cdef extern from "arpa/inet.h" nogil:
|
||||
int inet_pton(int af, char* src, void* dst)
|
||||
int htons(short p)
|
||||
|
||||
cdef extern from "sys/uio.h" nogil:
|
||||
struct iovec:
|
||||
void* iov_base
|
||||
size_t iov_len
|
||||
|
||||
|
||||
cdef extern from "<errno.h>" nogil:
|
||||
enum:
|
||||
ECANCELED
|
135
src/kloop/includes/linux.pxd
Normal file
135
src/kloop/includes/linux.pxd
Normal file
|
@ -0,0 +1,135 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef extern from "linux/fs.h" nogil:
|
||||
ctypedef int __kernel_rwf_t
|
||||
|
||||
|
||||
cdef extern from "linux/types.h" nogil:
|
||||
ctypedef int __u8
|
||||
ctypedef int __u16
|
||||
ctypedef int __u64
|
||||
ctypedef int __u32
|
||||
ctypedef int __s32
|
||||
ctypedef int __kernel_time64_t
|
||||
|
||||
|
||||
cdef extern from "linux/time_types.h" nogil:
|
||||
struct __kernel_timespec:
|
||||
__kernel_time64_t tv_sec
|
||||
long long tv_nsec
|
||||
|
||||
|
||||
cdef extern from "linux/tcp.h" nogil:
|
||||
int TCP_ULP
|
||||
|
||||
|
||||
cdef extern from "linux/tls.h" nogil:
|
||||
int TLS_GET_RECORD_TYPE
|
||||
|
||||
__u16 TLS_CIPHER_AES_GCM_256
|
||||
int TLS_CIPHER_AES_GCM_256_IV_SIZE
|
||||
int TLS_CIPHER_AES_GCM_256_SALT_SIZE
|
||||
int TLS_CIPHER_AES_GCM_256_KEY_SIZE
|
||||
int TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE
|
||||
int TLS_TX
|
||||
int TLS_RX
|
||||
|
||||
struct tls_crypto_info:
|
||||
__u16 version
|
||||
__u16 cipher_type
|
||||
|
||||
struct tls12_crypto_info_aes_gcm_256:
|
||||
tls_crypto_info info
|
||||
unsigned char* iv
|
||||
unsigned char* key
|
||||
unsigned char* salt
|
||||
unsigned char* rec_seq
|
||||
|
||||
|
||||
cdef extern from "linux/io_uring.h" nogil:
|
||||
unsigned IORING_SETUP_SQPOLL
|
||||
unsigned IORING_SETUP_SQ_AFF
|
||||
|
||||
unsigned IORING_ENTER_GETEVENTS
|
||||
unsigned IORING_ENTER_SQ_WAKEUP
|
||||
unsigned IORING_ENTER_EXT_ARG
|
||||
|
||||
unsigned IORING_SQ_NEED_WAKEUP
|
||||
unsigned IORING_SQ_CQ_OVERFLOW
|
||||
|
||||
unsigned long long IORING_OFF_SQ_RING
|
||||
unsigned long long IORING_OFF_SQES
|
||||
|
||||
unsigned IORING_TIMEOUT_ABS
|
||||
|
||||
unsigned IOSQE_IO_LINK
|
||||
|
||||
enum Operation:
|
||||
IORING_OP_NOP
|
||||
IORING_OP_CONNECT
|
||||
IORING_OP_SEND
|
||||
IORING_OP_SENDMSG
|
||||
IORING_OP_RECV
|
||||
IORING_OP_RECVMSG
|
||||
IORING_OP_OPENAT
|
||||
IORING_OP_READ
|
||||
IORING_OP_CLOSE
|
||||
|
||||
struct io_sqring_offsets:
|
||||
__u32 head
|
||||
__u32 tail
|
||||
__u32 ring_mask
|
||||
__u32 ring_entries
|
||||
__u32 flags
|
||||
__u32 dropped
|
||||
__u32 array
|
||||
|
||||
struct io_cqring_offsets:
|
||||
__u32 head
|
||||
__u32 tail
|
||||
__u32 ring_mask
|
||||
__u32 ring_entries
|
||||
__u32 overflow
|
||||
__u32 cqes
|
||||
__u32 flags
|
||||
|
||||
struct io_uring_params:
|
||||
__u32 flags
|
||||
__u32 sq_thread_cpu
|
||||
__u32 sq_thread_idle
|
||||
|
||||
# written by the kernel:
|
||||
__u32 sq_entries
|
||||
__u32 cq_entries
|
||||
__u32 features
|
||||
__u32 resv[4]
|
||||
io_sqring_offsets sq_off
|
||||
io_cqring_offsets cq_off
|
||||
|
||||
struct io_uring_sqe:
|
||||
__u8 opcode # type of operation for this sqe
|
||||
__s32 fd # file descriptor to do IO on
|
||||
__u64 off # offset into file
|
||||
__u64 addr # pointer to buffer or iovecs
|
||||
__u32 len # buffer size or number of iovecs
|
||||
__u64 user_data # data to be passed back at completion time
|
||||
__u8 flags # IOSQE_ flags
|
||||
__u32 open_flags
|
||||
|
||||
struct io_uring_cqe:
|
||||
__u64 user_data # data to be passed back at completion time
|
||||
__s32 res # result code for this event
|
||||
|
||||
struct io_uring_getevents_arg:
|
||||
__u64 sigmask
|
||||
__u32 sigmask_sz
|
||||
__u64 ts
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2022 Fantix King http://fantix.pro
|
||||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
|
@ -7,4 +7,3 @@
|
|||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
94
src/kloop/includes/openssl/bio.pxd
Normal file
94
src/kloop/includes/openssl/bio.pxd
Normal file
|
@ -0,0 +1,94 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef extern from "openssl/bio.h" nogil:
|
||||
enum BIOCtrl:
|
||||
BIO_CTRL_RESET # 1 opt - rewind/zero etc
|
||||
BIO_CTRL_EOF # 2 opt - are we at the eof
|
||||
BIO_CTRL_INFO # 3 opt - extra tit-bits
|
||||
BIO_CTRL_SET # 4 man - set the 'IO' type
|
||||
BIO_CTRL_GET # 5 man - get the 'IO' type
|
||||
BIO_CTRL_PUSH # 6 opt - internal, used to signify change
|
||||
BIO_CTRL_POP # 7 opt - internal, used to signify change
|
||||
BIO_CTRL_GET_CLOSE # 8 man - set the 'close' on free
|
||||
BIO_CTRL_SET_CLOSE # 9 man - set the 'close' on free
|
||||
BIO_CTRL_PENDING # 10 opt - is their more data buffered
|
||||
BIO_CTRL_FLUSH # 11 opt - 'flush' buffered output
|
||||
BIO_CTRL_DUP # 12 man - extra stuff for 'duped' BIO
|
||||
BIO_CTRL_WPENDING # 13 opt - number of bytes still to write
|
||||
BIO_CTRL_SET_CALLBACK # 14 opt - set callback function
|
||||
BIO_CTRL_GET_CALLBACK # 15 opt - set callback function
|
||||
|
||||
ctypedef struct Method "BIO_METHOD":
|
||||
pass
|
||||
|
||||
ctypedef struct BIO:
|
||||
pass
|
||||
|
||||
int get_new_index "BIO_get_new_index" ()
|
||||
|
||||
Method* meth_new "BIO_meth_new" (int type, const char* name)
|
||||
|
||||
int meth_set_write_ex "BIO_meth_set_write_ex" (
|
||||
Method* biom,
|
||||
int (*bwrite)(BIO*, const char*, size_t, size_t*),
|
||||
)
|
||||
int meth_set_write "BIO_meth_set_write" (
|
||||
Method* biom,
|
||||
int (*write)(BIO*, const char*, int),
|
||||
)
|
||||
|
||||
int meth_set_read_ex "BIO_meth_set_read_ex" (
|
||||
Method* biom,
|
||||
int (*bread)(BIO*, char*, size_t, size_t*),
|
||||
)
|
||||
int meth_set_read "BIO_meth_set_read"(
|
||||
Method* biom,
|
||||
int (*read)(BIO*, char*, int),
|
||||
)
|
||||
|
||||
int meth_set_ctrl "BIO_meth_set_ctrl" (
|
||||
Method* biom, long (*ctrl)(BIO*, int, long, void*)
|
||||
)
|
||||
|
||||
int meth_set_create "BIO_meth_set_create" (
|
||||
Method* biom,
|
||||
int (*create)(BIO*),
|
||||
)
|
||||
|
||||
int meth_set_destroy "BIO_meth_set_destroy" (
|
||||
Method* biom,
|
||||
int (*destroy)(BIO*),
|
||||
)
|
||||
|
||||
ctypedef int info_cb "BIO_info_cb" (BIO*, int, int)
|
||||
int meth_set_callback_ctrl "BIO_meth_set_callback_ctrl" (
|
||||
Method* biom,
|
||||
long (*callback_ctrl)(BIO*, int, info_cb*),
|
||||
)
|
||||
|
||||
BIO* new "BIO_new" (const Method* type)
|
||||
int up_ref "BIO_up_ref" (BIO* a)
|
||||
int free "BIO_free" (BIO* a)
|
||||
|
||||
void set_data "BIO_set_data" (BIO* a, void* ptr)
|
||||
void* get_data "BIO_get_data" (BIO* a)
|
||||
void set_init "BIO_set_init" (BIO* a, int init)
|
||||
void set_shutdown "BIO_set_shutdown" (BIO* a, int shut)
|
||||
|
||||
void set_retry_read "BIO_set_retry_read" (BIO* b)
|
||||
void set_retry_write "BIO_set_retry_write" (BIO* b)
|
||||
void clear_retry_flags "BIO_clear_retry_flags" (BIO *b)
|
||||
|
||||
cdef int FLAGS_IN_EOF "BIO_FLAGS_IN_EOF"
|
||||
|
||||
int test_flags "BIO_test_flags" (BIO* b, int flags)
|
||||
void set_flags "BIO_set_flags" (BIO *b, int flags)
|
14
src/kloop/includes/openssl/err.pxd
Normal file
14
src/kloop/includes/openssl/err.pxd
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef extern from "openssl/err.h" nogil:
|
||||
unsigned long get_error "ERR_get_error" ()
|
||||
const char* reason_error_string "ERR_reason_error_string" (unsigned long e)
|
43
src/kloop/includes/openssl/ssl.pxd
Normal file
43
src/kloop/includes/openssl/ssl.pxd
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
from .. cimport linux
|
||||
|
||||
|
||||
cdef extern from "openssl/ssl.h" nogil:
|
||||
ctypedef struct SSL:
|
||||
pass
|
||||
|
||||
int SSL3_RT_APPLICATION_DATA
|
||||
int OP_ENABLE_KTLS "SSL_OP_ENABLE_KTLS"
|
||||
int set_options "SSL_set_options" (SSL* ssl, int options)
|
||||
|
||||
long SSL_MODE_RELEASE_BUFFERS
|
||||
long SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
|
||||
long clear_mode "SSL_clear_mode" (SSL* ssl, long mode)
|
||||
int free_buffers "SSL_free_buffers" (SSL *ssl)
|
||||
|
||||
|
||||
cdef extern from *:
|
||||
"""
|
||||
typedef struct {
|
||||
union {
|
||||
struct tls12_crypto_info_aes_gcm_128 gcm128;
|
||||
struct tls12_crypto_info_aes_gcm_256 gcm256;
|
||||
struct tls12_crypto_info_aes_ccm_128 ccm128;
|
||||
struct tls12_crypto_info_chacha20_poly1305 chacha20poly1305;
|
||||
};
|
||||
size_t tls_crypto_info_len;
|
||||
} ktls_crypto_info_t;
|
||||
"""
|
||||
|
||||
ctypedef struct ktls_crypto_info_t:
|
||||
size_t tls_crypto_info_len
|
34
src/kloop/includes/pyssl.pxd
Normal file
34
src/kloop/includes/pyssl.pxd
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
from .openssl.ssl cimport SSL
|
||||
from .openssl.bio cimport BIO
|
||||
|
||||
cdef extern from *:
|
||||
"""
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
PyObject *Socket; /* weakref to socket on which we're layered */
|
||||
SSL *ssl;
|
||||
} PySSLSocket;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
BIO *bio;
|
||||
int eof_written;
|
||||
} PySSLMemoryBIO;
|
||||
"""
|
||||
|
||||
ctypedef struct PySSLSocket:
|
||||
SSL* ssl
|
||||
|
||||
ctypedef struct PySSLMemoryBIO:
|
||||
BIO* bio
|
56
src/kloop/loop.pxd
Normal file
56
src/kloop/loop.pxd
Normal file
|
@ -0,0 +1,56 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
from cpython cimport PyErr_SetFromErrno
|
||||
from cpython cimport PyMem_RawMalloc, PyMem_RawFree, PyMem_RawRealloc
|
||||
from cpython cimport PyObject, Py_INCREF, Py_DECREF
|
||||
from libc cimport errno, string
|
||||
from posix cimport mman, unistd, time
|
||||
from posix.types cimport mode_t
|
||||
|
||||
from .includes cimport atomic, libc, linux
|
||||
|
||||
include "./handle.pxd"
|
||||
include "./queue.pxd"
|
||||
include "./heapq.pxd"
|
||||
include "./uring.pxd"
|
||||
include "./tcp.pxd"
|
||||
include "./udp.pxd"
|
||||
include "./fileio.pxd"
|
||||
include "./resolver.pxd"
|
||||
|
||||
|
||||
cdef struct Loop:
|
||||
bint stopping
|
||||
Ring ring
|
||||
HeapQueue scheduled
|
||||
Queue ready
|
||||
int timer_cancelled_count
|
||||
PyObject* loop
|
||||
CResolver resolver
|
||||
|
||||
|
||||
cdef class KLoopImpl:
|
||||
cdef:
|
||||
bint closed
|
||||
object thread_id
|
||||
Loop loop
|
||||
Resolver resolver
|
||||
|
||||
cpdef create_future(self)
|
||||
cdef inline check_closed(self)
|
||||
cdef inline bint _is_running(self)
|
||||
cdef inline check_running(self)
|
||||
cdef inline Handle _call_soon(self, callback, args, context)
|
||||
cdef inline _add_callback(self, Handle handle)
|
||||
cdef inline TimerHandle _call_at(
|
||||
self, long long when, callback, args, context
|
||||
)
|
560
src/kloop/loop.pyx
Normal file
560
src/kloop/loop.pyx
Normal file
|
@ -0,0 +1,560 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
import time as py_time
|
||||
import asyncio
|
||||
import contextvars
|
||||
import functools
|
||||
import inspect
|
||||
import os
|
||||
import reprlib
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
cdef asyncio_isfuture = asyncio.isfuture
|
||||
cdef asyncio_ensure_future = asyncio.ensure_future
|
||||
cdef asyncio_set_running_loop = asyncio._set_running_loop
|
||||
cdef asyncio_get_running_loop = asyncio._get_running_loop
|
||||
cdef asyncio_Task = asyncio.Task
|
||||
cdef asyncio_Future = asyncio.Future
|
||||
cdef logger = asyncio.log.logger
|
||||
|
||||
cdef long long SECOND_NS = 1_000_000_000
|
||||
cdef long long MAX_SELECT_TIMEOUT = 24 * 3600 * SECOND_NS
|
||||
|
||||
# Minimum number of scheduled timer handles before cleanup of
|
||||
# cancelled handles is performed.
|
||||
cdef int MIN_SCHEDULED_TIMER_HANDLES = 100
|
||||
|
||||
# Maximum ratio of cancelled handles is performed of scheduled timer handles
|
||||
# that are cancelled before cleanup
|
||||
cdef int MAX_CANCELLED_TIMER_HANDLES_RATIO = 2
|
||||
|
||||
include "handle.pyx"
|
||||
include "queue.pyx"
|
||||
include "heapq.pyx"
|
||||
include "uring.pyx"
|
||||
include "tcp.pyx"
|
||||
include "udp.pyx"
|
||||
include "fileio.pyx"
|
||||
include "resolver.pyx"
|
||||
|
||||
|
||||
cdef long long monotonic_ns() nogil except -1:
|
||||
cdef:
|
||||
long long rv
|
||||
time.timespec ts
|
||||
if time.clock_gettime(time.CLOCK_MONOTONIC, &ts):
|
||||
with gil:
|
||||
PyErr_SetFromErrno(OSError)
|
||||
return -1
|
||||
rv = ts.tv_sec * SECOND_NS
|
||||
return rv + ts.tv_nsec
|
||||
|
||||
|
||||
cdef int loop_init(
|
||||
Loop* loop, linux.__u32 depth, linux.io_uring_params* params
|
||||
) nogil except 0:
|
||||
if not queue_init(&loop.ready):
|
||||
return 0
|
||||
if not heapq_init(&loop.scheduled):
|
||||
queue_uninit(&loop.ready)
|
||||
return 0
|
||||
if not ring_init(&loop.ring, depth, params):
|
||||
queue_uninit(&loop.ready)
|
||||
heapq_uninit(&loop.scheduled)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
cdef int loop_uninit(Loop* loop) nogil except 0:
|
||||
heapq_uninit(&loop.scheduled)
|
||||
queue_uninit(&loop.ready)
|
||||
return ring_uninit(&loop.ring)
|
||||
|
||||
|
||||
cdef int loop_run_forever(Loop* loop) nogil except 0:
|
||||
cdef:
|
||||
Ring* ring = &loop.ring
|
||||
Queue* ready = &loop.ready
|
||||
HeapQueue* scheduled = &loop.scheduled
|
||||
|
||||
while True:
|
||||
if not loop_run_once(loop, ring, ready, scheduled):
|
||||
return 0
|
||||
if loop.stopping:
|
||||
break
|
||||
return 1
|
||||
|
||||
|
||||
cdef inline int filter_cancelled_calls(Loop* loop) nogil except 0:
|
||||
cdef:
|
||||
HeapQueue* scheduled = &loop.scheduled
|
||||
HeapQueue heap, drop
|
||||
Callback** array = scheduled.array
|
||||
Callback* callback
|
||||
int i = 0, size = scheduled.tail
|
||||
|
||||
if (
|
||||
MIN_SCHEDULED_TIMER_HANDLES < size <
|
||||
loop.timer_cancelled_count * MAX_CANCELLED_TIMER_HANDLES_RATIO
|
||||
):
|
||||
# Remove delayed calls that were cancelled if their number
|
||||
# is too high
|
||||
if not heapq_init(&drop):
|
||||
return 0
|
||||
if not heapq_init(&heap):
|
||||
heapq_uninit(&drop)
|
||||
return 0
|
||||
while i < size:
|
||||
callback = array[i]
|
||||
if callback.mask & CANCELLED_MASK:
|
||||
callback.mask &= ~SCHEDULED_MASK
|
||||
if not heapq_push(&drop, callback, 0):
|
||||
heap.tail = 0
|
||||
heapq_uninit(&heap)
|
||||
drop.tail = 0
|
||||
heapq_uninit(&drop)
|
||||
return 0
|
||||
elif not heapq_push(&heap, callback, 0):
|
||||
heap.tail = 0
|
||||
heapq_uninit(&heap)
|
||||
drop.tail = 0
|
||||
heapq_uninit(&drop)
|
||||
return 0
|
||||
heapq_heapify(&heap)
|
||||
heap, scheduled[0] = scheduled[0], heap
|
||||
heap.tail = 0
|
||||
heapq_uninit(&heap)
|
||||
heapq_uninit(&drop)
|
||||
elif array[0].mask & CANCELLED_MASK:
|
||||
if not heapq_init(&drop):
|
||||
return 0
|
||||
while size:
|
||||
callback = heapq_pop(scheduled)
|
||||
if callback.mask & CANCELLED_MASK:
|
||||
loop.timer_cancelled_count -= 1
|
||||
callback.mask &= ~SCHEDULED_MASK
|
||||
if not heapq_push(&drop, callback, 0):
|
||||
with gil:
|
||||
Py_DECREF(<object>callback.handle)
|
||||
heapq_uninit(&drop)
|
||||
return 0
|
||||
if not array[0].mask & CANCELLED_MASK:
|
||||
break
|
||||
size -= 1
|
||||
heapq_uninit(&drop)
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
cdef loop_run_ready(Queue* ready, int ntodo):
|
||||
cdef Handle handle
|
||||
|
||||
while ntodo:
|
||||
handle = queue_pop_py(ready)
|
||||
if not handle.cb.mask & CANCELLED_MASK:
|
||||
handle.run()
|
||||
ntodo -= 1
|
||||
handle = None
|
||||
|
||||
|
||||
cdef inline int loop_run_once(
|
||||
Loop* loop, Ring* ring, Queue* ready, HeapQueue* scheduled
|
||||
) nogil except 0:
|
||||
cdef:
|
||||
Callback* callback
|
||||
long long timeout = -1, now
|
||||
int nready
|
||||
RingCallback* cb = NULL
|
||||
|
||||
if scheduled.tail:
|
||||
if not filter_cancelled_calls(loop):
|
||||
return 0
|
||||
|
||||
if ready.head >= 0 or loop.stopping:
|
||||
timeout = 0
|
||||
elif scheduled.tail:
|
||||
timeout = min(
|
||||
max(0, scheduled.array[0].when - monotonic_ns()),
|
||||
MAX_SELECT_TIMEOUT,
|
||||
)
|
||||
|
||||
nready = ring_select(ring, timeout)
|
||||
if nready < 0:
|
||||
return 0
|
||||
while nready:
|
||||
ring_cq_pop(&ring.cq, &cb)
|
||||
if cb != NULL and not cb.callback(cb):
|
||||
return 0
|
||||
nready -= 1
|
||||
|
||||
now = monotonic_ns() + 1
|
||||
while scheduled.tail and scheduled.array[0].when < now:
|
||||
callback = heapq_pop(scheduled)
|
||||
callback.mask &= ~SCHEDULED_MASK
|
||||
if not queue_push(ready, callback):
|
||||
if not heapq_push(scheduled, callback, 1):
|
||||
with gil:
|
||||
Py_DECREF(<object>callback.handle)
|
||||
return 0
|
||||
|
||||
if ready.head >= 0:
|
||||
with gil:
|
||||
loop_run_ready(ready, queue_size(ready))
|
||||
return 1
|
||||
|
||||
|
||||
class KLoopPolicy(asyncio.events.BaseDefaultEventLoopPolicy):
|
||||
__slots__ = ("_selector_args",)
|
||||
|
||||
def __init__(
|
||||
self, queue_depth=128, sq_thread_idle=2000, sq_thread_cpu=None
|
||||
):
|
||||
super().__init__()
|
||||
self._selector_args = (queue_depth, sq_thread_idle, sq_thread_cpu)
|
||||
|
||||
def _loop_factory(self):
|
||||
return KLoop(*self._selector_args)
|
||||
|
||||
# Child processes handling (Unix only).
|
||||
|
||||
def get_child_watcher(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_child_watcher(self, watcher):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
cdef class KLoopImpl:
|
||||
def __init__(self, queue_depth, sq_thread_idle, sq_thread_cpu):
|
||||
cdef:
|
||||
linux.io_uring_params params
|
||||
linux.__u32 depth
|
||||
string.memset(¶ms, 0, sizeof(params))
|
||||
params.flags = linux.IORING_SETUP_SQPOLL
|
||||
params.sq_thread_idle = sq_thread_idle
|
||||
if sq_thread_cpu is not None:
|
||||
params.sq_thread_cpu = sq_thread_cpu
|
||||
params.flags |= linux.IORING_SETUP_SQ_AFF
|
||||
depth = queue_depth
|
||||
self.loop.loop = <PyObject*>self
|
||||
with nogil:
|
||||
loop_init(&self.loop, depth, ¶ms)
|
||||
|
||||
self.resolver = Resolver.new(self)
|
||||
self.closed = False
|
||||
self.thread_id = None
|
||||
|
||||
def __dealloc__(self):
|
||||
with nogil:
|
||||
loop_uninit(&self.loop)
|
||||
|
||||
cdef inline check_closed(self):
|
||||
if self.closed:
|
||||
raise RuntimeError('Event loop is closed')
|
||||
|
||||
cdef inline bint _is_running(self):
|
||||
return self.thread_id is not None
|
||||
|
||||
cdef inline check_running(self):
|
||||
if self._is_running():
|
||||
raise RuntimeError('This event loop is already running')
|
||||
if asyncio_get_running_loop() is not None:
|
||||
raise RuntimeError(
|
||||
'Cannot run the event loop while another loop is running')
|
||||
|
||||
def run_forever(self):
|
||||
"""Run until stop() is called."""
|
||||
self.check_closed()
|
||||
self.check_running()
|
||||
# self._set_coroutine_origin_tracking(self._debug)
|
||||
self.thread_id = threading.get_ident()
|
||||
|
||||
# old_agen_hooks = sys.get_asyncgen_hooks()
|
||||
# sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook,
|
||||
# finalizer=self._asyncgen_finalizer_hook)
|
||||
try:
|
||||
asyncio_set_running_loop(self)
|
||||
with nogil:
|
||||
loop_run_forever(&self.loop)
|
||||
finally:
|
||||
self.loop.stopping = 0
|
||||
self.thread_id = None
|
||||
asyncio_set_running_loop(None)
|
||||
# self._set_coroutine_origin_tracking(False)
|
||||
# sys.set_asyncgen_hooks(*old_agen_hooks)
|
||||
|
||||
def run_until_complete(self, future):
|
||||
self.check_closed()
|
||||
self.check_running()
|
||||
|
||||
new_task = not asyncio_isfuture(future)
|
||||
future = asyncio_ensure_future(future, loop=self)
|
||||
if new_task:
|
||||
# An exception is raised if the future didn't complete, so there
|
||||
# is no need to log the "destroy pending task" message
|
||||
future._log_destroy_pending = False
|
||||
|
||||
future.add_done_callback(_run_until_complete_cb)
|
||||
try:
|
||||
self.run_forever()
|
||||
except:
|
||||
if new_task and future.done() and not future.cancelled():
|
||||
# The coroutine raised a BaseException. Consume the exception
|
||||
# to not log a warning, the caller doesn't have access to the
|
||||
# local task.
|
||||
future.exception()
|
||||
raise
|
||||
finally:
|
||||
future.remove_done_callback(_run_until_complete_cb)
|
||||
if not future.done():
|
||||
raise RuntimeError('Event loop stopped before Future completed.')
|
||||
|
||||
return future.result()
|
||||
|
||||
def create_task(self, coro, *, name=None):
|
||||
self.check_closed()
|
||||
# if self._task_factory is None:
|
||||
task = asyncio_Task(coro, loop=self, name=name)
|
||||
if task._source_traceback:
|
||||
del task._source_traceback[-1]
|
||||
# else:
|
||||
# task = self._task_factory(self, coro)
|
||||
# tasks._set_task_name(task, name)
|
||||
|
||||
return task
|
||||
|
||||
def stop(self):
|
||||
self.loop.stopping = 1
|
||||
|
||||
def close(self):
|
||||
if self.is_running():
|
||||
raise RuntimeError("Cannot close a running event loop")
|
||||
if self.closed:
|
||||
return
|
||||
# if self._debug:
|
||||
# logger.debug("Close %r", self)
|
||||
self.closed = True
|
||||
# self.ready.clear()
|
||||
# self._scheduled.clear()
|
||||
# self._executor_shutdown_called = True
|
||||
# executor = self._default_executor
|
||||
# if executor is not None:
|
||||
# self._default_executor = None
|
||||
# executor.shutdown(wait=False)
|
||||
|
||||
def fileno(self):
|
||||
return self.loop.ring.ring_fd
|
||||
|
||||
def is_running(self):
|
||||
return self._is_running()
|
||||
|
||||
def get_debug(self):
|
||||
return False
|
||||
|
||||
def call_soon(self, callback, *args, context=None):
|
||||
cdef Handle handle
|
||||
self.check_closed()
|
||||
# if self._debug:
|
||||
# self._check_thread()
|
||||
# self._check_callback(callback, 'call_soon')
|
||||
handle = self._call_soon(callback, args, context)
|
||||
if handle.source_traceback:
|
||||
del handle.source_traceback[-1]
|
||||
return handle
|
||||
|
||||
def time(self):
|
||||
return (<float>monotonic_ns()) / SECOND_NS
|
||||
|
||||
def call_later(self, delay, callback, *args, context=None):
|
||||
cdef long long when = monotonic_ns()
|
||||
when += delay * SECOND_NS
|
||||
timer = self._call_at(when, callback, args, context)
|
||||
if timer.source_traceback:
|
||||
del timer.source_traceback[-1]
|
||||
return timer
|
||||
|
||||
def call_at(self, when, callback, *args, context=None):
|
||||
timer = self._call_at(when * SECOND_NS, callback, args, context)
|
||||
if timer.source_traceback:
|
||||
del timer.source_traceback[-1]
|
||||
return timer
|
||||
|
||||
cdef inline TimerHandle _call_at(
|
||||
self, long long when, callback, args, context
|
||||
):
|
||||
cdef TimerHandle timer
|
||||
self.check_closed()
|
||||
# if self._debug:
|
||||
# self._check_thread()
|
||||
# self._check_callback(callback, 'call_at')
|
||||
timer = TimerHandle(when, callback, args, self, context)
|
||||
heapq_push_py(&self.loop.scheduled, timer)
|
||||
# else:
|
||||
# heapq_heappush(self.heapq)
|
||||
timer.cb.mask |= SCHEDULED_MASK
|
||||
return timer
|
||||
|
||||
cdef inline Handle _call_soon(self, callback, args, context):
|
||||
cdef Handle handle = Handle(callback, args, self, context)
|
||||
self._add_callback(handle)
|
||||
return handle
|
||||
|
||||
cdef inline _add_callback(self, Handle handle):
|
||||
queue_push_py(&self.loop.ready, handle)
|
||||
|
||||
def default_exception_handler(self, context):
|
||||
message = context.get('message')
|
||||
if not message:
|
||||
message = 'Unhandled exception in event loop'
|
||||
|
||||
exception = context.get('exception')
|
||||
if exception is not None:
|
||||
exc_info = (type(exception), exception, exception.__traceback__)
|
||||
else:
|
||||
exc_info = False
|
||||
|
||||
# if ('source_traceback' not in context and
|
||||
# self._current_handle is not None and
|
||||
# self._current_handle._source_traceback):
|
||||
# context['handle_traceback'] = \
|
||||
# self._current_handle._source_traceback
|
||||
|
||||
log_lines = [message]
|
||||
for key in sorted(context):
|
||||
if key in {'message', 'exception'}:
|
||||
continue
|
||||
value = context[key]
|
||||
if key == 'source_traceback':
|
||||
tb = ''.join(traceback.format_list(value))
|
||||
value = 'Object created at (most recent call last):\n'
|
||||
value += tb.rstrip()
|
||||
elif key == 'handle_traceback':
|
||||
tb = ''.join(traceback.format_list(value))
|
||||
value = 'Handle created at (most recent call last):\n'
|
||||
value += tb.rstrip()
|
||||
else:
|
||||
value = repr(value)
|
||||
log_lines.append(f'{key}: {value}')
|
||||
|
||||
logger.error('\n'.join(log_lines), exc_info=exc_info)
|
||||
|
||||
def call_exception_handler(self, context):
|
||||
# if self._exception_handler is None:
|
||||
try:
|
||||
self.default_exception_handler(context)
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except BaseException:
|
||||
# Second protection layer for unexpected errors
|
||||
# in the default implementation, as well as for subclassed
|
||||
# event loops with overloaded "default_exception_handler".
|
||||
logger.error('Exception in default exception handler',
|
||||
exc_info=True)
|
||||
# else:
|
||||
# try:
|
||||
# self._exception_handler(self, context)
|
||||
# except (SystemExit, KeyboardInterrupt):
|
||||
# raise
|
||||
# except BaseException as exc:
|
||||
# # Exception in the user set custom exception handler.
|
||||
# try:
|
||||
# # Let's try default handler.
|
||||
# self.default_exception_handler({
|
||||
# 'message': 'Unhandled error in exception handler',
|
||||
# 'exception': exc,
|
||||
# 'context': context,
|
||||
# })
|
||||
# except (SystemExit, KeyboardInterrupt):
|
||||
# raise
|
||||
# except BaseException:
|
||||
# # Guard 'default_exception_handler' in case it is
|
||||
# # overloaded.
|
||||
# logger.error('Exception in default exception handler '
|
||||
# 'while handling an unexpected error '
|
||||
# 'in custom exception handler',
|
||||
# exc_info=True)
|
||||
|
||||
async def shutdown_asyncgens(self):
|
||||
pass
|
||||
|
||||
async def shutdown_default_executor(self):
|
||||
pass
|
||||
|
||||
cpdef create_future(self):
|
||||
return asyncio_Future(loop=self)
|
||||
|
||||
def _timer_handle_cancelled(self, handle):
|
||||
pass
|
||||
|
||||
async def create_connection(
|
||||
self,
|
||||
protocol_factory,
|
||||
host=None,
|
||||
port=None,
|
||||
*,
|
||||
ssl=None,
|
||||
family=0,
|
||||
proto=0,
|
||||
flags=0,
|
||||
sock=None,
|
||||
local_addr=None,
|
||||
server_hostname=None,
|
||||
ssl_handshake_timeout=None,
|
||||
happy_eyeballs_delay=None,
|
||||
interleave=None,
|
||||
):
|
||||
cdef:
|
||||
int fd
|
||||
|
||||
if ssl is False:
|
||||
ssl = None
|
||||
elif ssl is not None:
|
||||
from . import tls
|
||||
if ssl is True:
|
||||
import ssl as ssl_mod
|
||||
ssl = ssl_mod.create_default_context()
|
||||
fd = await tcp_connect(self, host, port)
|
||||
protocol = protocol_factory()
|
||||
if ssl is not None:
|
||||
waiter = self.create_future()
|
||||
transport = tls.TLSTransport.new(fd, protocol, self, ssl, waiter=waiter)
|
||||
await waiter
|
||||
else:
|
||||
transport = TCPTransport.new(fd, protocol, self)
|
||||
return transport, protocol
|
||||
|
||||
|
||||
class KLoop(KLoopImpl, asyncio.AbstractEventLoop):
|
||||
pass
|
||||
|
||||
|
||||
def _run_until_complete_cb(fut):
|
||||
if not fut.cancelled():
|
||||
exc = fut.exception()
|
||||
if isinstance(exc, (SystemExit, KeyboardInterrupt)):
|
||||
# Issue #22429: run_forever() already finished, no need to
|
||||
# stop it.
|
||||
return
|
||||
_get_loop(fut).stop()
|
||||
|
||||
|
||||
def _get_loop(fut):
|
||||
# Tries to call Future.get_loop() if it's available.
|
||||
# Otherwise fallbacks to using the old '_loop' property.
|
||||
try:
|
||||
get_loop = fut.get_loop
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
return get_loop()
|
||||
return fut._loop
|
16
src/kloop/queue.pxd
Normal file
16
src/kloop/queue.pxd
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef struct Queue:
|
||||
Callback** array
|
||||
int size
|
||||
int head
|
||||
int tail
|
165
src/kloop/queue.pyx
Normal file
165
src/kloop/queue.pyx
Normal file
|
@ -0,0 +1,165 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef int QUEUE_BLOCK_SIZE = 1024
|
||||
|
||||
|
||||
cdef int queue_init(Queue* queue) nogil except 0:
|
||||
queue.array = <Callback**>PyMem_RawMalloc(
|
||||
sizeof(Callback*) * QUEUE_BLOCK_SIZE
|
||||
)
|
||||
if queue.array == NULL:
|
||||
with gil:
|
||||
raise MemoryError
|
||||
queue.head = -1
|
||||
queue.tail = 0
|
||||
queue.size = QUEUE_BLOCK_SIZE
|
||||
return 1
|
||||
|
||||
|
||||
cdef void queue_uninit(Queue* queue) nogil:
|
||||
cdef:
|
||||
int i = queue.head, size = queue.size, tail = queue.tail
|
||||
Callback** array = queue.array
|
||||
|
||||
if array == NULL:
|
||||
return
|
||||
if i >= 0:
|
||||
with gil:
|
||||
while True:
|
||||
Py_DECREF(<object>array[i].handle)
|
||||
i = (i + 1) % size
|
||||
if i == tail:
|
||||
break
|
||||
PyMem_RawFree(array)
|
||||
queue.array = NULL
|
||||
|
||||
|
||||
cdef queue_push_py(Queue* queue, Handle handle):
|
||||
cdef Callback* callback = &handle.cb
|
||||
Py_INCREF(handle)
|
||||
with nogil:
|
||||
queue_push(queue, callback)
|
||||
|
||||
|
||||
cdef int queue_push(Queue* queue, Callback* callback) nogil except 0:
|
||||
cdef:
|
||||
Callback** orig = queue.array
|
||||
Callback** array = orig
|
||||
int size = queue.size, head = queue.head, tail = queue.tail
|
||||
|
||||
if head == tail:
|
||||
if head == 0:
|
||||
tail = size
|
||||
size += QUEUE_BLOCK_SIZE
|
||||
array = <Callback**>PyMem_RawRealloc(
|
||||
orig, sizeof(Callback*) * size
|
||||
)
|
||||
if array == NULL:
|
||||
with gil:
|
||||
raise MemoryError
|
||||
else:
|
||||
tail = size + QUEUE_BLOCK_SIZE
|
||||
array = <Callback**>PyMem_RawMalloc(sizeof(Callback*) * tail)
|
||||
if array == NULL:
|
||||
with gil:
|
||||
raise MemoryError
|
||||
queue.array = array
|
||||
string.memcpy(
|
||||
array, orig + head, sizeof(Callback*) * (size - head)
|
||||
)
|
||||
string.memcpy(array + size - head, orig, sizeof(Callback*) * head)
|
||||
size, tail = tail, size
|
||||
queue.head = head = 0
|
||||
PyMem_RawFree(orig)
|
||||
queue.size = size
|
||||
elif head < 0:
|
||||
queue.head = head = 0
|
||||
array[tail] = callback
|
||||
queue.tail = (tail + 1) % size
|
||||
return 1
|
||||
|
||||
|
||||
cdef Handle queue_pop_py(Queue* queue):
|
||||
cdef:
|
||||
Handle handle
|
||||
Callback* callback
|
||||
|
||||
with nogil:
|
||||
callback = queue_pop(queue)
|
||||
if callback == NULL:
|
||||
return None
|
||||
else:
|
||||
handle = <Handle>callback.handle
|
||||
Py_DECREF(handle)
|
||||
return handle
|
||||
|
||||
|
||||
cdef Callback* queue_pop(Queue* queue) nogil:
|
||||
cdef:
|
||||
int size = queue.size, head = queue.head, tail = queue.tail
|
||||
Callback* rv
|
||||
Callback** orig = queue.array
|
||||
Callback** array = orig
|
||||
|
||||
if head < 0:
|
||||
return NULL
|
||||
rv = array[head]
|
||||
queue.head = head = (head + 1) % size
|
||||
if head == tail:
|
||||
queue.head = -1
|
||||
queue.tail = 0
|
||||
if size > QUEUE_BLOCK_SIZE:
|
||||
size = QUEUE_BLOCK_SIZE
|
||||
if PyMem_RawRealloc(
|
||||
array, sizeof(Callback*) * size
|
||||
) != NULL:
|
||||
queue.size = size
|
||||
elif (head - tail) % size >= QUEUE_BLOCK_SIZE * 2:
|
||||
if head < tail:
|
||||
size -= QUEUE_BLOCK_SIZE
|
||||
if tail > size:
|
||||
tail -= head
|
||||
string.memmove(array, array + head, sizeof(Callback*) * tail)
|
||||
queue.tail = tail
|
||||
queue.head = 0
|
||||
if PyMem_RawRealloc(
|
||||
array, sizeof(Callback*) * size
|
||||
) != NULL:
|
||||
queue.size = size
|
||||
queue.tail = tail % size
|
||||
else:
|
||||
array = <Callback**>PyMem_RawMalloc(
|
||||
sizeof(Callback*) * (size - QUEUE_BLOCK_SIZE)
|
||||
)
|
||||
if array != NULL:
|
||||
string.memcpy(
|
||||
array, orig + head, sizeof(Callback*) * (size - head)
|
||||
)
|
||||
string.memcpy(
|
||||
array + size - head, orig, sizeof(Callback*) * tail
|
||||
)
|
||||
queue.array = array
|
||||
queue.head = 0
|
||||
queue.tail = (tail - head) % size
|
||||
queue.size = size - QUEUE_BLOCK_SIZE
|
||||
PyMem_RawFree(orig)
|
||||
return rv
|
||||
|
||||
|
||||
cdef int queue_size(Queue* queue) nogil:
|
||||
cdef int size = queue.size, head = queue.head, tail = queue.tail
|
||||
if head < 0:
|
||||
return 0
|
||||
elif head == tail:
|
||||
return size
|
||||
else:
|
||||
return (tail - head) % size
|
75
src/kloop/resolver.pxd
Normal file
75
src/kloop/resolver.pxd
Normal file
|
@ -0,0 +1,75 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef extern from * nogil:
|
||||
int resolver_init(
|
||||
CResolver* resolver,
|
||||
char* resolv_conf_data,
|
||||
size_t resolv_conf_data_size,
|
||||
char* hosts_conf_data,
|
||||
size_t hosts_conf_data_size,
|
||||
)
|
||||
int resolver_lookup_ip(
|
||||
void* resolver,
|
||||
void* resolve,
|
||||
char* host,
|
||||
size_t length,
|
||||
libc.in_port_t port,
|
||||
)
|
||||
void resolver_run_until_stalled(void* rust_resolver)
|
||||
void waker_wake(void* waker)
|
||||
void waker_forget(void* waker)
|
||||
|
||||
|
||||
cdef struct CResolver:
|
||||
Loop* loop
|
||||
Callback* cb
|
||||
FileReader resolv_conf
|
||||
FileReader hosts_conf
|
||||
int res
|
||||
void* rust_resolver
|
||||
|
||||
|
||||
cdef class Resolver:
|
||||
cdef:
|
||||
CResolver resolver
|
||||
KLoopImpl loop
|
||||
Handle handle
|
||||
object waiter
|
||||
bint initialized
|
||||
|
||||
@staticmethod
|
||||
cdef Resolver new(KLoopImpl loop)
|
||||
cdef init_cb(self)
|
||||
cdef err_cb(self, exc)
|
||||
|
||||
|
||||
cdef struct CResolve:
|
||||
CResolver* resolver
|
||||
libc.sockaddr* result
|
||||
size_t result_len, result_size
|
||||
Callback* cb
|
||||
int res
|
||||
char* host
|
||||
size_t host_len
|
||||
libc.in_port_t port
|
||||
|
||||
|
||||
cdef class Resolve:
|
||||
cdef:
|
||||
CResolve r
|
||||
Handle handle
|
||||
object waiter
|
||||
object host
|
||||
|
||||
@staticmethod
|
||||
cdef new(Resolver resolver, host, port)
|
||||
cdef resolve_cb(self)
|
179
src/kloop/resolver.pyx
Normal file
179
src/kloop/resolver.pyx
Normal file
|
@ -0,0 +1,179 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef const char* RESOLV_CONF = "/etc/resolv.conf"
|
||||
cdef const char* HOSTS_CONF = "/etc/hosts"
|
||||
cdef size_t SOCKADDR_CHUNK_SIZE = 4
|
||||
|
||||
|
||||
cdef int resolve_cb(RingCallback* cb) nogil except 0:
|
||||
cdef:
|
||||
CResolver* r = <CResolver*>cb.data
|
||||
int rv = 1
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
cdef int resolver_read_file_cb(RingCallback* cb) nogil except 0:
|
||||
cdef:
|
||||
CResolver* r = <CResolver*>cb.data
|
||||
int rv = 1
|
||||
void* ptr
|
||||
|
||||
if r.hosts_conf.done_cb.res == 0 or r.resolv_conf.done_cb.res == 0:
|
||||
if cb.res < 0:
|
||||
r.resolv_conf.cancelled = r.hosts_conf.cancelled = 1
|
||||
else:
|
||||
r.res = min(r.hosts_conf.done_cb.res, r.resolv_conf.done_cb.res)
|
||||
if r.res > 0:
|
||||
r.res = resolver_init(
|
||||
r,
|
||||
r.resolv_conf.data,
|
||||
r.resolv_conf.offset,
|
||||
r.hosts_conf.data,
|
||||
r.hosts_conf.offset,
|
||||
)
|
||||
rv = queue_push(&r.loop.ready, r.cb)
|
||||
if not file_reader_done(&r.resolv_conf):
|
||||
# TODO: fd not closed?
|
||||
pass
|
||||
if not file_reader_done(&r.hosts_conf):
|
||||
# TODO: fd not closed?
|
||||
pass
|
||||
return rv
|
||||
|
||||
|
||||
cdef extern libc.sockaddr* resolve_prep_addr(CResolve* r) nogil:
|
||||
cdef size_t l = r.result_len, size = r.result_size
|
||||
if l == size:
|
||||
size += SOCKADDR_CHUNK_SIZE
|
||||
if PyMem_RawRealloc(r.result, sizeof(libc.sockaddr) * size) == NULL:
|
||||
return NULL
|
||||
r.result_size = size
|
||||
r.result_len = l + 1
|
||||
return r.result + l
|
||||
|
||||
|
||||
cdef extern int resolve_done_cb(CResolve* r) nogil:
|
||||
return queue_push(&r.resolver.loop.ready, r.cb)
|
||||
|
||||
|
||||
cdef extern void resolver_set(CResolver* resolver, void* rust_resolver) nogil:
|
||||
resolver.rust_resolver = rust_resolver
|
||||
|
||||
|
||||
cdef class Resolver:
|
||||
@staticmethod
|
||||
cdef Resolver new(KLoopImpl loop):
|
||||
cdef:
|
||||
Resolver rv = Resolver.__new__(Resolver)
|
||||
CResolver* r = &rv.resolver
|
||||
rv.loop = loop
|
||||
r.loop = &loop.loop
|
||||
r.resolv_conf.done_cb.callback = resolver_read_file_cb
|
||||
r.resolv_conf.done_cb.data = r
|
||||
r.hosts_conf.done_cb.callback = resolver_read_file_cb
|
||||
r.hosts_conf.done_cb.data = r
|
||||
return rv
|
||||
|
||||
async def ensure_initialized(self):
|
||||
cdef CResolver* r
|
||||
|
||||
if self.initialized:
|
||||
return
|
||||
waiter = self.waiter
|
||||
if waiter is None:
|
||||
r = &self.resolver
|
||||
waiter = self.waiter = self.loop.create_future()
|
||||
if not file_reader_start(&r.resolv_conf, r.loop, RESOLV_CONF):
|
||||
self.err_cb(ValueError("Submission queue is full!"))
|
||||
elif not file_reader_start(&r.hosts_conf, r.loop, HOSTS_CONF):
|
||||
r.resolv_conf.cancelled = 1
|
||||
self.err_cb(ValueError("Submission queue is full!"))
|
||||
else:
|
||||
self.handle = Handle(self.init_cb, (self,), self.loop, None)
|
||||
r.cb = &self.handle.cb
|
||||
await waiter
|
||||
|
||||
async def reload_config(self, *, force=False):
|
||||
if self.initialized:
|
||||
waiter = self.waiter
|
||||
if waiter is None:
|
||||
waiter = self.waiter = self.loop.create_future()
|
||||
self.err_cb(NotImplementedError())
|
||||
await waiter
|
||||
else:
|
||||
await self.ensure_initialized()
|
||||
|
||||
cdef init_cb(self):
|
||||
cdef int res = self.resolver.res
|
||||
|
||||
if self.waiter.done():
|
||||
return
|
||||
|
||||
if res < 0:
|
||||
try:
|
||||
errno.errno = -res
|
||||
PyErr_SetFromErrno(IOError)
|
||||
except IOError as e:
|
||||
self.waiter.set_exception(e)
|
||||
else:
|
||||
self.waiter.set_result(None)
|
||||
|
||||
cdef err_cb(self, exc):
|
||||
waiter, self.waiter = self.waiter, None
|
||||
if waiter is not None:
|
||||
if not waiter.done():
|
||||
waiter.set_exception(exc)
|
||||
|
||||
async def lookup_ip(self, host, port):
|
||||
await self.ensure_initialized()
|
||||
return await Resolve.new(self, host, port)
|
||||
|
||||
|
||||
cdef class Resolve:
|
||||
@staticmethod
|
||||
cdef new(Resolver resolver, host, port):
|
||||
cdef:
|
||||
Resolve rv = Resolve.__new__(Resolve)
|
||||
CResolve* r = &rv.r
|
||||
rv.host = host.encode("utf-8")
|
||||
rv.waiter = resolver.loop.create_future()
|
||||
r.resolver = &resolver.resolver
|
||||
r.host = <char*>rv.host
|
||||
r.host_len = len(rv.host)
|
||||
r.port = port
|
||||
r.result = <libc.sockaddr*>PyMem_RawMalloc(
|
||||
sizeof(libc.sockaddr) * SOCKADDR_CHUNK_SIZE
|
||||
)
|
||||
if r.result == NULL:
|
||||
raise MemoryError
|
||||
r.result_size = SOCKADDR_CHUNK_SIZE
|
||||
rv.handle = Handle(rv.resolve_cb, (rv,), resolver.loop, None)
|
||||
r.cb = &rv.handle.cb
|
||||
return rv
|
||||
|
||||
def __await__(self):
|
||||
cdef CResolve* r = &self.r
|
||||
resolver_lookup_ip(r.resolver.rust_resolver, r, r.host, r.host_len, r.port)
|
||||
return self.waiter.__await__()
|
||||
|
||||
def __dealloc__(self):
|
||||
cdef CResolve* r = &self.r
|
||||
r.host = NULL
|
||||
r.host_len = 0
|
||||
if r.result != NULL:
|
||||
PyMem_RawFree(r.result)
|
||||
r.result = NULL
|
||||
r.result_size = 0
|
||||
|
||||
cdef resolve_cb(self):
|
||||
self.waiter.set_result(self)
|
25
src/kloop/tcp.pxd
Normal file
25
src/kloop/tcp.pxd
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef struct TCPConnect:
|
||||
RingCallback ring_cb
|
||||
Loop* loop
|
||||
Callback* cb
|
||||
|
||||
|
||||
cdef class TCPTransport:
|
||||
cdef:
|
||||
KLoopImpl loop
|
||||
int fd
|
||||
object protocol
|
||||
|
||||
@staticmethod
|
||||
cdef TCPTransport new(int fd, object protocol, KLoopImpl loop)
|
78
src/kloop/tcp.pyx
Normal file
78
src/kloop/tcp.pyx
Normal file
|
@ -0,0 +1,78 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
async def tcp_connect(KLoopImpl loop, host, port):
|
||||
cdef:
|
||||
Resolve resolve
|
||||
TCPConnect connector
|
||||
int fd, res
|
||||
libc.sockaddr * addr
|
||||
Handle handle
|
||||
size_t i
|
||||
|
||||
resolve = await loop.resolver.lookup_ip(host, port)
|
||||
if not resolve.r.result_len:
|
||||
raise RuntimeError(f"Cannot resolve host: {host!r}")
|
||||
|
||||
connector.loop = &loop.loop
|
||||
connector.ring_cb.callback = tcp_connect_cb
|
||||
connector.ring_cb.data = &connector
|
||||
|
||||
exceptions = []
|
||||
for i in range(resolve.r.result_len):
|
||||
addr = resolve.r.result + i
|
||||
fd = libc.socket(addr.sa_family, libc.SOCK_STREAM, 0)
|
||||
if fd == -1:
|
||||
raise IOError("Cannot create socket")
|
||||
|
||||
try:
|
||||
waiter = loop.create_future()
|
||||
handle = Handle(waiter.set_result, (None,), loop, None)
|
||||
connector.cb = &handle.cb
|
||||
|
||||
if not ring_sq_submit_connect(
|
||||
&loop.loop.ring.sq,
|
||||
fd,
|
||||
addr,
|
||||
&connector.ring_cb,
|
||||
):
|
||||
raise ValueError("Submission queue is full!")
|
||||
|
||||
await waiter
|
||||
|
||||
res = abs(connector.ring_cb.res)
|
||||
if res != 0:
|
||||
raise IOError(res, string.strerror(res))
|
||||
return fd
|
||||
|
||||
except Exception as e:
|
||||
os.close(fd)
|
||||
exceptions.append(e)
|
||||
raise exceptions[0]
|
||||
|
||||
|
||||
cdef int tcp_connect_cb(RingCallback* cb) nogil except 0:
|
||||
cdef TCPConnect* connector = <TCPConnect*>cb.data
|
||||
return queue_push(&connector.loop.ready, connector.cb)
|
||||
|
||||
|
||||
cdef class TCPTransport:
|
||||
@staticmethod
|
||||
cdef TCPTransport new(int fd, object protocol, KLoopImpl loop):
|
||||
cdef TCPTransport rv = TCPTransport.__new__(TCPTransport)
|
||||
rv.fd = fd
|
||||
rv.protocol = protocol
|
||||
rv.loop = loop
|
||||
loop.call_soon(protocol.connection_made, rv)
|
||||
return rv
|
||||
|
||||
def get_extra_info(self, x):
|
||||
return None
|
60
src/kloop/tls.pxd
Normal file
60
src/kloop/tls.pxd
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
from cpython cimport PyObject
|
||||
from .includes cimport libc
|
||||
from .includes.openssl cimport bio
|
||||
from .loop cimport KLoopImpl, Loop, RingCallback
|
||||
|
||||
|
||||
cdef struct Proxy:
|
||||
PyObject* transport
|
||||
libc.iovec send_vec
|
||||
libc.msghdr send_msg
|
||||
RingCallback send_callback
|
||||
libc.iovec recv_vec
|
||||
libc.msghdr recv_msg
|
||||
RingCallback recv_callback
|
||||
unsigned char flags
|
||||
char* read_buffer
|
||||
void* cmsg
|
||||
|
||||
Loop* loop
|
||||
int fd
|
||||
|
||||
|
||||
cdef enum State:
|
||||
UNWRAPPED
|
||||
HANDSHAKING
|
||||
WRAPPED
|
||||
WRAPPED_KTLS
|
||||
|
||||
|
||||
cdef class TLSTransport:
|
||||
cdef:
|
||||
KLoopImpl loop
|
||||
int fd
|
||||
bio.BIO* bio
|
||||
object protocol
|
||||
object sslctx
|
||||
object sslobj
|
||||
object waiter
|
||||
Proxy proxy
|
||||
State state
|
||||
object write_buffer
|
||||
bint sending
|
||||
|
||||
|
||||
cdef do_handshake(self)
|
||||
cdef do_read(self)
|
||||
cdef do_read_ktls(self)
|
||||
cdef write_cb(self, int res)
|
||||
cdef read_cb(self, int res)
|
611
src/kloop/tls.pyx
Normal file
611
src/kloop/tls.pyx
Normal file
|
@ -0,0 +1,611 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
import collections
|
||||
import socket
|
||||
import ssl
|
||||
from cpython cimport PyMem_RawMalloc, PyMem_RawFree
|
||||
from libc cimport errno, string
|
||||
|
||||
from .includes.openssl cimport err, ssl as ssl_h
|
||||
from .includes cimport pyssl, linux
|
||||
from .loop cimport ring_sq_submit_sendmsg, ring_sq_submit_recvmsg
|
||||
|
||||
|
||||
cdef int BIO_CTRL_SET_KTLS = 72
|
||||
cdef int BIO_CTRL_GET_KTLS_SEND = 73
|
||||
cdef int BIO_CTRL_GET_KTLS_RECV = 76
|
||||
|
||||
cdef int FLAGS_KTLS_TX_CTRL_MSG = 0x1000
|
||||
cdef int FLAGS_KTLS_RX = 0x2000
|
||||
cdef int FLAGS_KTLS_TX = 0x4000
|
||||
|
||||
cdef unsigned char FLAGS_PROXY_SEND_SUBMITTED = 1 << 0
|
||||
cdef unsigned char FLAGS_PROXY_SEND_COMPLETED = 1 << 1
|
||||
cdef unsigned char FLAGS_PROXY_SEND_IN_PROXY = 1 << 2
|
||||
cdef unsigned char FLAGS_PROXY_SEND_ALL = (
|
||||
FLAGS_PROXY_SEND_SUBMITTED |
|
||||
FLAGS_PROXY_SEND_COMPLETED |
|
||||
FLAGS_PROXY_SEND_IN_PROXY
|
||||
)
|
||||
cdef unsigned char FLAGS_PROXY_RECV_SUBMITTED = 1 << 4
|
||||
cdef unsigned char FLAGS_PROXY_RECV_COMPLETED = 1 << 5
|
||||
cdef unsigned char FLAGS_PROXY_RECV_KTLS = 1 << 6
|
||||
cdef unsigned char FLAGS_PROXY_RECV_ALL = (
|
||||
FLAGS_PROXY_RECV_SUBMITTED |
|
||||
FLAGS_PROXY_RECV_COMPLETED
|
||||
)
|
||||
|
||||
cdef size_t CMSG_SIZE = libc.CMSG_SPACE(sizeof(unsigned char))
|
||||
|
||||
|
||||
cdef inline void reset_msg(libc.msghdr* msg, void* cmsg) nogil:
|
||||
msg.msg_name = NULL
|
||||
msg.msg_namelen = 0
|
||||
msg.msg_flags = 0
|
||||
if cmsg == NULL:
|
||||
msg.msg_control = NULL
|
||||
msg.msg_controllen = 0
|
||||
else:
|
||||
msg.msg_control = cmsg
|
||||
msg.msg_controllen = CMSG_SIZE
|
||||
|
||||
|
||||
cdef object fromOpenSSLError(object err_type):
|
||||
cdef:
|
||||
unsigned long e = err.get_error()
|
||||
const char* msg = err.reason_error_string(e)
|
||||
if msg == NULL:
|
||||
return err_type()
|
||||
else:
|
||||
return err_type(msg.decode("ISO-8859-1"))
|
||||
|
||||
|
||||
cdef int bio_write_ex(
|
||||
bio.BIO* b, const char* data, size_t datal, size_t* written
|
||||
) nogil:
|
||||
cdef:
|
||||
Proxy* proxy = <Proxy*>bio.get_data(b)
|
||||
int res
|
||||
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_write_ex(data=%x, datal=%d)" % (<long long>data, datal))
|
||||
|
||||
if proxy.flags & FLAGS_PROXY_SEND_SUBMITTED:
|
||||
if proxy.send_vec.iov_base != data:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_write_ex() error: concurrent call")
|
||||
return 0
|
||||
if proxy.send_vec.iov_len > datal:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_write_ex() error: short rewrite")
|
||||
return 0
|
||||
|
||||
bio.clear_retry_flags(b)
|
||||
if proxy.flags & FLAGS_PROXY_SEND_COMPLETED:
|
||||
proxy.flags &= ~FLAGS_PROXY_SEND_ALL
|
||||
res = proxy.send_callback.res
|
||||
if res < 0:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_write_ex() error:", -res)
|
||||
errno.errno = -res
|
||||
return 0
|
||||
|
||||
written[0] = res
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_write_ex() written:", res)
|
||||
print(">>> ", end="")
|
||||
for i in range(res):
|
||||
print(
|
||||
"%02x " % <unsigned char>data[i],
|
||||
end="" if (i + 1) % 16 or i == res - 1 else "\n>>> ",
|
||||
)
|
||||
print()
|
||||
|
||||
else:
|
||||
written[0] = 0
|
||||
bio.set_retry_write(b)
|
||||
|
||||
if not proxy.flags & FLAGS_PROXY_SEND_SUBMITTED:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_write_ex() submit")
|
||||
proxy.send_vec.iov_base = data
|
||||
proxy.send_vec.iov_len = datal
|
||||
reset_msg(&proxy.send_msg, NULL)
|
||||
if not ring_sq_submit_sendmsg(
|
||||
&proxy.loop.ring.sq,
|
||||
proxy.fd,
|
||||
&proxy.send_msg,
|
||||
&proxy.send_callback,
|
||||
):
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_write_ex() error: SQ full")
|
||||
return 0
|
||||
proxy.flags |= FLAGS_PROXY_SEND_SUBMITTED
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
cdef int bio_read_ex(
|
||||
bio.BIO* b, char* data, size_t datal, size_t* readbytes
|
||||
) nogil:
|
||||
cdef:
|
||||
Proxy* proxy = <Proxy*>bio.get_data(b)
|
||||
libc.cmsghdr* cmsg = NULL
|
||||
int res
|
||||
int is_ktls = bio.test_flags(b, FLAGS_KTLS_RX)
|
||||
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print('bio_read_ex(data=%x, datal=%d)' % (<long long>data, datal))
|
||||
|
||||
if proxy.flags & FLAGS_PROXY_RECV_SUBMITTED:
|
||||
if proxy.recv_vec.iov_base != (data + 5 if is_ktls else data):
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_read_ex() error: concurrent call")
|
||||
return 0
|
||||
if proxy.recv_vec.iov_len > (datal - 21 if is_ktls else datal):
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_read_ex() error: short reread")
|
||||
return 0
|
||||
|
||||
bio.clear_retry_flags(b)
|
||||
if (
|
||||
proxy.flags & FLAGS_PROXY_RECV_KTLS and
|
||||
proxy.flags & FLAGS_PROXY_RECV_COMPLETED
|
||||
):
|
||||
proxy.flags &= ~FLAGS_PROXY_RECV_ALL
|
||||
res = proxy.recv_callback.res
|
||||
if datal < res + 5:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_read_ex() error: datal too short")
|
||||
errno.errno = errno.EINVAL
|
||||
return 0
|
||||
|
||||
cmsg = libc.CMSG_FIRSTHDR(&proxy.recv_msg)
|
||||
if cmsg.cmsg_type == linux.TLS_GET_RECORD_TYPE:
|
||||
data[0] = (<unsigned char *> libc.CMSG_DATA(cmsg))[0]
|
||||
data[1] = 0x03 # TLS1_2_VERSION_MAJOR
|
||||
data[2] = 0x03 # TLS1_2_VERSION_MINOR
|
||||
# returned length is limited to msg_iov.iov_len above
|
||||
data[3] = (res >> 8) & 0xff
|
||||
data[4] = res & 0xff
|
||||
string.memcpy(data + 5, proxy.read_buffer, res)
|
||||
res += 5
|
||||
else:
|
||||
string.memcpy(data, proxy.read_buffer, res)
|
||||
readbytes[0] = res
|
||||
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_read_ex() read:", res, "(forwarded TLS record)")
|
||||
print("<<< ", end="")
|
||||
for i in range(res):
|
||||
print(
|
||||
"%02x " % <unsigned char>data[i],
|
||||
end="" if (i + 1) % 16 or i == res - 1 else "\n<<< ",
|
||||
)
|
||||
print()
|
||||
|
||||
elif proxy.flags & FLAGS_PROXY_RECV_COMPLETED:
|
||||
proxy.flags &= ~FLAGS_PROXY_RECV_ALL
|
||||
res = proxy.recv_callback.res
|
||||
if res < 0:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_read_ex() error:", -res)
|
||||
errno.errno = -res
|
||||
return 0
|
||||
|
||||
if is_ktls:
|
||||
if proxy.recv_msg.msg_controllen:
|
||||
cmsg = libc.CMSG_FIRSTHDR(&proxy.recv_msg)
|
||||
if cmsg.cmsg_type == linux.TLS_GET_RECORD_TYPE:
|
||||
data[0] = (<unsigned char*>libc.CMSG_DATA(cmsg))[0]
|
||||
data[1] = 0x03 # TLS1_2_VERSION_MAJOR
|
||||
data[2] = 0x03 # TLS1_2_VERSION_MINOR
|
||||
# returned length is limited to msg_iov.iov_len above
|
||||
data[3] = (res >> 8) & 0xff
|
||||
data[4] = res & 0xff
|
||||
res += 5
|
||||
|
||||
if res == 0:
|
||||
bio.set_flags(b, bio.FLAGS_IN_EOF)
|
||||
readbytes[0] = res
|
||||
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print(
|
||||
"bio_read_ex() read:", res, "(TLS record)" if cmsg else ""
|
||||
)
|
||||
print("<<< ", end="")
|
||||
for i in range(res):
|
||||
print(
|
||||
"%02x " % <unsigned char>data[i],
|
||||
end="" if (i + 1) % 16 or i == res - 1 else "\n<<< ",
|
||||
)
|
||||
print()
|
||||
else:
|
||||
bio.set_retry_read(b)
|
||||
readbytes[0] = 0
|
||||
if not proxy.flags & FLAGS_PROXY_RECV_SUBMITTED:
|
||||
if proxy.flags & FLAGS_PROXY_RECV_KTLS:
|
||||
reset_msg(&proxy.recv_msg, proxy.cmsg)
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_read_ex() submit(%x, %d)" % (
|
||||
<long long>proxy.recv_vec.iov_base,
|
||||
proxy.recv_vec.iov_len,
|
||||
))
|
||||
elif is_ktls:
|
||||
if datal < 21:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_read_ex() error: datal too short")
|
||||
errno.errno = errno.EINVAL
|
||||
return 0
|
||||
|
||||
proxy.recv_vec.iov_base = data + 5
|
||||
proxy.recv_vec.iov_len = datal - 21
|
||||
reset_msg(&proxy.recv_msg, proxy.cmsg)
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_read_ex() submit(%x, %d)" % (
|
||||
<long long>proxy.recv_vec.iov_base,
|
||||
proxy.recv_vec.iov_len,
|
||||
))
|
||||
else:
|
||||
proxy.recv_vec.iov_base = data
|
||||
proxy.recv_vec.iov_len = datal
|
||||
reset_msg(&proxy.recv_msg, NULL)
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_read_ex() submit")
|
||||
|
||||
if not ring_sq_submit_recvmsg(
|
||||
&proxy.loop.ring.sq,
|
||||
proxy.fd,
|
||||
&proxy.recv_msg,
|
||||
&proxy.recv_callback,
|
||||
):
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("bio_read_ex() error: SQ full")
|
||||
return 0
|
||||
proxy.flags |= FLAGS_PROXY_RECV_SUBMITTED
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
cdef long bio_ctrl(bio.BIO* b, int cmd, long num, void* ptr) nogil:
|
||||
cdef:
|
||||
ssl_h.ktls_crypto_info_t* crypto_info
|
||||
long ret = 0
|
||||
if cmd == bio.BIO_CTRL_EOF:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("BIO_CTRL_EOF", ret)
|
||||
elif cmd == bio.BIO_CTRL_PUSH:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("BIO_CTRL_PUSH", ret)
|
||||
elif cmd == bio.BIO_CTRL_POP:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("BIO_CTRL_POP", ret)
|
||||
elif cmd == bio.BIO_CTRL_FLUSH:
|
||||
ret = 1
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print('BIO_CTRL_FLUSH', ret)
|
||||
elif cmd == BIO_CTRL_SET_KTLS:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print("BIO_CTRL_SET_KTLS", "TX end" if num else "RX end")
|
||||
crypto_info = <ssl_h.ktls_crypto_info_t*>ptr
|
||||
if libc.setsockopt(
|
||||
(<Proxy*>bio.get_data(b)).fd,
|
||||
libc.SOL_TLS,
|
||||
linux.TLS_TX if num else linux.TLS_RX,
|
||||
crypto_info,
|
||||
crypto_info.tls_crypto_info_len,
|
||||
) == 0:
|
||||
bio.set_flags(b, FLAGS_KTLS_TX if num else FLAGS_KTLS_RX)
|
||||
else:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print(
|
||||
"BIO_CTRL_SET_KTLS",
|
||||
"TX end" if num else "RX end",
|
||||
"failed",
|
||||
)
|
||||
elif cmd == BIO_CTRL_GET_KTLS_SEND:
|
||||
return bio.test_flags(b, FLAGS_KTLS_TX) != 0
|
||||
elif cmd == BIO_CTRL_GET_KTLS_RECV:
|
||||
return bio.test_flags(b, FLAGS_KTLS_RX) != 0
|
||||
else:
|
||||
IF DEBUG:
|
||||
with gil:
|
||||
print('bio_ctrl', cmd, num)
|
||||
return ret
|
||||
|
||||
|
||||
cdef int bio_create(bio.BIO* b) nogil:
|
||||
bio.set_init(b, 1)
|
||||
return 1
|
||||
|
||||
|
||||
cdef int bio_destroy(bio.BIO* b) nogil:
|
||||
bio.set_shutdown(b, 1)
|
||||
return 1
|
||||
|
||||
|
||||
cdef int tls_send_cb(RingCallback* cb) nogil except 0:
|
||||
cdef Proxy* proxy = <Proxy*>cb.data
|
||||
proxy.flags |= FLAGS_PROXY_SEND_COMPLETED
|
||||
with gil:
|
||||
(<TLSTransport>proxy.transport).write_cb(cb.res)
|
||||
return 1
|
||||
|
||||
|
||||
cdef int tls_recv_cb(RingCallback* cb) nogil except 0:
|
||||
cdef Proxy* proxy = <Proxy*>cb.data
|
||||
proxy.flags |= FLAGS_PROXY_RECV_COMPLETED
|
||||
with gil:
|
||||
(<TLSTransport>proxy.transport).read_cb(cb.res)
|
||||
return 1
|
||||
|
||||
|
||||
cdef class TLSTransport:
|
||||
@staticmethod
|
||||
def new(
|
||||
int fd,
|
||||
protocol,
|
||||
KLoopImpl loop,
|
||||
sslctx,
|
||||
server_side=False,
|
||||
server_hostname=None,
|
||||
session=None,
|
||||
waiter=None,
|
||||
):
|
||||
cdef:
|
||||
TLSTransport rv = TLSTransport.__new__(TLSTransport)
|
||||
pyssl.PySSLMemoryBIO* c_bio
|
||||
pyssl.SSL* s
|
||||
|
||||
libc.setsockopt(fd, socket.SOL_TCP, linux.TCP_ULP, b"tls", 3)
|
||||
|
||||
py_bio = ssl.MemoryBIO()
|
||||
c_bio = <pyssl.PySSLMemoryBIO*>py_bio
|
||||
c_bio.bio, rv.bio = rv.bio, c_bio.bio
|
||||
try:
|
||||
rv.sslobj = sslctx.wrap_bio(
|
||||
py_bio, py_bio, server_side, server_hostname, session
|
||||
)
|
||||
finally:
|
||||
c_bio.bio, rv.bio = rv.bio, c_bio.bio
|
||||
del py_bio
|
||||
|
||||
s = (<pyssl.PySSLSocket*>rv.sslobj._sslobj).ssl
|
||||
ssl_h.set_options(s, ssl_h.OP_ENABLE_KTLS)
|
||||
ssl_h.clear_mode(
|
||||
s,
|
||||
ssl_h.SSL_MODE_RELEASE_BUFFERS |
|
||||
ssl_h.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
|
||||
)
|
||||
rv.fd = fd
|
||||
rv.protocol = protocol
|
||||
rv.loop = loop
|
||||
rv.sslctx = sslctx
|
||||
rv.proxy.loop = &loop.loop
|
||||
rv.proxy.fd = fd
|
||||
rv.waiter = waiter
|
||||
rv.write_buffer = collections.deque()
|
||||
|
||||
rv.do_handshake()
|
||||
return rv
|
||||
|
||||
def __cinit__(self):
|
||||
self.state = UNWRAPPED
|
||||
self.bio = bio.new(KTLS_BIO_METHOD)
|
||||
self.proxy.transport = <PyObject*>self
|
||||
self.proxy.send_msg.msg_iov = &self.proxy.send_vec
|
||||
self.proxy.send_msg.msg_iovlen = 1
|
||||
self.proxy.send_callback.data = <void*>&self.proxy
|
||||
self.proxy.send_callback.callback = tls_send_cb
|
||||
self.proxy.cmsg = PyMem_RawMalloc(CMSG_SIZE)
|
||||
if self.proxy.cmsg == NULL:
|
||||
raise MemoryError
|
||||
self.proxy.recv_msg.msg_iov = &self.proxy.recv_vec
|
||||
self.proxy.recv_msg.msg_iovlen = 1
|
||||
self.proxy.recv_callback.data = <void*>&self.proxy
|
||||
self.proxy.recv_callback.callback = tls_recv_cb
|
||||
bio.set_data(self.bio, <void*>&self.proxy)
|
||||
|
||||
def __dealloc__(self):
|
||||
self.sslobj = None
|
||||
bio.free(self.bio)
|
||||
PyMem_RawFree(self.proxy.read_buffer)
|
||||
PyMem_RawFree(self.proxy.cmsg)
|
||||
|
||||
cdef do_handshake(self):
|
||||
if self.state == UNWRAPPED:
|
||||
self.state = HANDSHAKING
|
||||
elif self.state != HANDSHAKING:
|
||||
raise RuntimeError("Cannot do handshake now")
|
||||
|
||||
|
||||
try:
|
||||
IF DEBUG:
|
||||
print("do_handshake()")
|
||||
self.sslobj.do_handshake()
|
||||
except ssl.SSLWantReadError:
|
||||
IF DEBUG:
|
||||
print("do_handshake() SSLWantReadError")
|
||||
except ssl.SSLWantWriteError:
|
||||
IF DEBUG:
|
||||
print("do_handshake() SSLWantWriteError")
|
||||
except Exception as ex:
|
||||
IF DEBUG:
|
||||
print('do_handshake() error:', ex)
|
||||
raise
|
||||
else:
|
||||
IF DEBUG:
|
||||
print('do_handshake() done')
|
||||
|
||||
self.state = WRAPPED
|
||||
if self.waiter:
|
||||
self.waiter.set_result(self)
|
||||
self.waiter = None
|
||||
|
||||
if bio.test_flags(self.bio, FLAGS_KTLS_RX):
|
||||
self.proxy.read_buffer = <char*>PyMem_RawMalloc(65536)
|
||||
if self.proxy.read_buffer == NULL:
|
||||
raise MemoryError
|
||||
self.proxy.flags |= FLAGS_PROXY_RECV_KTLS
|
||||
self.proxy.recv_vec.iov_base = self.proxy.read_buffer
|
||||
self.proxy.recv_vec.iov_len = 65536
|
||||
self.do_read_ktls()
|
||||
else:
|
||||
self.do_read()
|
||||
|
||||
cdef do_read_ktls(self):
|
||||
cdef:
|
||||
int res
|
||||
libc.cmsghdr* cmsg
|
||||
unsigned char record_type
|
||||
|
||||
if self.proxy.flags & FLAGS_PROXY_RECV_COMPLETED:
|
||||
self.proxy.flags &= ~FLAGS_PROXY_RECV_ALL
|
||||
res = self.proxy.recv_callback.res
|
||||
if res < 0:
|
||||
IF DEBUG:
|
||||
print("do_read_ktls() error:", -res)
|
||||
self.loop.call_soon(
|
||||
self.protocol.connection_lost,
|
||||
IOError(-res, string.strerror(-res))
|
||||
)
|
||||
elif res == 0:
|
||||
IF DEBUG:
|
||||
print("do_read_ktls() EOF")
|
||||
self.loop.call_soon(self.protocol.eof_received)
|
||||
self.loop.call_soon(self.protocol.connection_lost, None)
|
||||
else:
|
||||
if self.proxy.recv_msg.msg_controllen:
|
||||
cmsg = libc.CMSG_FIRSTHDR(&self.proxy.recv_msg)
|
||||
if cmsg.cmsg_type == linux.TLS_GET_RECORD_TYPE:
|
||||
record_type = (<unsigned char*>libc.CMSG_DATA(cmsg))[0]
|
||||
if record_type != ssl_h.SSL3_RT_APPLICATION_DATA:
|
||||
IF DEBUG:
|
||||
print("do_read_ktls() forward CMSG")
|
||||
self.proxy.flags |= FLAGS_PROXY_RECV_COMPLETED
|
||||
return self.do_read()
|
||||
IF DEBUG:
|
||||
print("do_read_ktls() received", res, "bytes")
|
||||
self.loop.call_soon(
|
||||
self.protocol.data_received,
|
||||
bytes(self.proxy.read_buffer[:res]),
|
||||
)
|
||||
self.loop.call_soon(self.do_read_ktls, self)
|
||||
|
||||
elif not self.proxy.flags & FLAGS_PROXY_RECV_SUBMITTED:
|
||||
IF DEBUG:
|
||||
print("do_read_ktls() submit")
|
||||
reset_msg(&self.proxy.recv_msg, self.proxy.cmsg)
|
||||
if not ring_sq_submit_recvmsg(
|
||||
&self.proxy.loop.ring.sq,
|
||||
self.fd,
|
||||
&self.proxy.recv_msg,
|
||||
&self.proxy.recv_callback,
|
||||
):
|
||||
raise RuntimeError("SQ full")
|
||||
self.proxy.flags |= FLAGS_PROXY_RECV_SUBMITTED
|
||||
|
||||
cdef do_read(self):
|
||||
try:
|
||||
data = self.sslobj.read(65536)
|
||||
except ssl.SSLWantReadError:
|
||||
IF DEBUG:
|
||||
print("do_read() SSLWantReadError")
|
||||
except ssl.SSLWantWriteError:
|
||||
IF DEBUG:
|
||||
print("do_read() SSLWantWriteError")
|
||||
except Exception as ex:
|
||||
IF DEBUG:
|
||||
print("do_read() error:", ex)
|
||||
self.loop.call_soon(self.protocol.connection_lost, ex)
|
||||
else:
|
||||
if data:
|
||||
IF DEBUG:
|
||||
print("do_read() received", len(data), bytes)
|
||||
print(data)
|
||||
self.loop.call_soon(self.protocol.data_received, data)
|
||||
if self.proxy.flags & FLAGS_PROXY_RECV_KTLS:
|
||||
self.loop.call_soon(self.do_read_ktls, self)
|
||||
else:
|
||||
self.loop.call_soon(self.do_read, self)
|
||||
else:
|
||||
IF DEBUG:
|
||||
print("do_read() EOF")
|
||||
self.loop.call_soon(self.protocol.eof_received)
|
||||
self.loop.call_soon(self.protocol.connection_lost, None)
|
||||
|
||||
cdef write_cb(self, int res):
|
||||
IF DEBUG:
|
||||
print("write_cb", res, "state:", self.state)
|
||||
if self.state == HANDSHAKING:
|
||||
self.do_handshake()
|
||||
|
||||
cdef read_cb(self, int res):
|
||||
IF DEBUG:
|
||||
print("read_cb", res, "state:", self.state)
|
||||
if self.state == HANDSHAKING:
|
||||
self.do_handshake()
|
||||
elif self.state == WRAPPED:
|
||||
if self.proxy.flags & FLAGS_PROXY_RECV_KTLS:
|
||||
self.do_read_ktls()
|
||||
else:
|
||||
self.do_read()
|
||||
|
||||
def write(self, data):
|
||||
if self.sending:
|
||||
self.write_buffer.append(data)
|
||||
else:
|
||||
try:
|
||||
self.sslobj.write(data)
|
||||
except ssl.SSLWantWriteError:
|
||||
IF DEBUG:
|
||||
print("write() SSLWantWriteError")
|
||||
|
||||
|
||||
cdef bio.Method* KTLS_BIO_METHOD = bio.meth_new(
|
||||
bio.get_new_index(), "kTLS BIO"
|
||||
)
|
||||
if not bio.meth_set_write_ex(KTLS_BIO_METHOD, bio_write_ex):
|
||||
raise fromOpenSSLError(ImportError)
|
||||
if not bio.meth_set_read_ex(KTLS_BIO_METHOD, bio_read_ex):
|
||||
raise fromOpenSSLError(ImportError)
|
||||
if not bio.meth_set_ctrl(KTLS_BIO_METHOD, bio_ctrl):
|
||||
raise fromOpenSSLError(ImportError)
|
||||
if not bio.meth_set_create(KTLS_BIO_METHOD, bio_create):
|
||||
raise fromOpenSSLError(ImportError)
|
||||
if not bio.meth_set_destroy(KTLS_BIO_METHOD, bio_destroy):
|
||||
raise fromOpenSSLError(ImportError)
|
19
src/kloop/udp.pxd
Normal file
19
src/kloop/udp.pxd
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef struct UDPAction:
|
||||
int fd
|
||||
libc.iovec vec
|
||||
libc.msghdr msg
|
||||
RingCallback callback
|
||||
CResolver* resolver
|
||||
void* rust_waker
|
||||
libc.sockaddr addr
|
104
src/kloop/udp.pyx
Normal file
104
src/kloop/udp.pyx
Normal file
|
@ -0,0 +1,104 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef extern int udp_bind(libc.sockaddr* addr, libc.socklen_t addrlen) nogil:
|
||||
cdef int fd = libc.socket(addr.sa_family, libc.SOCK_DGRAM, 0)
|
||||
if fd == -1:
|
||||
return -1
|
||||
if libc.bind(fd, addr, addrlen) == -1:
|
||||
# TODO: close fd
|
||||
return -1
|
||||
return fd
|
||||
|
||||
|
||||
cdef extern unsigned long udp_action_init(int fd, CResolver* resolver) nogil:
|
||||
cdef UDPAction* rv
|
||||
rv = <UDPAction*>PyMem_RawMalloc(sizeof(UDPAction))
|
||||
if rv == NULL:
|
||||
return 0
|
||||
string.memset(rv, 0, sizeof(UDPAction))
|
||||
rv.fd = fd
|
||||
rv.resolver = resolver
|
||||
rv.msg.msg_iov = &rv.vec
|
||||
rv.msg.msg_iovlen = 1
|
||||
rv.callback.data = <void*>rv
|
||||
rv.callback.callback = udp_action_cb
|
||||
rv.msg.msg_name = <void*>&rv.addr
|
||||
rv.msg.msg_namelen = sizeof(rv.addr)
|
||||
return <unsigned long>rv
|
||||
|
||||
|
||||
cdef int udp_action_cb(RingCallback* cb) nogil except 0:
|
||||
cdef UDPAction* action = <UDPAction*>cb.data
|
||||
waker_wake(action.rust_waker)
|
||||
resolver_run_until_stalled(action.resolver.rust_resolver)
|
||||
return 1
|
||||
|
||||
|
||||
cdef extern int udp_send_poll(
|
||||
unsigned long send_in,
|
||||
const char* data,
|
||||
size_t datalen,
|
||||
libc.sockaddr* addr,
|
||||
libc.socklen_t addrlen,
|
||||
void* waker,
|
||||
) nogil:
|
||||
cdef UDPAction* send = <UDPAction*>send_in
|
||||
if send.vec.iov_base == NULL:
|
||||
send.vec.iov_base = data
|
||||
send.vec.iov_len = datalen
|
||||
send.addr = addr[0]
|
||||
send.msg.msg_namelen = addrlen
|
||||
send.rust_waker = waker
|
||||
return ring_sq_submit_sendmsg(
|
||||
&send.resolver.loop.ring.sq,
|
||||
send.fd,
|
||||
&send.msg,
|
||||
&send.callback,
|
||||
) - 1
|
||||
else:
|
||||
waker_forget(waker)
|
||||
if send.vec.iov_base != data or send.vec.iov_len != datalen:
|
||||
return -1
|
||||
return send.callback.res or -1
|
||||
|
||||
|
||||
cdef extern int udp_recv_poll(
|
||||
unsigned long recv_in,
|
||||
char* data,
|
||||
size_t datalen,
|
||||
void* waker,
|
||||
) nogil:
|
||||
cdef UDPAction* recv = <UDPAction*>recv_in
|
||||
if recv.vec.iov_base == NULL:
|
||||
recv.vec.iov_base = data
|
||||
recv.vec.iov_len = datalen
|
||||
recv.rust_waker = waker
|
||||
return ring_sq_submit_recvmsg(
|
||||
&recv.resolver.loop.ring.sq,
|
||||
recv.fd,
|
||||
&recv.msg,
|
||||
&recv.callback,
|
||||
) - 1
|
||||
else:
|
||||
waker_forget(waker)
|
||||
if recv.vec.iov_base != data or recv.vec.iov_len != datalen:
|
||||
return -1
|
||||
return recv.callback.res or -1
|
||||
|
||||
|
||||
cdef extern libc.sockaddr* udp_get_addr(unsigned long recv_in) nogil:
|
||||
cdef UDPAction* recv = <UDPAction*>recv_in
|
||||
return &recv.addr
|
||||
|
||||
|
||||
cdef extern void udp_action_free(unsigned long action) nogil:
|
||||
PyMem_RawFree(<void*>action)
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2022 Fantix King http://fantix.pro
|
||||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
|
@ -8,3 +8,67 @@
|
|||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef struct SubmissionQueue:
|
||||
unsigned* khead
|
||||
unsigned* ktail
|
||||
unsigned* kring_mask
|
||||
unsigned* kring_entries
|
||||
unsigned* kflags
|
||||
unsigned* kdropped
|
||||
unsigned* array
|
||||
linux.io_uring_sqe* sqes
|
||||
|
||||
unsigned sqe_head
|
||||
unsigned sqe_tail
|
||||
|
||||
size_t ring_size
|
||||
void* ring_ptr
|
||||
|
||||
|
||||
cdef struct CompletionQueue:
|
||||
unsigned* khead
|
||||
unsigned* ktail
|
||||
unsigned* kring_mask
|
||||
unsigned* kring_entries
|
||||
unsigned* kflags
|
||||
unsigned* koverflow
|
||||
linux.io_uring_cqe* cqes
|
||||
|
||||
size_t ring_size
|
||||
void* ring_ptr
|
||||
|
||||
|
||||
cdef struct Ring:
|
||||
SubmissionQueue sq
|
||||
CompletionQueue cq
|
||||
unsigned flags
|
||||
int ring_fd
|
||||
|
||||
unsigned features
|
||||
int enter_ring_fd
|
||||
linux.__u8 int_flags
|
||||
linux.__u8 pad[3]
|
||||
unsigned pad2
|
||||
|
||||
|
||||
cdef struct RingCallback:
|
||||
void* data
|
||||
int res
|
||||
int (*callback)(RingCallback* cb) nogil except 0
|
||||
|
||||
|
||||
cdef int ring_sq_submit_sendmsg(
|
||||
SubmissionQueue* sq,
|
||||
int fd,
|
||||
const libc.msghdr *msg,
|
||||
RingCallback* callback,
|
||||
) nogil
|
||||
|
||||
|
||||
cdef int ring_sq_submit_recvmsg(
|
||||
SubmissionQueue* sq,
|
||||
int fd,
|
||||
const libc.msghdr *msg,
|
||||
RingCallback* callback,
|
||||
) nogil
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2022 Fantix King http://fantix.pro
|
||||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
|
@ -8,3 +8,327 @@
|
|||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
|
||||
cdef linux.__u32 SIG_SIZE = libc._NSIG // 8
|
||||
|
||||
|
||||
cdef int ring_init(
|
||||
Ring* ring,
|
||||
linux.__u32 queue_depth,
|
||||
linux.io_uring_params* params
|
||||
) nogil except 0:
|
||||
# SYSCALL: SYS_io_uring_setup
|
||||
ring.ring_fd = ring.enter_ring_fd = libc.syscall(
|
||||
libc.SYS_io_uring_setup, queue_depth, params
|
||||
)
|
||||
if ring.ring_fd < 0:
|
||||
with gil:
|
||||
PyErr_SetFromErrno(IOError)
|
||||
return 0
|
||||
|
||||
# mmap the ring_ptr
|
||||
ring.sq.ring_size = ring.cq.ring_size = max(
|
||||
params.sq_off.array + params.sq_entries * sizeof(unsigned),
|
||||
params.cq_off.cqes + params.cq_entries * sizeof(linux.io_uring_cqe)
|
||||
)
|
||||
ring.sq.ring_ptr = ring.cq.ring_ptr = mman.mmap(
|
||||
NULL,
|
||||
ring.sq.ring_size,
|
||||
mman.PROT_READ | mman.PROT_WRITE,
|
||||
mman.MAP_SHARED | mman.MAP_POPULATE,
|
||||
ring.ring_fd,
|
||||
linux.IORING_OFF_SQ_RING,
|
||||
)
|
||||
if ring.sq.ring_ptr == mman.MAP_FAILED:
|
||||
with gil:
|
||||
PyErr_SetFromErrno(IOError)
|
||||
return 0
|
||||
|
||||
# Initialize the SubmissionQueue
|
||||
ring.sq.khead = <unsigned*>(ring.sq.ring_ptr + params.sq_off.head)
|
||||
ring.sq.ktail = <unsigned*>(ring.sq.ring_ptr + params.sq_off.tail)
|
||||
ring.sq.kring_mask = <unsigned*>(ring.sq.ring_ptr + params.sq_off.ring_mask)
|
||||
ring.sq.kring_entries = <unsigned*>(ring.sq.ring_ptr + params.sq_off.ring_entries)
|
||||
ring.sq.kflags = <unsigned*>(ring.sq.ring_ptr + params.sq_off.flags)
|
||||
ring.sq.kdropped = <unsigned*>(ring.sq.ring_ptr + params.sq_off.dropped)
|
||||
ring.sq.array = <unsigned*>(ring.sq.ring_ptr + params.sq_off.array)
|
||||
ring.sq.sqes = <linux.io_uring_sqe*>mman.mmap(
|
||||
NULL,
|
||||
params.sq_entries * sizeof(linux.io_uring_sqe),
|
||||
mman.PROT_READ | mman.PROT_WRITE,
|
||||
mman.MAP_SHARED | mman.MAP_POPULATE,
|
||||
ring.ring_fd,
|
||||
linux.IORING_OFF_SQES,
|
||||
)
|
||||
if ring.sq.sqes == mman.MAP_FAILED:
|
||||
mman.munmap(ring.sq.ring_ptr, ring.sq.ring_size)
|
||||
with gil:
|
||||
PyErr_SetFromErrno(IOError)
|
||||
return 0
|
||||
|
||||
# Initialize the CompletionQueue
|
||||
ring.cq.khead = <unsigned*>(ring.cq.ring_ptr + params.cq_off.head)
|
||||
ring.cq.ktail = <unsigned*>(ring.cq.ring_ptr + params.cq_off.tail)
|
||||
ring.cq.kring_mask = <unsigned*>(ring.cq.ring_ptr + params.cq_off.ring_mask)
|
||||
ring.cq.kring_entries = <unsigned*>(ring.cq.ring_ptr + params.cq_off.ring_entries)
|
||||
ring.cq.koverflow = <unsigned*>(ring.cq.ring_ptr + params.cq_off.overflow)
|
||||
ring.cq.cqes = <linux.io_uring_cqe*>(ring.cq.ring_ptr + params.cq_off.cqes)
|
||||
if params.cq_off.flags:
|
||||
ring.cq.kflags = <unsigned*>(ring.cq.ring_ptr + params.cq_off.flags)
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
cdef int ring_uninit(Ring* ring) nogil except 0:
|
||||
if ring.sq.sqes != NULL:
|
||||
mman.munmap(
|
||||
ring.sq.sqes,
|
||||
ring.sq.kring_entries[0] * sizeof(linux.io_uring_sqe),
|
||||
)
|
||||
if ring.sq.ring_ptr != NULL:
|
||||
mman.munmap(ring.sq.ring_ptr, ring.sq.ring_size)
|
||||
if ring.ring_fd:
|
||||
if unistd.close(ring.ring_fd) != 0:
|
||||
with gil:
|
||||
PyErr_SetFromErrno(IOError)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
cdef inline int ring_sq_flush(SubmissionQueue* sq) nogil:
|
||||
cdef:
|
||||
unsigned mask = sq.kring_mask[0]
|
||||
unsigned tail = sq.ktail[0]
|
||||
unsigned to_submit = sq.sqe_tail - sq.sqe_head
|
||||
|
||||
if to_submit:
|
||||
while to_submit:
|
||||
sq.array[tail & mask] = sq.sqe_head & mask
|
||||
tail += 1
|
||||
sq.sqe_head += 1
|
||||
to_submit -= 1
|
||||
atomic.store_explicit(<atomic.uint*>sq.ktail, tail, atomic.release)
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
cdef int ring_select(Ring* ring, long long timeout) nogil except -1:
|
||||
cdef:
|
||||
int flags = linux.IORING_ENTER_EXT_ARG
|
||||
bint need_enter = 0
|
||||
unsigned ready
|
||||
unsigned wait_nr = 0
|
||||
linux.io_uring_getevents_arg arg
|
||||
linux.__kernel_timespec ts
|
||||
CompletionQueue* cq = &ring.cq
|
||||
SubmissionQueue* sq = &ring.sq
|
||||
|
||||
string.memset(&arg, 0, sizeof(arg))
|
||||
# Call enter if we have no CQE ready and timeout is not 0, or else we
|
||||
# handle the ready CQEs first.
|
||||
ready = atomic.load_explicit(
|
||||
<atomic.uint*>cq.ktail, atomic.acquire
|
||||
) - cq.khead[0]
|
||||
if not ready and timeout != 0:
|
||||
flags |= linux.IORING_ENTER_GETEVENTS
|
||||
if timeout > 0:
|
||||
ts.tv_sec = timeout // SECOND_NS
|
||||
ts.tv_nsec = timeout % SECOND_NS
|
||||
arg.ts = <linux.__u64> &ts
|
||||
wait_nr = 1
|
||||
need_enter = 1
|
||||
|
||||
# Flush the submission queue, and only wakeup the SQ polling thread if
|
||||
# there is something for the kernel to handle.
|
||||
if ring_sq_flush(sq):
|
||||
atomic.thread_fence(atomic.seq_cst)
|
||||
if atomic.load_explicit(
|
||||
<atomic.uint*>sq.kflags, atomic.relaxed
|
||||
) & linux.IORING_SQ_NEED_WAKEUP:
|
||||
arg.ts = 0
|
||||
flags |= linux.IORING_ENTER_SQ_WAKEUP
|
||||
need_enter = 1
|
||||
|
||||
if need_enter:
|
||||
arg.sigmask = 0
|
||||
arg.sigmask_sz = SIG_SIZE
|
||||
if libc.syscall(
|
||||
libc.SYS_io_uring_enter,
|
||||
ring.enter_ring_fd,
|
||||
0,
|
||||
wait_nr,
|
||||
flags,
|
||||
&arg,
|
||||
sizeof(arg),
|
||||
) < 0:
|
||||
if errno.errno != errno.ETIME:
|
||||
with gil:
|
||||
PyErr_SetFromErrno(IOError)
|
||||
return -1
|
||||
|
||||
ready = atomic.load_explicit(
|
||||
<atomic.uint*>cq.ktail, atomic.acquire
|
||||
) - cq.khead[0]
|
||||
|
||||
return ready
|
||||
|
||||
|
||||
cdef inline void ring_cq_pop(CompletionQueue* cq, RingCallback** callback) nogil:
|
||||
cdef:
|
||||
unsigned head
|
||||
linux.io_uring_cqe* cqe
|
||||
RingCallback* ret
|
||||
head = cq.khead[0]
|
||||
cqe = cq.cqes + (head & cq.kring_mask[0])
|
||||
ret = <RingCallback*>cqe.user_data
|
||||
if ret != NULL:
|
||||
ret.res = cqe.res
|
||||
callback[0] = ret
|
||||
atomic.store_explicit(<atomic.uint*>cq.khead, head + 1, atomic.release)
|
||||
|
||||
|
||||
cdef inline linux.io_uring_sqe* ring_sq_submit(
|
||||
SubmissionQueue* sq,
|
||||
linux.__u8 op,
|
||||
int fd,
|
||||
unsigned long addr,
|
||||
unsigned len,
|
||||
linux.__u64 offset,
|
||||
bint link,
|
||||
RingCallback* callback,
|
||||
) nogil:
|
||||
cdef:
|
||||
unsigned int head, next
|
||||
linux.io_uring_sqe* sqe
|
||||
head = atomic.load_explicit(<atomic.uint*>sq.khead, atomic.acquire)
|
||||
next = sq.sqe_tail + 1
|
||||
if next - head <= sq.kring_entries[0]:
|
||||
sqe = &sq.sqes[sq.sqe_tail & sq.kring_mask[0]]
|
||||
sq.sqe_tail = next
|
||||
|
||||
string.memset(sqe, 0, sizeof(linux.io_uring_sqe))
|
||||
sqe.opcode = op
|
||||
sqe.fd = fd
|
||||
sqe.off = offset
|
||||
sqe.addr = addr
|
||||
sqe.len = len
|
||||
if link:
|
||||
sqe.flags = linux.IOSQE_IO_LINK
|
||||
sqe.user_data = <linux.__u64>callback
|
||||
return sqe
|
||||
else:
|
||||
return NULL
|
||||
|
||||
|
||||
cdef int ring_sq_submit_connect(
|
||||
SubmissionQueue* sq, int fd, libc.sockaddr* addr, RingCallback* callback
|
||||
) nogil:
|
||||
return 1 if ring_sq_submit(
|
||||
sq,
|
||||
linux.IORING_OP_CONNECT,
|
||||
fd,
|
||||
<unsigned long>addr,
|
||||
0,
|
||||
sizeof(addr[0]),
|
||||
0,
|
||||
callback,
|
||||
) else 0
|
||||
|
||||
|
||||
cdef int ring_sq_submit_openat(
|
||||
SubmissionQueue* sq,
|
||||
int dfd,
|
||||
const char* path,
|
||||
int flags,
|
||||
mode_t mode,
|
||||
RingCallback* callback,
|
||||
) nogil:
|
||||
cdef linux.io_uring_sqe* sqe = ring_sq_submit(
|
||||
sq,
|
||||
linux.IORING_OP_OPENAT,
|
||||
dfd,
|
||||
<unsigned long>path,
|
||||
mode,
|
||||
0,
|
||||
0,
|
||||
callback,
|
||||
)
|
||||
if sqe == NULL:
|
||||
return 0
|
||||
else:
|
||||
sqe.open_flags = flags
|
||||
return 1
|
||||
|
||||
|
||||
cdef int ring_sq_submit_read(
|
||||
SubmissionQueue* sq,
|
||||
int fd,
|
||||
char* buf,
|
||||
unsigned nbytes,
|
||||
linux.__u64 offset,
|
||||
RingCallback* callback,
|
||||
) nogil:
|
||||
return 1 if ring_sq_submit(
|
||||
sq,
|
||||
linux.IORING_OP_READ,
|
||||
fd,
|
||||
<unsigned long>buf,
|
||||
nbytes,
|
||||
offset,
|
||||
0,
|
||||
callback,
|
||||
) else 0
|
||||
|
||||
|
||||
cdef int ring_sq_submit_close(
|
||||
SubmissionQueue* sq,
|
||||
int fd,
|
||||
RingCallback * callback,
|
||||
) nogil:
|
||||
return 1 if ring_sq_submit(
|
||||
sq,
|
||||
linux.IORING_OP_CLOSE,
|
||||
fd,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
callback,
|
||||
) else 0
|
||||
|
||||
|
||||
cdef int ring_sq_submit_sendmsg(
|
||||
SubmissionQueue* sq,
|
||||
int fd,
|
||||
const libc.msghdr *msg,
|
||||
RingCallback* callback,
|
||||
) nogil:
|
||||
return 1 if ring_sq_submit(
|
||||
sq,
|
||||
linux.IORING_OP_SENDMSG,
|
||||
fd,
|
||||
<unsigned long>msg,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
callback,
|
||||
) else 0
|
||||
|
||||
|
||||
cdef int ring_sq_submit_recvmsg(
|
||||
SubmissionQueue* sq,
|
||||
int fd,
|
||||
const libc.msghdr *msg,
|
||||
RingCallback* callback,
|
||||
) nogil:
|
||||
return 1 if ring_sq_submit(
|
||||
sq,
|
||||
linux.IORING_OP_RECVMSG,
|
||||
fd,
|
||||
<unsigned long>msg,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
callback,
|
||||
) else 0
|
||||
|
|
28
tests/cert.pem
Normal file
28
tests/cert.pem
Normal file
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIErjCCApYCCQDzWJgnuLzufzANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5s
|
||||
b2NhbGhvc3Q6ODA4ODAeFw0yMjA3MDIyMDQ5MzZaFw0yMzA3MDIyMDQ5MzZaMBkx
|
||||
FzAVBgNVBAMMDmxvY2FsaG9zdDo4MDg4MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
|
||||
MIICCgKCAgEAsTtnZV5hayOTVfyq6MbkLRaJj4tVoamMXmqTepqpEwQFxUY80MUi
|
||||
iTcnoxeKTHyToEcPDBINxDAQyvYZQyReo2CScPEBeYlW8Ur6DcZ6CslcrQKIAQEp
|
||||
JQ6a/DwZT/GS71qQfaOkiO+tKU7TbaPvF3a6kK+JFOgBaFbkfcb6Ef0xUfhrgfut
|
||||
fQFDrF4up3otlFY3thofTgoLcpx04OHIeveMWHEemEi8LOr/LRV5MDAwlWSqSHuF
|
||||
JMn/U9fPXZXiv450QINtrBY43n9xAP1sQZL4YowYbkbxWjj/ghpzwqqWZz/sxzHr
|
||||
ayy6UJcOkYiY+RzkLPZ5GLU1BJlWUKNueUtZZQ+n3OOYUGFpbeGnOl6YB1qqvNtn
|
||||
8Lvsmqu0nc3gDsINnxi4E3KEstAJlk7X1M8l7XCgjXhTXrv2ebvQyd7gEAMIXtuy
|
||||
Rpv75Kga+ydapxLnI1spF0q31eYuA0EJlWBcLd0nnU/e0GuhmqWB7PCFm2JQ3LyY
|
||||
1GFUuvR0/VAoDf06vyicpTIkuCMrSsbSUqNZamyKyfujUgLqbUlfBfkV++q1cFGY
|
||||
MQJBTI6U9qNc4JBxuiHbPENMVLQQAUsSEbdltsaOYV9rMhZn5FdZa/plOuC7Q5+C
|
||||
7SllnnRKWJIl7pQh3bF6NNq3cjtuu3suDipFoFcyo5f06wpxJHbiZH0CAwEAATAN
|
||||
BgkqhkiG9w0BAQsFAAOCAgEAcc7L+4z7B9SESGKeVVZavt6pvApM9JIJ7SsRjuDp
|
||||
I026Ld7LkOp+QyQ30yVhmYCxKYFCaJSngHsusk8/TMr5OU80ixGEzUXzaq82UJkQ
|
||||
zkfMHm7k3/UKkJ4I4pT7lLeG20M+QQVB5a8tTFBGDt3Rkc54nuxKNq5JKwQ0AaHz
|
||||
aid8LhHZwQMOiRMJgaga0bjrKo8I9yTcOvip+4UFitJe0EiSjLKWS4eyrWN7I7t9
|
||||
T1OctNWb8aILnO9SvjREvVFk5rN70F0nOM0PSQGUdO0KfNAuE+2e47avlTHwH+I9
|
||||
0m1PEDcLikcoo0rCC+8tLDvwuBS+GdH+NKQYSdlnik9EU7dSo+cRHDzTS9oU8/Kv
|
||||
81teIY45pUN0AzoySxgjBr8Z/lKiEXhUq/OCZHEl6lOZGtExuwlwr8ZSZNqNFZkh
|
||||
iqNb8p81ER8nbk6U2T6tx7heBFb0hipfp5hywLL7DMffx1iqEdsIEqYJiFPT4oen
|
||||
34zM0vft/zMys8oqvo82sNSeQUwFfq0Tr5IopJSaGjud+x1qqRW8XEsyW5t5YEsk
|
||||
BjEcRgXWHsXOKRGuTdosftJyf6S/3MCysLrkwftOxWNi8Hin55XxzS6nmoGy0Y4y
|
||||
SOvyGjrlkzRdb187E5Exm2gqArTDJgvl0wFegRxqmQnAe0ABdLz/BYm9nt4AUQca
|
||||
ZeY=
|
||||
-----END CERTIFICATE-----
|
54
tests/debug.py
Normal file
54
tests/debug.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
import asyncio
|
||||
import ssl
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
import pathlib
|
||||
|
||||
import kloop
|
||||
|
||||
|
||||
def server():
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
cur = pathlib.Path(__file__).parent
|
||||
ctx.load_cert_chain(cur / "cert.pem", cur / "key.pem")
|
||||
|
||||
ss = socket.socket()
|
||||
ss.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
ss.bind(("localhost", 8088))
|
||||
ss.listen(1)
|
||||
s, addr = ss.accept()
|
||||
s = ctx.wrap_socket(s, server_side=True)
|
||||
print(s)
|
||||
while True:
|
||||
req = s.recv(65536)
|
||||
print("<<<", req)
|
||||
if req == b'Hello':
|
||||
s.send(b'world\n')
|
||||
elif req.startswith(b'Sleep'):
|
||||
time.sleep(float(req.split()[1]))
|
||||
s.send(b'Sleep done\n')
|
||||
elif req == b'Bye':
|
||||
s.send(b'So long!\n')
|
||||
s.close()
|
||||
break
|
||||
else:
|
||||
s.send(b'unknown command\n')
|
||||
|
||||
|
||||
async def main():
|
||||
ctx = ssl.create_default_context(cafile="tests/cert.pem")
|
||||
r, w = await asyncio.open_connection("localhost", 8088, ssl=ctx)
|
||||
print(r, w)
|
||||
w.write(b'Sleep 3')
|
||||
print(await r.readline())
|
||||
w.write(b'Hello')
|
||||
print(await r.readline())
|
||||
|
||||
|
||||
t = threading.Thread(target=server)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
asyncio.set_event_loop_policy(kloop.KLoopPolicy())
|
||||
asyncio.run(main())
|
52
tests/key.pem
Normal file
52
tests/key.pem
Normal file
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCxO2dlXmFrI5NV
|
||||
/KroxuQtFomPi1WhqYxeapN6mqkTBAXFRjzQxSKJNyejF4pMfJOgRw8MEg3EMBDK
|
||||
9hlDJF6jYJJw8QF5iVbxSvoNxnoKyVytAogBASklDpr8PBlP8ZLvWpB9o6SI760p
|
||||
TtNto+8XdrqQr4kU6AFoVuR9xvoR/TFR+GuB+619AUOsXi6nei2UVje2Gh9OCgty
|
||||
nHTg4ch694xYcR6YSLws6v8tFXkwMDCVZKpIe4Ukyf9T189dleK/jnRAg22sFjje
|
||||
f3EA/WxBkvhijBhuRvFaOP+CGnPCqpZnP+zHMetrLLpQlw6RiJj5HOQs9nkYtTUE
|
||||
mVZQo255S1llD6fc45hQYWlt4ac6XpgHWqq822fwu+yaq7SdzeAOwg2fGLgTcoSy
|
||||
0AmWTtfUzyXtcKCNeFNeu/Z5u9DJ3uAQAwhe27JGm/vkqBr7J1qnEucjWykXSrfV
|
||||
5i4DQQmVYFwt3SedT97Qa6GapYHs8IWbYlDcvJjUYVS69HT9UCgN/Tq/KJylMiS4
|
||||
IytKxtJSo1lqbIrJ+6NSAuptSV8F+RX76rVwUZgxAkFMjpT2o1zgkHG6Ids8Q0xU
|
||||
tBABSxIRt2W2xo5hX2syFmfkV1lr+mU64LtDn4LtKWWedEpYkiXulCHdsXo02rdy
|
||||
O267ey4OKkWgVzKjl/TrCnEkduJkfQIDAQABAoICAQCTIsYMGfa2g6pl0IXzCmuU
|
||||
IwnA6eQFekTWfZRCTPPgnY5M4KrMh1zMncWCWKCwLxxAC74qPzK+lUjbtsPyQddv
|
||||
u1TX/r1CsQpE+AHwPb5EBjeySk+uxc/qu91fWmWJQPzDSWR7acfHB3Oyv4Y3l8l+
|
||||
qUrpo1Ei0hmZDcpTwUUKejDf5GUcXF0DUnFkQCxrTbtZUtqdi8JBf4VReSJvMALI
|
||||
U4toQUEtG/VdNRrzysf5gYhT/maAFkvK5wfaC2hUQYMllJZemL4MB8besN6X+8xM
|
||||
BD/BUJ1Xr/3e8ULuOBeynPyQazmfhYn0SAXFsiKeH9EfvySPT2/pW0bQspw+BfBH
|
||||
0IyZXaH0tqvvTLfhfUBB5CtW8KnJ7n2r9QZbX8+Y8WnyD/BmWhAjN/wihph12vNO
|
||||
wxPlA95yKX3KTNDwTCjvtmgNQU+IkujTfBsH9v701ASQcZhScYLQZ6kksiFTagbf
|
||||
Sk9Pk3i2Et465wBMOrPi2GmcPEXLz4IKK0k5ex3WYi3mIBuCZx/vFOOo/K6x637k
|
||||
g5NeunFJg1n3w/igVOCK2sQQWU9bQHwwVlj0YX4ZrUeC+/l3V5xTZVrqn8TWiHfx
|
||||
8N3DF5oEkW/jbEuI1rrNo1X8Ma+Ij2E1rDPYcNh/4/STIg/i4FRM0qYf13cpzgE0
|
||||
XwBrR8BdWhgSZODGuen44QKCAQEA3m8TtziLbxD0Uc/Xe8ax7eHmi1Bvn4G95dgM
|
||||
TI5ZKOVFEIObe2036cwvTvIsJbDi+AbjXpswQdyxwGku9nZRv8jgiMyNGJKMiyPc
|
||||
RQKFkxfvL+SpQnjITFvPiXdXBVUWWKhBjsNXn29iG9a43WI0yJc8DVEzeOMncwvP
|
||||
gErHu8wXSweBmCSwWD8EE7+CLHJyEB7klF314hkYImSDepKAhXZEsQ0u2BYOsmtD
|
||||
Gie4F8U5kDHJgTV1riMEvLYJFjcYxvhcgTaZOCP4GgVUrnZ8zk1PFQjA2Rc3np5b
|
||||
HCL3Q5aCU/V/tDFKXSCY7GoMbqid4GCsF/wQes0/KDgHpDiutwKCAQEAy/oeQWHx
|
||||
xHnuXtbWtAM+E9M/gbHhnZSxcxwexe87kW4a6kDY15GPpe8BvbfIxoafEv8UzKGf
|
||||
kzZnXwXyXVgUX99rz3gcu40ohT0LIBlE+E4j7TT5DXwD9l9Hd3LHbqkykxiRUEW8
|
||||
2G2PL45qGu9JSA5Yh2QgQxRy8BNe/ilzDE14YWnr+5XaLaMDOAAFwpwHQkzEpAqh
|
||||
o2eLJ6BHn0qL328uX30FlnURjKig4Ag8uPuDmf6UIyIqs4IlfnML+x7UKNprTBgl
|
||||
5vctgcD6xt6fYYcA4GHyibJWqPWw2+6wQgEuCSCP5qqIqksj2ageB4nU3acF5mZR
|
||||
V7Mcta2iP9uSawKCAQACbvqrPX4hB/F0V67G1uSpcphAG/AssZGvSw7PY5tMHD4G
|
||||
MTppPkenUimOVo3vF6FUD1c7eL0ta0myjjQKVD8OtxblyD8e7rOE7i6BvsZRVqiy
|
||||
QHEtnf6q/zIlEd4s28Jz09trwW1a/C/5l/7LxUBIlYb9qk7C2tFaq91oTOkkf9Yd
|
||||
Zwae8H/RZ1cXFDhLXjErRftAOErxX07pSWo61BF95E5aFYfWq18bypqgVaDo+apo
|
||||
jnLi6//OmOl7Ww58ZLvrqVnxu/QvLg2P01Ea24cABs5/r2nUtjFQlfDvPN6xqZrh
|
||||
akUsZoGmxS2HIJ3kNAoOI2Ceno5bmZVATmSfAlO/AoIBACtKUSOFOv989Ucxh227
|
||||
BnDEs8S90OlVxndsr2dIx7aszI+M5biSjw8jc5YlLDpeFeK9OlfXsXtX34Z+R5rh
|
||||
96kFTOdCUa7IXaIxe2v7kPS1+M/+HyFqgZjHTe6e8I6e4Dnxy91if5rbXbk1G3XN
|
||||
9RqS2N0bNfGmpGIpak6800r4IiViio/hlOV8pFE9R/uui6fZjR5Xl0iL0C+/x5Oa
|
||||
CaeI/CmN6iKtg+T7YPFLLkAAOUT0j9IJDVD9vSFiH1fTiimFeaIzuc+UBBd2soKE
|
||||
ewUC1v/IKeZpcBT51+hFdyj7AR80xvWomcqymdA26vCkowXLzefBLWBUhjJUwIuW
|
||||
+TMCggEBAIe10HbMp0nbogdAGE28gEpjzkBBeEl/EgMhN8EjemrLmjWI4rK3+Nyh
|
||||
Jo6W4QG4dT9C7KUP9RzDOoF4kkaFaPZ/KqlSn2qUVYLf9N4s95qQvGlVFIjy5tEp
|
||||
ChYdiuP71DNXCjolQIq7nb1zkgbTbvuwVSnw2QwQnYdkZMulgjVrvsK55jrOnsuO
|
||||
EMxjVrlNxeUrb2zLaBsfA5uTPgKgtDGmTDMx+wFAay5jKc9Zmgr1+z834bCKStAh
|
||||
7gcRoVAjkiu2hoNxBTXgqtFtCizyajiiUUYfFAswJ9RLv7qdrqnpuCv1cffrALmd
|
||||
PcTNbfrOMsRvyHwyfUIHDaBT62YTztQ=
|
||||
-----END PRIVATE KEY-----
|
|
@ -1,9 +1,58 @@
|
|||
# Copyright (c) 2022 Fantix King https://fantix.pro
|
||||
# kLoop is licensed under Mulan PSL v2.
|
||||
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
# You may obtain a copy of Mulan PSL v2 at:
|
||||
# http://license.coscl.org.cn/MulanPSL2
|
||||
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
# See the Mulan PSL v2 for more details.
|
||||
|
||||
import asyncio
|
||||
import ssl
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from kloop import uring, ktls
|
||||
import kloop
|
||||
|
||||
|
||||
class TestLoop(unittest.TestCase):
|
||||
def test_loop(self):
|
||||
self.assertIsNotNone(uring)
|
||||
self.assertIsNotNone(ktls)
|
||||
def setUp(self):
|
||||
asyncio.set_event_loop_policy(kloop.KLoopPolicy())
|
||||
self.loop = asyncio.new_event_loop()
|
||||
|
||||
def tearDown(self):
|
||||
self.loop.close()
|
||||
asyncio.set_event_loop_policy(None)
|
||||
|
||||
def test_call_soon(self):
|
||||
self.loop.call_soon(self.loop.stop)
|
||||
self.loop.run_forever()
|
||||
|
||||
def test_call_later(self):
|
||||
secs = 0.1
|
||||
self.loop.call_later(secs, self.loop.stop)
|
||||
start = time.monotonic()
|
||||
self.loop.run_forever()
|
||||
self.assertGreaterEqual(time.monotonic() - start, secs)
|
||||
|
||||
def test_connect(self):
|
||||
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||
# ctx.check_hostname = False
|
||||
# ctx.verify_mode = ssl.CERT_NONE
|
||||
# ctx.minimum_version = ssl.TLSVersion.TLSv1_3
|
||||
host = "www.google.com"
|
||||
r, w = self.loop.run_until_complete(
|
||||
# asyncio.open_connection("127.0.0.1", 8080, ssl=ctx)
|
||||
asyncio.open_connection(host, 443, ssl=ctx)
|
||||
)
|
||||
self.loop.run_until_complete(asyncio.sleep(1))
|
||||
print('send request')
|
||||
w.write(b"GET / HTTP/1.1\r\n" +
|
||||
f"Host: {host}\r\n".encode("ISO-8859-1") +
|
||||
b"Connection: close\r\n"
|
||||
b"\r\n")
|
||||
while line := self.loop.run_until_complete(r.read()):
|
||||
print(line)
|
||||
w.close()
|
||||
self.loop.run_until_complete(w.wait_closed())
|
||||
|
|
Loading…
Reference in a new issue