summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author张杨 <[email protected]>2023-10-18 03:35:17 +0000
committer陆秋文 <[email protected]>2023-10-18 03:35:17 +0000
commitecc6d08170d6f4914fd90bf9fe574c657546cfae (patch)
treeb1a45b2f671d3213347be268c5787eebba1ff5b8
parent36450f5dfa230ef806151101ee8047375915ad73 (diff)
merge bind-rs-timeoutHEADmain
-rw-r--r--.gitignore2
-rw-r--r--Cargo.toml6
-rw-r--r--bindings/rs-timeout/.gitignore8
-rw-r--r--bindings/rs-timeout/Cargo.toml13
-rw-r--r--bindings/rs-timeout/README.md163
-rw-r--r--bindings/rs-timeout/README.zh_cn.md163
-rw-r--r--bindings/rs-timeout/build.rs52
-rw-r--r--bindings/rs-timeout/src/lib.rs4
-rw-r--r--bindings/rs-timeout/src/timeout.rs634
-rw-r--r--bindings/rs-timeout/src/timeout_bind.rs246
-rw-r--r--bindings/rs-timeout/src/timeout_bind_test.rs266
-rw-r--r--bindings/rs-timeout/tests/test_timeout.rs793
-rw-r--r--bindings/rs-timeout/timeout/Makefile79
-rw-r--r--bindings/rs-timeout/timeout/Rules.shrc40
-rw-r--r--bindings/rs-timeout/timeout/bench/Rules.mk49
-rw-r--r--bindings/rs-timeout/timeout/bench/bench-add.lua30
-rw-r--r--bindings/rs-timeout/timeout/bench/bench-aux.lua30
-rw-r--r--bindings/rs-timeout/timeout/bench/bench-del.lua25
-rw-r--r--bindings/rs-timeout/timeout/bench/bench-expire.lua29
-rw-r--r--bindings/rs-timeout/timeout/bench/bench-heap.c236
-rw-r--r--bindings/rs-timeout/timeout/bench/bench-llrb.c425
-rw-r--r--bindings/rs-timeout/timeout/bench/bench-wheel.c81
-rw-r--r--bindings/rs-timeout/timeout/bench/bench.c293
-rw-r--r--bindings/rs-timeout/timeout/bench/bench.h11
-rw-r--r--bindings/rs-timeout/timeout/bench/bench.plt19
-rw-r--r--bindings/rs-timeout/timeout/libtimeout.abin0 -> 25780 bytes
-rw-r--r--bindings/rs-timeout/timeout/lua/Rules.mk20
-rw-r--r--bindings/rs-timeout/timeout/lua/timeout-lua.c396
-rw-r--r--bindings/rs-timeout/timeout/test-timeoutbin0 -> 47088 bytes
-rw-r--r--bindings/rs-timeout/timeout/test-timeout.c530
-rw-r--r--bindings/rs-timeout/timeout/timeout-bitops.c249
-rw-r--r--bindings/rs-timeout/timeout/timeout-debug.h77
-rw-r--r--bindings/rs-timeout/timeout/timeout.c744
-rw-r--r--bindings/rs-timeout/timeout/timeout.h253
34 files changed, 5964 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 4fffb2f..995d172 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
/target
/Cargo.lock
+.devcontainer/
+.vscode/
diff --git a/Cargo.toml b/Cargo.toml
index 022b102..178679d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,11 +7,13 @@ edition = "2021"
[workspace]
members = [
- "bindings/marsio"
+ "bindings/rs-timeout",
+ "bindings/rs-dablooms",
]
[dependencies]
-marsio = {path="bindings/marsio"}
+rs-dablooms = {path="bindings/rs-dablooms"}
+rs-timeout = {path="bindings/rs-timeout"}
nom = "7"
chrono = "0.4"
pcap = "0.11" \ No newline at end of file
diff --git a/bindings/rs-timeout/.gitignore b/bindings/rs-timeout/.gitignore
new file mode 100644
index 0000000..e49b6bf
--- /dev/null
+++ b/bindings/rs-timeout/.gitignore
@@ -0,0 +1,8 @@
+*.o
+*.gcda
+*.gcov
+*.gcno
+*~
+*.so
+*.dSYM
+target
diff --git a/bindings/rs-timeout/Cargo.toml b/bindings/rs-timeout/Cargo.toml
new file mode 100644
index 0000000..67dd982
--- /dev/null
+++ b/bindings/rs-timeout/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "rs-timeout"
+version = "0.1.0"
+edition = "2021"
+
+build = "./build.rs"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+libc = "0.2.0"
+bindgen = "0.68.1"
+rand = "0.8.5"
diff --git a/bindings/rs-timeout/README.md b/bindings/rs-timeout/README.md
new file mode 100644
index 0000000..642f71e
--- /dev/null
+++ b/bindings/rs-timeout/README.md
@@ -0,0 +1,163 @@
+# bind-rs-timeout
+
+Rust binding for [timeout](https://github.com/wahern/timeout).
+
+It mainly includes two modules: `Timeout` and `TimeoutManager`. They respectively represent a timeout instance and a timeout manager.
+- `Timeout` is a timeout instance used to set the timeout time and bind the callback function (supporting closures).
+- `TimeoutManager` is used to manage timeout instances, update time, and trigger timeouts.
+
+## Usage
+
+To use the `Timeout` structure, you need to first create a `Timeout` instance and set the timeout callback function. Then add it to the `TimeoutManager` (ownership transferred to the C-side `TimeoutManager`), continuously update the `TimeoutManager` time, and call the `TimeoutManager::expired_timeouts` method to return the expired `Timeout` and execute the corresponding timeout callback function.
+
+```rust
+let to = Timeout::new(ToType::Default).unwrap(); // onetime type, relative timeout
+let to2 = Timeout::new(ToType::INT).unwrap(); // periodic type
+let mut tos = TimeoutManager::new(TIMEOUT_mHZ).unwrap();
+
+tos.update_time_rel(0); // relative time, tos.now = 0
+tos.add(to, 50); // onetime, expired time = tos.now + 50
+tos.add(to2, 100); // periodic, starting from tos.now, expiring every 100.
+
+tos.update_time_abs(150); // absolute time, tos.now = 150
+
+for to in tos.expired_timeouts() {
+ match to {
+ ToR::OneTime(temp) => {
+ // onetime type, Timeout, do something
+ // all flag has cleared assert!(to.is_expired()); will error
+ temp.run_cb();
+ }
+ ToR::Periodic(temp) => {
+ //periodic type, &Timeout, do something
+ // assert!(temp.is_periodic()); will work
+ }
+ };
+}
+```
+
+## Timeout
+
+`Timeout` is a structure representing a timeout instance used to set the timeout time. The structure contains multiple methods for setting the timeout callback function (supporting closures), checking the timeout status, deleting the timeout operation, etc.
+
+`Timeout` has three types:
+- `ToType::INT`: periodic timer, triggered repeatedly at a fixed interval, independent between each trigger, missing a trigger does not affect the next trigger time.
+- `ToType::ABS`: one-time timer, triggered once at the specified absolute time.
+- `ToType::Default`: one-time timer, triggered once after the specified relative time after adding the timer time point.
+
+### Method List
+
+The following is a list of methods for the `Timeout` structure:
+
+- `new(flags: ToType) -> Result<Timeout, &'static str>`: Create a new `Timeout` instance and set the timeout flag.
+- `is_pending(&self) -> bool`: Check whether the timeout has been registered and is on the timing wheel.
+- `is_expired(&self) -> bool`: Check whether the timeout has been registered and is on the expiration queue.
+- `is_periodic(&self) -> bool`: Check whether the timeout is periodic.
+- `delete(&mut self)`: Delete the timeout from the timing wheel or expiration queue.
+- `set_cb<T>(&mut self, cb: TimeoutCallBack<T>)`: Set the timeout callback function.
+- `set_cb_closure<F>(&mut self, closure: &mut F) where F: FnMut()`: Pass in a closure to set the timeout callback.
+- `run_cb(&self) -> bool`: Run the timeout callback function and return whether it has been executed successfully.
+
+### Adding Callbacks
+
+`Timeout` supports adding callbacks, which will be called by `run_cb` after expiration.
+
+The callback function of `Timeout` supports passing in C-compatible functions and `FnMut()` type closures.
+- When passing in a function, the function parameter is of type `&mut T`, and the return value is `()`.
+- When passing in a closure, the input parameter is `()`, and the return value is `()`.
+
+```rust
+pub struct Session { pub session_id: String,}
+let mut timeout = Timeout::new(ToType::Default).unwrap();
+
+extern "C" fn rust_callback(arg: &mut i32) {
+ println!("Callback executed with arg: {}", arg);
+}
+
+let mut binding = 11;
+let cb = TimeoutCallBack::new(rust_callback, &mut binding);
+
+timeout.set_cb(cb);
+timeout.run_cb();
+
+let mut session = Session { session_id: "123".to_string(),};
+
+timeout.set_cb_closure(&mut || {
+ println!("Callback executed with session_id: {}", session.session_id);
+ session.session_id = "456".to_string();
+ println!("Callback executed with session_id: {}", session.session_id);
+});
+
+timeout.run_cb();
+```
+
+Please note the lifetime of the callback.
+- When passing in a function, it is necessary to ensure that the lifetime of the function and its parameters is longer than the lifetime of `Timeout`, otherwise calling it will return false, indicating that the call failed.
+- When passing in a closure, it is necessary to ensure that the lifetime of the closure and its calling variables is longer than the lifetime of `Timeout`, otherwise calling it will return false, indicating that the call failed.
+
+## TimeoutManager
+
+`TimeoutManager` provides a `TimeoutManager` structure used to manage timeout operations. The structure contains multiple methods for creating, adding, deleting, and updating timeout operations.
+
+### Method List
+
+The following is a list of methods for the `TimeoutManager` structure:
+
+- `new(hz_set: timeout_t) -> Result<TimeoutManager, &'static str>`: Create a new `TimeoutManager` instance.
+- `close(&mut self)`: Close the `TimeoutManager` instance.
+- `update_time_rel(&mut self, time: timeout_t)`: Update the time, now = now + relative time.
+- `update_time_abs(&mut self, current_time: timeout_t)`: Update the time, now = absolute time.
+- `get_hz(&self) -> timeout_t`: Get the frequency of `TimeoutManager`.
+- `get_next_wait_time(&self) -> timeout_t`: Get the waiting time before the next timeout expires (definitely less than the actual waiting time). Returns `u64::MAX` if there is no waiting time.
+- `any_pending(&self) -> bool`: Check whether any timeout is waiting.
+- `any_expired(&self) -> bool`: Check whether any timeout has expired.
+- `check(&self) -> bool`: Check whether `TimeoutManager` is valid.
+
+Methods related to `Timeout`:
+
+- `add(&mut self, to: Timeout, timeout: timeout_t)`: Add a timeout, and transfer ownership to the C-side `TimeoutManager`. Depending on the type of the `to` parameter, the `timeout` parameter will have different effects.
+ - For the `ToType::INT` type, the `timeout` parameter is the periodic interval time. The start time is the current time of `TimeoutManager`.
+ - For the `ToType::ABS` type, it is triggered once at the specified absolute time.
+ - For the `ToType::Default` type, it is triggered once after the specified relative time after adding the timer time point.
+- `delete(&mut self, to: &mut Timeout)`: Delete the timeout.
+- `expired_timeouts(&mut self) -> ExpireIter`: Get the expired timeouts and return an iterator.
+ - For the corresponding `Timeout`, two types are returned: `ToR::OneTime` and `ToR::Periodic`.
+ - For non-periodic `Timeout`, the returned type is `Timeout`, and ownership is transferred to Rust.
+ - For periodic `Timeout`, its ownership is still on the C-side, so `&Timeout` type is returned.
+- `next_timeouts_pending(&mut self) -> NextIter`: Get the waiting `&Timeout` and return an iterator.
+- `next_timeouts_expired(&mut self) -> NextIter`: Get the expired `&Timeout` and return an iterator.
+- `next_timeouts_all(&mut self) -> NextIter`: Get all `&Timeout` and return an iterator.
+
+Note: `next_timeouts_xxx` is only a query method and does not perform any operations on the waiting or expired queues.
+
+Simple example
+
+```rust
+xxx
+let mut tos = TimeoutManager::new(TIMEOUT_mHZ).unwrap();
+xxx
+
+for to in tos.expired_timeouts() {
+ match to {
+ ToR::OneTime(temp) => {
+ // onetime type, Timeout, do something
+ temp.run_cb();
+ }
+ ToR::Periodic(temp) => {
+ //periodic type, &Timeout, do something
+ // temp.run_cb();
+ }
+ };
+}
+
+for to_ptr in tos.next_timeouts_expired() {
+ // do something
+ to_ptr.run_cb();
+}
+```
+
+## Others
+
+For periodic `Timeout`, its ownership is on the C side from the beginning of adding to `TimeoutManager` until `TimeoutManager` is closed. Therefore, the ownership of periodic `Timeout` cannot be obtained on the Rust side, only its reference can be obtained.
+
+A separate `delete_to_periodic(to: &Timeout)` is provided to remove periodic `Timeout` from `TimeoutManager`. \ No newline at end of file
diff --git a/bindings/rs-timeout/README.zh_cn.md b/bindings/rs-timeout/README.zh_cn.md
new file mode 100644
index 0000000..2c15249
--- /dev/null
+++ b/bindings/rs-timeout/README.zh_cn.md
@@ -0,0 +1,163 @@
+# bind-rs-timeout
+
+[timeout](https://github.com/wahern/timeout) 的 Rust binding.
+
+主要包括 `Timeout` 和 `TimeoutManager` 两个模块. 分别是 timeout 实例和 timeout 管理.
+- `Timeout` 是 timeout 实例,用于设置超时时间,可以绑定回调函数(支持闭包).
+- `TimeoutManager` 用于管理 timeout 实例, 更新时间, 触发超时等.
+
+使用方法
+- 首先需要创建一个 `Timeout` 实例,并设置超时回调函数.将其添加到 `TimeoutManager` 中(所有权转移到 C 端的`TimeoutManager`),不断更新 `TimeoutManager` 时间,调用 `TimeoutManager::expired_timeouts` 方法返回已过期的 `Timeout`, 执行对应超时回调函数.
+
+```rust
+let to = Timeout::new(ToType::Default).unwrap(); // onetime type, relative timeout
+let to2 = Timeout::new(ToType::INT).unwrap(); // periodic type
+let mut tos = TimeoutManager::new(TIMEOUT_mHZ).unwrap();
+
+tos.update_time_rel(0); // relative time, tos.now = 0
+tos.add(to, 50); // onetime, expired time = tos.now + 50
+tos.add(to2, 100); // periodic, starting from tos.now, expiring every 100.
+
+tos.update_time_abs(150); // absolute time, tos.now = 150
+
+for to in tos.expired_timeouts() {
+ match to {
+ ToR::OneTime(temp) => {
+ // onetime type, Timeout, do something
+ // all flag has cleared assert!(to.is_expired()); will error
+ temp.run_cb();
+ }
+ ToR::Periodic(temp) => {
+ //periodic type, &Timeout, do something
+ // assert!(temp.is_periodic()); will work
+ }
+ };
+}
+```
+
+## Timeout
+
+`Timeout` 是 timeout 实例的结构体,用于设置超时时间.该结构体包含了多个方法, 用于设置超时回调函数(支持闭包(FnMut 类型))、检查超时状态、删除超时操作等.
+
+`Timeout` 有 3 种类型
+- `ToType::INT`: 周期性的定时器, 间隔固定时间重复触发, 每次触发之间独立, 错过某次触发不影响下次触发时间.
+- `ToType::ABS`: 一次性定时器, 在指定绝对时间触发一次.
+- `ToType::Default`: 一次性定时器, 添加定时器时间点后,经过指定相对时间后触发一次.
+
+### 方法列表
+
+以下是 `Timeout` 结构体的方法列表:
+
+- `new(flags: ToType) -> Result<Timeout, &'static str>`: 创建一个新的 `Timeout` 实例,并设置超时标志.
+- `is_pending(&self) -> bool`: 检查超时是否已经注册并在计时轮上
+- `is_expired(&self) -> bool`: 检查超时是否已经注册并在过期队列上
+- `is_periodic(&self) -> bool`: 检查超时是否是周期性的
+- `delete(&mut self)`: 从计时轮或过期队列中删除超时
+- `set_cb<T>(&mut self, cb: TimeoutCallBack<T>)`: 设置超时回调函数
+- `set_cb_closure<F>(&mut self, closure: &mut F) where F: FnMut()`: 传入闭包,设置超时回调.
+- `run_cb(&self) -> bool`: 运行超时回调函数,并返回是否已经执行成功.
+
+
+### 添加回调
+
+`Timeout` 支持添加回调,在过期后自行调用 `run_cb` 执行回调.
+
+`Timeout` 的回调函数支持传入 C 兼容的函数,也支持传入 `FnMut()` 类型的闭包.
+- 传入函数限定,函数参数为 `&mut T` 类型,返回值为 `()`.
+- 传入闭包限定,入参为 `()`,返回值为 `()`.
+
+```rust
+pub struct Session { pub session_id: String,}
+let mut timeout = Timeout::new(ToType::Default).unwrap();
+
+extern "C" fn rust_callback(arg: &mut i32) {
+ println!("Callback executed with arg: {}", arg);
+}
+
+let mut binding = 11;
+let cb = TimeoutCallBack::new(rust_callback, &mut binding);
+
+timeout.set_cb(cb);
+timeout.run_cb();
+
+let mut session = Session { session_id: "123".to_string(),};
+
+timeout.set_cb_closure(&mut || {
+ println!("Callback executed with session_id: {}", session.session_id);
+ session.session_id = "456".to_string();
+ println!("Callback executed with session_id: {}", session.session_id);
+});
+
+timeout.run_cb();
+```
+
+请注意回调的生命周期.
+- 传入函数时,需要保证 函数及其参数 的生命周期长于 `Timeout` 的生命周期,否则调用时会返回 false 表示调用失败.
+- 传入闭包时,需要保证 闭包及其调用变量 的生命周期长于 `Timeout` 的生命周期,否则调用时会返回 false 表示调用失败.
+
+## TimeoutManager
+
+`TimeoutManager` 提供了一个 `TimeoutManager` 结构体,用于管理超时操作.该结构体包含了多个方法,用于创建、添加、删除、更新超时操作等.
+
+### 方法列表
+
+以下是 `TimeoutManager` 结构体的方法列表:
+
+- `new(hz_set: timeout_t) -> Result<TimeoutManager, &'static str>`: 创建一个新的 `TimeoutManager` 实例
+- `close(&mut self)`: 关闭 `TimeoutManager` 实例.
+- `update_time_rel(&mut self, time: timeout_t)`: 更新时间,now = now + 相对时间,
+- `update_time_abs(&mut self, current_time: timeout_t)`: 更新时间,now = 绝对时间.
+- `get_hz(&self) -> timeout_t`: 获取 `TimeoutManager` 的频率.
+- `get_next_wait_time(&self) -> timeout_t`: 获取下一个超时过期前的等待时间(一定小于实际的等待时间).没有等待时返回 `u64::MAX`.
+- `any_pending(&self) -> bool`: 检查是否有 Timeout 正在等待.
+- `any_expired(&self) -> bool`: 检查是否有 Timeout 已经过期.
+- `check(&self) -> bool`: 检查 `TimeoutManager` 是否有效.
+
+与 `Timeout` 相关的方法:
+
+- `add(&mut self, to: Timeout, timeout: timeout_t)`: 添加 Timeout,所有权转移到 C 端的 `TimeoutManager`, 依照参数 to 的类型,参数 timeout 会有不同的效果.
+ - `ToType::INT` 类型,参数 timeout 为周期性的间隔时间.开始时间为 `TimeoutManager` 的当前时间.
+ - `ToType::ABS` 类型,在参数 timeout 指定的绝对时间触发一次.
+ - `ToType::Default` 类型,在参数 timeout + TimeoutManger.now 的时间触发一次.
+- `delete(&mut self, to: &mut Timeout)`: 删除 Timeout.
+- `expired_timeouts(&mut self) -> ExpireIter`: 获取已过期的 Timeout, 返回迭代器.
+ - 对应 `Timeout`, 返回的包括两种类型: `ToR::OneTime` 和 `ToR::Periodic`.
+ - 非周期的 `Timeout` 对应 `ToR::OneTime`,返回 `Timeout` 类型,所有权转移到 Rust 中.
+ - 周期的 `Timeout` 对应 `ToR::Periodic`,其所有权还在 C 端,因此返回 `&Timeout` 类型.
+- `next_timeouts_pending(&mut self) -> NextIter`: 获取等待的 &Timeout,返回迭代器.
+- `next_timeouts_expired(&mut self) -> NextIter`: 获取过期的 &Timeout,返回迭代器.
+- `next_timeouts_all(&mut self) -> NextIter`: 获取全部的 &Timeout,返回迭代器.
+
+备注: `next_timeouts_xxx` 仅为查询方法,并不会对等待或过期队列进行任何操作.
+
+简单示例
+
+```rust
+xxx
+let mut tos = TimeoutManager::new(TIMEOUT_mHZ).unwrap();
+xxx
+
+for to in tos.expired_timeouts() {
+ match to {
+ ToR::OneTime(temp) => {
+ // onetime type, Timeout, do something
+ temp.run_cb();
+ }
+ ToR::Periodic(temp) => {
+ //periodic type, &Timeout, do something
+ // temp.run_cb();
+ }
+ };
+}
+
+for to_ptr in tos.next_timeouts_expired() {
+ // do something
+ to_ptr.run_cb();
+}
+```
+
+## 其他
+
+对于周期性 `Timeout` 其所有权自添加到 `TimeoutManager` 开始,直到 `TimeoutManager` 关闭,都在 C 端.因此,在 Rust 端无法获取周期性 `Timeout` 的所有权,只能获取其引用.
+
+单独提供了 `delete_to_periodic(to: &Timeout)` 用于从 `TimeoutManager` 中删除周期性 `Timeout`. \ No newline at end of file
diff --git a/bindings/rs-timeout/build.rs b/bindings/rs-timeout/build.rs
new file mode 100644
index 0000000..bdefe22
--- /dev/null
+++ b/bindings/rs-timeout/build.rs
@@ -0,0 +1,52 @@
+use std::env;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+fn main() {
+ // Compile C source file
+ let output = Command::new("make")
+ // .arg("so") // 生成动态库
+ .arg("static") // 生成静态库
+ .current_dir("./timeout")
+ .output()
+ .expect("Failed to compile C source file");
+
+ if !output.status.success() {
+ panic!(
+ "Failed to compile C source file: {}",
+ String::from_utf8_lossy(&output.stderr)
+ );
+ }
+
+ // // Add shared library path to system library search path
+ // let output = Command::new("sh")
+ // .arg("-c")
+ // .arg("export LD_LIBRARY_PATH=/workspaces/rs-timeout/timeout:$LD_LIBRARY_PATH")
+ // .output()
+ // .expect("Failed to add shared library path to system library search path");
+
+ // if !output.status.success() {
+ // panic!(
+ // "Failed to add shared library path to system library search path: {}",
+ // String::from_utf8_lossy(&output.stderr)
+ // );
+ // }
+
+ // let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+ // println!(
+ // "cargo:rustc-link-search=native={}",
+ // Path::new(&dir).join("timeout").display()
+ // );
+ // Link shared library
+ // let root = String::from(env::var_os("CARGO_MANIFEST_DIR").unwrap().to_str().unwrap());
+ // // 将 /lib 添加到链接搜索目录
+ // println!("cargo:rustc-link-search=native={}", format!("{}/timeout", &root));
+
+ // println!("cargo:rustc-link-lib=timeout");
+ // // println!("cargo:rustc-link-search=native=./timeout");
+ // println!("cargo:rerun-if-changed=./timeout");
+
+ // let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+ println!("cargo:rustc-link-search=native=./bindings/rs-timeout/timeout");
+ println!("cargo:rustc-link-lib=static=timeout");
+}
diff --git a/bindings/rs-timeout/src/lib.rs b/bindings/rs-timeout/src/lib.rs
new file mode 100644
index 0000000..7c853bc
--- /dev/null
+++ b/bindings/rs-timeout/src/lib.rs
@@ -0,0 +1,4 @@
+pub mod timeout;
+pub mod timeout_bind;
+mod timeout_bind_test;
+// mod test_timeout; \ No newline at end of file
diff --git a/bindings/rs-timeout/src/timeout.rs b/bindings/rs-timeout/src/timeout.rs
new file mode 100644
index 0000000..d3382d4
--- /dev/null
+++ b/bindings/rs-timeout/src/timeout.rs
@@ -0,0 +1,634 @@
+use std::{panic, ptr::NonNull};
+
+use libc::c_void;
+
+use crate::timeout_bind::*;
+
+/// timeout type:
+/// - INT: Periodic timeout, start time only supports relative time.
+/// - ABS: Executed only once, start time is absolute time.
+/// - Default: Executed only once, start time is relative time
+/// ToType could use as i32
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum ToType {
+ INT = TIMEOUT_INT as isize, // periodic timeout, relative time
+ ABS = TIMEOUT_ABS as isize, // onetime timeout, absolute time
+ Default = TIMEOUT_DEFAULT as isize, // onetime timeout, relative time
+}
+// as i32
+impl From<ToType> for i32 {
+ fn from(timeout_type: ToType) -> Self {
+ timeout_type as i32
+ }
+}
+
+impl ToType {
+ // i32 -> ToType
+ fn new(flag: i32) -> Self {
+ match flag {
+ TIMEOUT_INT => ToType::INT,
+ TIMEOUT_ABS => ToType::ABS,
+ _ => ToType::Default,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct TimeoutCallBack<'a, T> {
+ pub fn_ptr: Option<extern "C" fn(arg: &mut T)>,
+ pub arg: &'a mut T,
+}
+
+/// TimeoutCallBack<T> -> timeout_cb
+impl<T> From<TimeoutCallBack<'_, T>> for timeout_cb {
+ fn from(timeout_cb: TimeoutCallBack<T>) -> Self {
+ timeout_cb {
+ fn_ptr: timeout_cb.fn_ptr.map(|f| unsafe { std::mem::transmute(f) }),
+ arg: timeout_cb.arg as *mut T as *mut c_void,
+ }
+ }
+}
+
+// type TimeoutCallBack = timeout_cb; // callback
+impl<'a, T> TimeoutCallBack<'a, T> {
+ pub fn new(callback: extern "C" fn(arg: &mut T), arg: &'a mut T) -> Self {
+ let fn_ptr = Some(callback);
+ TimeoutCallBack { fn_ptr, arg }
+ }
+}
+
+impl timeout_cb {
+ fn call(&self) -> bool {
+ if let Some(callback) = self.fn_ptr {
+ let result = panic::catch_unwind(|| unsafe {
+ callback(self.arg);
+ });
+ if result.is_ok() {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+/// on C side: callback function is hook<F>, arg is closure.
+/// run callback is run hook<F>, make hook like a exec for closure.
+unsafe extern "C" fn hook<F>(arg: *mut c_void)
+where
+ F: FnMut(),
+{
+ let closure = &mut *(arg as *mut F);
+ closure();
+}
+
+/// return callback exec function
+/// without the actual effect, only the compiler associated HOOK and F
+pub(crate) fn get_cb_exec<F>(_closure: &F) -> unsafe extern "C" fn(arg: *mut c_void)
+where
+ F: FnMut(), // closure
+{
+ hook::<F>
+}
+
+type Timeout = timeout;
+
+impl Timeout {
+ pub fn new(flags: ToType) -> Result<Timeout, &'static str> {
+ // timeout instantiated in rust rather than C side
+ let raw = Box::into_raw(Box::new(timeout::default()));
+ // Box::into_raw let instance ownership transfer to C
+ let raw = unsafe { timeout_init(raw, flags as i32) };
+ if raw.is_null() {
+ return Err("Failed to create Timeout");
+ }
+ // Box::from_raw: instance ownership from C side to rust.
+ Ok(*unsafe { Box::from_raw(raw) })
+ }
+ /// transfer *timeout to Timeout
+ /// instance ownership form C side to rust
+ pub(crate) fn transfer(to: *mut timeout) -> Timeout {
+ *unsafe { Box::from_raw(to) }
+ }
+ /// set callback
+ pub fn set_cb<T>(&mut self, cb: TimeoutCallBack<T>) {
+ self.callback = cb.into();
+ }
+
+ /// set callback by timeout_cb
+ /// for test
+ pub(crate) fn set_cb_raw(&mut self, cb: timeout_cb) {
+ self.callback = cb.into();
+ }
+
+ /// set closure
+ pub fn set_cb_closure<F>(&mut self, closure: &mut F)
+ where
+ F: FnMut(),
+ {
+ // let (cb_exec, mut cb) = make_cb!(closure);
+ let cb_exec = get_cb_exec(closure);
+ let exec_ptr = Box::into_raw(Box::new(cb_exec));
+
+ let to_cb = timeout_cb {
+ fn_ptr: Some(unsafe { *exec_ptr }),
+ arg: closure as *mut _ as *mut c_void,
+ };
+ self.callback = to_cb;
+ }
+
+ /// return true if timeout is registered and on timing wheel
+ pub fn is_pending(&self) -> bool {
+ //C code: return to->pending && to->pending != &to->timeouts->expired;
+ if !self.pending.is_null() // pending not null
+ && !self.timeouts.is_null() // timeouts not null
+ && self.pending != (unsafe { &mut (*self.timeouts).expired // pending not expired
+ })
+ {
+ return true;
+ }
+ false
+ }
+
+ /// return true if timeout is registered and on expired queue
+ pub fn is_expired(&self) -> bool {
+ //return to->pending && to->pending == &to->timeouts->expired;
+ if !self.pending.is_null() // pending not null
+ && !self.timeouts.is_null() // timeouts not null
+ && self.pending == (unsafe { &mut (*self.timeouts).expired // pending is expired
+ }) {
+ return true;
+ }
+ false
+ }
+ /// return true if timeout is periodic
+ pub fn is_periodic(&self) -> bool {
+ // if ((to->flags & TIMEOUT_INT) && to->interval > 0)
+ return (self.flags & TIMEOUT_INT != 0) && (self.interval > 0);
+ }
+
+ /// remove timeout from any timing wheel or expired queue (okay if on neither)
+ pub fn delete(&mut self) {
+ unsafe { timeout_del(self) };
+ }
+
+ /// return true if cb is not null and callback has executed
+ pub fn run_cb(&self) -> bool {
+ let cb = self.callback;
+ return cb.call();
+ }
+}
+
+impl Drop for Timeout {
+ fn drop(&mut self) {
+ self.delete(); // delete
+ }
+}
+
+impl Timeout {
+ /// create periodic timeout
+ pub fn new_int() -> Result<Timeout, &'static str> {
+ Timeout::new(ToType::INT)
+ }
+ // create onetime timeout with absolute time
+ pub fn new_abs() -> Result<Timeout, &'static str> {
+ Timeout::new(ToType::ABS)
+ }
+ /// create onetime timeout with relative time
+ pub fn new_default() -> Result<Timeout, &'static str> {
+ Timeout::new(ToType::Default)
+ }
+}
+
+/// delete periodic timeout
+/// No ownership of the Timeout object is passed in
+pub fn delete_to_periodic(to: &Timeout) {
+ if to.is_periodic() {
+ unsafe { timeout_del(to as *const _ as *mut timeout) };
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum TomItType {
+ PENDING = TIMEOUTS_PENDING as isize,
+ EXPIRED = TIMEOUTS_EXPIRED as isize,
+ ALL = TIMEOUTS_ALL as isize,
+ // CLEAR = TIMEOUTS_CLEAR as isize,
+}
+// as i32
+impl From<TomItType> for i32 {
+ fn from(flag: TomItType) -> Self {
+ flag as i32
+ }
+}
+
+impl TomItType {
+ pub fn new(flag: i32) -> Self {
+ match flag {
+ TIMEOUTS_PENDING => TomItType::PENDING,
+ TIMEOUTS_EXPIRED => TomItType::EXPIRED,
+ TIMEOUTS_ALL => TomItType::ALL,
+ // TIMEOUTS_CLEAR => TimeoutSItFlag::CLEAR, // CLEAR means clear all expired timeout on expire queue.
+ // this creates complications in ownership
+ _ => TomItType::ALL,
+ }
+ }
+}
+
+type TomIt = timeouts_it;
+
+impl TomIt {
+ /// flag has 3 value: PENDING, EXPIRED, ALL
+ fn new(flags: TomItType) -> TomIt {
+ let mut instance = timeouts_it::default();
+ TIMEOUTS_IT_INIT(&mut instance, flags as i32);
+ instance
+ }
+}
+
+/// expired timeout return type
+pub enum ToR {
+ OneTime(Timeout), // instance ownership from C side to rust
+ Periodic(&'static Timeout), // instance ownership still on C side
+}
+
+// TimeoutManager
+pub struct TimeoutManager {
+ tos: NonNull<timeouts>,
+}
+
+impl TimeoutManager {
+ /// TIMEOUT_mHZ 1000; TIMEOUT_uHZ 1000000; TIMEOUT_nHZ 1000000000;
+ /// if hz_set = 0, default hz_set = TIMEOUT_mHZ
+ pub fn new(hz_set: timeout_t) -> Result<TimeoutManager, &'static str> {
+ let mut err = 0 as usize;
+ // if hz_set = 0, default set to TIMEOUT_mHZ (timeouts_open)
+ let tos = unsafe { timeouts_open(hz_set, &mut err) };
+ if err != 0 {
+ return Err("Failed to create timeout manager, null");
+ }
+ let tos = NonNull::new(tos).ok_or("Failed to create timeout manager, null")?;
+ Ok(TimeoutManager { tos })
+ }
+
+ // get raw pointer
+ fn get_raw(&self) -> *const timeouts {
+ self.tos.as_ptr()
+ }
+
+ // get raw mut pointer
+ fn get_raw_mut(&self) -> *mut timeouts {
+ self.tos.as_ptr()
+ }
+
+ // close
+ pub fn close(&mut self) {
+ unsafe {
+ timeouts_close(self.get_raw_mut());
+ }
+ }
+
+ fn update_time(&mut self, time: timeout_t, timeout_type: ToType) {
+ match timeout_type {
+ ToType::INT => unsafe { timeouts_step(self.get_raw_mut(), time) },
+ ToType::ABS => unsafe { timeouts_update(self.get_raw_mut(), time) },
+ ToType::Default => unsafe { timeouts_step(self.get_raw_mut(), time) },
+ }
+ }
+ /// update time: relative time
+ pub fn update_time_rel(&mut self, time: timeout_t) {
+ self.update_time(time, ToType::Default);
+ }
+ /// update time: absolute time
+ pub fn update_time_abs(&mut self, current_time: timeout_t) {
+ self.update_time(current_time, ToType::ABS);
+ }
+ /// get tos hz
+ pub fn get_hz(&self) -> timeout_t {
+ unsafe { timeouts_hz(self.get_raw()) }
+ }
+ /// return interval to next required update.
+ ///
+ /// careful for two case:
+ /// - return value could be u64::MAX, it means no timeout on timing wheel
+ /// - return value will always be less than the next most recent timeout expiration time
+ pub fn get_next_wait_time(&self) -> timeout_t {
+ unsafe { timeouts_timeout(self.get_raw_mut()) }
+ }
+ /// return true if any timeouts pending on timing wheel
+ pub fn any_pending(&self) -> bool {
+ unsafe { timeouts_pending(self.get_raw()) }
+ }
+ /// return true if any timeouts on expired queue
+ pub fn any_expired(&self) -> bool {
+ unsafe { timeouts_expired(self.get_raw()) }
+ }
+ // return true if TimeoutManager is effective
+ pub fn check(&self) -> bool {
+ unsafe { timeouts_check(self.get_raw_mut(), stderr) }
+ }
+}
+
+impl TimeoutManager {
+ /// add Timeout to timing wheel
+ /// Timeout type:
+ /// - INT: first expired on now + timeout, then expired at now + timeout + timeout + ...
+ /// even if it's expired,TimeoutManger will not auto renew it. must consume it use expired_timeouts.
+ /// - ABS: expired on timeout, then expired only once.
+ /// - Default: first expired on now + timeout, then expired only once.
+ /// Pass in ownership of the Timeout object
+ pub fn add(&mut self, to: Timeout, timeout: timeout_t) {
+ let to_ptr = Box::into_raw(Box::new(to));
+ unsafe { timeouts_add(self.get_raw_mut(), to_ptr, timeout) };
+ }
+ /// remove Timeout from any timing wheel or expired queue (okay if on neither)
+ /// No ownership of the Timeout object is passed in
+ pub fn delete(&mut self, to: &mut Timeout) {
+ unsafe { timeouts_del(self.get_raw_mut(), to) };
+ }
+
+ /// consume expired timeout
+ /// return next expired timeout until the queue is empty, then returns NULL.
+ /// ToR::OneTime: Ownership of timeout objects moved from C to rust
+ /// all pending/expired flag has cleared.
+ /// ToR::Periodic: Ownership still on C side
+ pub(crate) fn expired_timeout<'a>(&'a mut self) -> Option<ToR> {
+ let to_ptr: *mut timeout = unsafe { timeouts_get(self.get_raw_mut()) };
+ if to_ptr.is_null() {
+ return None;
+ }
+ if unsafe { (*to_ptr).is_periodic() } {
+ return Some(ToR::Periodic(unsafe { &*to_ptr }));
+ }
+ return Some(ToR::OneTime(Timeout::transfer(to_ptr)));
+ }
+ /// return next expired timeout iterator
+ /// ToR::OneTime: Ownership of timeout objects moved from C to rust
+ /// all pending/expired flag has cleared.
+ /// ToR::Periodic: Ownership still on C side
+ pub fn expired_timeouts(&mut self) -> ExpireIter {
+ ExpireIter {
+ timeout_manager: self,
+ }
+ }
+ ///
+ /// return next quote of timeout as timeout_sit requested, or NULL if none
+ /// No ownership of the Timeout object is passed in from C to rust
+ /// No consume any timeout
+ pub(crate) fn next_timeout<'a>(&mut self, tos_it: &mut TomIt) -> Option<&'a Timeout> {
+ let to_ptr: *mut timeout = unsafe { timeouts_next(self.get_raw_mut(), tos_it) };
+ if to_ptr.is_null() {
+ return None;
+ }
+ return Some(unsafe { &*to_ptr });
+ }
+ /// return next timeout quote iterator(as requested by tos_it_type)
+ pub fn next_timeouts(&mut self, tos_it_type: TomItType) -> NextIter {
+ let tos_it = TomIt::new(tos_it_type);
+ NextIter {
+ timeout_manager: self,
+ timeout_sit: tos_it,
+ }
+ }
+}
+
+impl Drop for TimeoutManager {
+ fn drop(&mut self) {
+ self.close();
+ }
+}
+
+impl TimeoutManager {
+ pub fn next_timeouts_pending(&mut self) -> NextIter {
+ self.next_timeouts(TomItType::PENDING)
+ }
+ pub fn next_timeouts_expired(&mut self) -> NextIter {
+ self.next_timeouts(TomItType::EXPIRED)
+ }
+ pub fn next_timeouts_all(&mut self) -> NextIter {
+ self.next_timeouts(TomItType::ALL)
+ }
+}
+
+pub struct ExpireIter<'a> {
+ timeout_manager: &'a mut TimeoutManager,
+}
+
+impl<'a> Iterator for ExpireIter<'a> {
+ type Item = ToR;
+ fn next(&mut self) -> Option<Self::Item> {
+ self.timeout_manager.expired_timeout()
+ }
+}
+
+pub struct NextIter<'a> {
+ timeout_manager: &'a mut TimeoutManager,
+ timeout_sit: TomIt,
+}
+
+impl<'a> Iterator for NextIter<'a> {
+ type Item = &'a Timeout;
+ fn next(&mut self) -> Option<Self::Item> {
+ self.timeout_manager.next_timeout(&mut self.timeout_sit)
+ }
+}
+
+#[cfg(test)]
+#[allow(unused_variables)]
+mod tests {
+ use std::{cell::RefCell, rc::Rc};
+
+ use super::*;
+
+ #[test]
+ fn test_timeout_type() {
+ let int_type = ToType::INT;
+ let abs_type = ToType::ABS;
+ let default_type = ToType::Default;
+
+ assert_eq!(i32::from(int_type), TIMEOUT_INT);
+ assert_eq!(i32::from(abs_type), TIMEOUT_ABS);
+ assert_eq!(i32::from(default_type), TIMEOUT_DEFAULT);
+
+ assert_eq!(ToType::new(TIMEOUT_INT), int_type);
+ assert_eq!(ToType::new(TIMEOUT_ABS), abs_type);
+ assert_eq!(ToType::new(123), default_type);
+ }
+
+ #[test]
+ fn test_timeout() {
+ let to = Timeout::new(ToType::Default).unwrap(); // relative timeout
+ assert!(!to.is_pending());
+ assert!(!to.is_expired());
+
+ let to2 = Timeout::new(ToType::INT).unwrap(); // relative timeout
+ assert!(!to2.is_pending());
+ assert!(!to2.is_expired());
+
+ let mut tos = TimeoutManager::new(TIMEOUT_mHZ).unwrap();
+ tos.update_time_rel(0); // tos.now = 0
+ tos.add(to, 100); // onetime, expired time = tos.now + 100
+ tos.add(to2, 100); // periodic
+
+ let mut tos_it = TomIt::new(TomItType::PENDING);
+ let to = tos.next_timeout(&mut tos_it).unwrap();
+ let to2 = tos.next_timeout(&mut tos_it).unwrap();
+
+ tos.update_time_rel(1); // tos.now = 1
+ assert!(to.is_pending());
+ assert!(!to.is_expired());
+
+ tos.update_time_rel(98); // tos.now = 99
+ assert!(to.is_pending());
+ assert!(!to.is_expired());
+
+ tos.update_time_rel(10); // tos.now = 109
+
+ for to in tos.next_timeouts(TomItType::ALL) {
+ assert!(!to.is_pending());
+ assert!(to.is_expired());
+ }
+
+ for to in tos.expired_timeouts() {
+ match to {
+ ToR::OneTime(temp) => {
+ // all flag has cleared
+ // assert!(to.is_expired());
+ }
+ ToR::Periodic(temp) => {
+ assert!(temp.is_periodic()); // next expired time = 200
+ }
+ };
+ }
+
+ tos.update_time_rel(110); // tos.now = 219
+
+ let mut temp_flag = false;
+
+ for to in tos.expired_timeouts() {
+ match to {
+ ToR::OneTime(temp) => {
+ // all flag has cleared
+ // assert!(to.is_expired());
+ }
+ ToR::Periodic(temp) => {
+ assert!(temp.is_periodic());
+ temp_flag = true;
+ }
+ };
+ }
+
+ assert!(temp_flag);
+ }
+
+ #[test]
+ fn test_timeout_sit_flag_into_i32() {
+ let pending = TomItType::PENDING;
+ let expired = TomItType::EXPIRED;
+ let all = TomItType::ALL;
+
+ assert_eq!(i32::from(pending), TIMEOUTS_PENDING);
+ assert_eq!(i32::from(expired), TIMEOUTS_EXPIRED);
+ assert_eq!(i32::from(all), TIMEOUTS_ALL);
+
+ assert_eq!(TomItType::new(TIMEOUTS_PENDING), pending);
+ assert_eq!(TomItType::new(TIMEOUTS_EXPIRED), expired);
+ assert_eq!(TomItType::new(TIMEOUTS_ALL), all);
+ assert_eq!(TomItType::new(123), all);
+ }
+
+ #[test]
+ fn test_timeout_manger() {
+ let mut tos = TimeoutManager::new(TIMEOUT_mHZ).unwrap();
+ assert_eq!(tos.get_hz(), TIMEOUT_mHZ);
+ assert!(tos.check());
+
+ tos.update_time_abs(0);
+ assert_eq!(tos.get_next_wait_time(), u64::MAX); // no timeout wait, so wait time is u64::MAX
+
+ let timeout = Timeout::new(ToType::Default).unwrap(); // relative timeout
+ tos.add(timeout, 100); // expired time = tos.now + 100
+
+ let mut tos_it = TomIt::new(TomItType::PENDING);
+ let to = tos.next_timeout(&mut tos_it);
+ let to = to.unwrap();
+
+ tos.update_time_abs(30);
+ assert!(tos.any_pending());
+ assert!(!tos.any_expired());
+ assert!(tos.get_next_wait_time() < 70);
+
+ assert!(to.is_pending());
+ assert!(!to.is_expired());
+
+ tos.update_time_abs(100);
+ assert!(!tos.any_pending());
+ assert!(tos.any_expired());
+ assert_eq!(tos.get_next_wait_time(), 0);
+
+ tos.update_time_abs(150);
+
+ assert!(!to.is_pending());
+ assert!(to.is_expired());
+
+ let timeout2 = tos.expired_timeout();
+ assert!(timeout2.is_some());
+ }
+
+ // just for test
+ #[derive(Clone, Debug, PartialEq, Eq)]
+ pub struct Session {
+ pub session_id: String,
+ }
+ impl Drop for Session {
+ fn drop(&mut self) {
+ println!("drop session: {}", self.session_id);
+ }
+ }
+
+ #[test]
+ #[allow(unused_variables)]
+ fn test_cb() {
+ let mut timeout = Timeout::new(ToType::Default).unwrap();
+
+ extern "C" fn rust_callback2(arg: &mut i32) {
+ println!("Callback executed with arg: {}", arg);
+ }
+ let mut binding = 11;
+ let cb = TimeoutCallBack::new(rust_callback2, &mut binding);
+ timeout.set_cb(cb);
+ timeout.run_cb();
+
+ extern "C" fn rust_callback3(arg: &mut Rc<RefCell<Session>>) {
+ let value = arg.borrow();
+ println!("Callback executed with session_id: {}", value.session_id);
+ }
+
+ let session = Session {
+ session_id: "123".to_string(),
+ };
+ let session_ref = Rc::new(RefCell::new(session));
+ let mut binding = session_ref.clone();
+ let cb = TimeoutCallBack::new(rust_callback3, &mut binding);
+ timeout.set_cb(cb);
+ timeout.run_cb();
+ }
+
+ #[test]
+ #[allow(unused_variables)]
+ fn test_cb_closure() {
+ let mut timeout = Timeout::new(ToType::Default).unwrap();
+
+ let mut session = Session {
+ session_id: "123".to_string(),
+ };
+
+ timeout.set_cb_closure(&mut || {
+ println!("Callback executed with session_id: {}", session.session_id);
+ session.session_id = "456".to_string();
+ println!("Callback executed with session_id: {}", session.session_id);
+ });
+
+ timeout.run_cb();
+ }
+}
diff --git a/bindings/rs-timeout/src/timeout_bind.rs b/bindings/rs-timeout/src/timeout_bind.rs
new file mode 100644
index 0000000..bb65062
--- /dev/null
+++ b/bindings/rs-timeout/src/timeout_bind.rs
@@ -0,0 +1,246 @@
+#![allow(non_upper_case_globals)] // allow snake case just in this file
+use std::ptr::null_mut;
+
+use libc::{c_char, c_int, c_uint, c_ulonglong, c_void, FILE};
+// TODO Check conditional compilation options
+
+/// V E R S I O N I N T E R F A C E S
+
+pub const TIMEOUT_VENDOR: &[u8; 26] = b"[email protected]";
+pub const TIMEOUT_V_REL: u32 = 538313254;
+pub const TIMEOUT_V_ABI: u32 = 538313252;
+pub const TIMEOUT_V_API: u32 = 538313254;
+
+extern "C" {
+ pub fn timeout_version() -> c_int;
+ pub fn timeout_vendor() -> *const c_char;
+ pub fn timeout_v_rel() -> c_int;
+ pub fn timeout_v_abi() -> c_int;
+ pub fn timeout_v_api() -> c_int;
+}
+
+/// I N T E G E R T Y P E I N T E R F A C E S
+
+// Originally a macro definition, use static variables instead
+// test-timeout.c pass test
+pub const TIMEOUT_PRIu: &[u8; 3] = b"lu\0"; // C 语言字符串结尾 `\0`, rust 没有
+pub const TIMEOUT_PRIx: &[u8; 3] = b"lx\0";
+pub const TIMEOUT_PRIX: &[u8; 3] = b"lX\0";
+
+pub const TIMEOUT_mHZ: u64 = 1000;
+pub const TIMEOUT_uHZ: u64 = 1000000;
+pub const TIMEOUT_nHZ: u64 = 1000000000;
+
+pub type timeout_t = u64;
+pub type timeout_error_t = usize;
+
+/// C A L L B A C K I N T E R F A C E
+///
+/// Callback function parameters unspecified to make embedding into existing
+/// applications easier.
+
+// #[cfg(TIMEOUT_CB_OVERRIDE)]
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct timeout_cb {
+ pub fn_ptr: Option<unsafe extern "C" fn(arg: *mut c_void)>,
+ pub arg: *mut c_void,
+}
+
+/// T I M E O U T I N T E R F A C E S
+
+// #[cfg(TIMEOUT_DISABLE_INTERVALS)]
+pub const TIMEOUT_DEFAULT: i32 = 0;
+pub const TIMEOUT_INT: i32 = 1;
+pub const TIMEOUT_ABS: i32 = 2;
+
+// macro TIMEOUT_INITIALIZER timeout_setcb
+
+// from TAILQ_ENTRY macro
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct timeout_TAILQ_ENTRY {
+ pub tqe_next: *mut timeout,
+ pub tqe_prev: *mut *mut timeout,
+}
+
+#[repr(C)]
+#[derive(Debug, Clone)]
+pub struct timeout {
+ pub flags: c_int,
+ pub expires: timeout_t, /* absolute expiration time */
+ pub pending: *mut timeout_list, /* timeout list if pending on wheel or expiry queue */
+ pub tqe: timeout_TAILQ_ENTRY, /* entry member for struct timeout_list lists */
+ // #[cfg(TIMEOUT_DISABLE_CALLBACKS)]
+ pub callback: timeout_cb, /* optional callback information */
+ // #[cfg(TIMEOUT_DISABLE_INTERVALS)]
+ pub interval: timeout_t, /* timeout interval if periodic */
+ // #[cfg(TIMEOUT_DISABLE_RELATIVE_ACCESS)]
+ pub timeouts: *mut timeouts, /* timeouts collection if member of */
+}
+
+impl Default for timeout {
+ fn default() -> Self {
+ timeout {
+ flags: 0,
+ expires: 0,
+ pending: null_mut(),
+ tqe: timeout_TAILQ_ENTRY {
+ tqe_next: null_mut(),
+ tqe_prev: null_mut(),
+ },
+ callback: timeout_cb {
+ fn_ptr: None,
+ arg: null_mut(),
+ },
+ interval: 0,
+ timeouts: null_mut(),
+ }
+ }
+}
+
+extern "C" {
+ /* initialize timeout structure (same as TIMEOUT_INITIALIZER) */
+ pub fn timeout_init(arg1: *mut timeout, arg2: c_int) -> *mut timeout;
+}
+// #[cfg(TIMEOUT_DISABLE_RELATIVE_ACCESS)]
+
+extern "C" {
+ /* true if on timing wheel, false otherwise */
+ pub fn timeout_pending(arg1: *mut timeout) -> bool;
+ /* true if on expired queue, false otherwise */
+ pub fn timeout_expired(arg1: *mut timeout) -> bool;
+ /* remove timeout from any timing wheel (okay if not member of any) */
+ pub fn timeout_del(arg1: *mut timeout);
+}
+
+/// T I M I N G W H E E L I N T E R F A C E S
+
+// 原定义在 timeout.c line 206 | TAILQ_HEAD(timeout_list, timeout);
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct timeout_list {
+ pub tqh_first: *mut timeout,
+ pub tqh_last: *mut *mut timeout,
+}
+
+// for timeouts
+const WHEEL_LEN: usize = 64; // timeout.c line 132 | (1U << WHEEL_BIT ) ,WHEEL_BIT = 6
+const WHEEL_NUM: usize = 4;
+pub type wheel_t = u64;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct timeouts {
+ pub wheel: [[timeout_list; WHEEL_LEN]; WHEEL_NUM],
+ pub expired: timeout_list,
+ pub pending: [wheel_t; WHEEL_NUM],
+ pub curtime: timeout_t,
+ pub hertz: timeout_t,
+}
+
+extern "C" {
+ /* open a new timing wheel, setting optional HZ (for float conversions) */
+ pub fn timeouts_open(arg1: timeout_t, arg2: *mut timeout_error_t) -> *mut timeouts;
+ /* destroy timing wheel */
+ pub fn timeouts_close(arg1: *mut timeouts);
+ /* return HZ setting (for float conversions) */
+ pub fn timeouts_hz(arg1: *const timeouts) -> timeout_t;
+ /* update timing wheel with current absolute time */
+ pub fn timeouts_update(arg1: *mut timeouts, arg2: timeout_t);
+ /* step timing wheel by relative time */
+ pub fn timeouts_step(arg1: *mut timeouts, arg2: timeout_t);
+ /* return interval to next required update */
+ pub fn timeouts_timeout(arg1: *mut timeouts) -> timeout_t;
+ /* add timeout to timing wheel */
+ pub fn timeouts_add(arg1: *mut timeouts, arg2: *mut timeout, arg3: timeout_t);
+ /* remove timeout from any timing wheel or expired queue (okay if on neither) */
+ pub fn timeouts_del(arg1: *mut timeouts, arg2: *mut timeout);
+ /* return any expired timeout (caller should loop until NULL-return) */
+ pub fn timeouts_get(arg1: *mut timeouts) -> *mut timeout;
+ /* return true if any timeouts pending on timing wheel */
+ pub fn timeouts_pending(arg1: *const timeouts) -> bool;
+ /* return true if any timeouts on expired queue */
+ pub fn timeouts_expired(arg1: *const timeouts) -> bool;
+ /* return true if invariants hold. describes failures to optional file handle. */
+ // arg2 use stderr is normal
+ pub fn timeouts_check(arg1: *mut timeouts, arg2: *mut FILE) -> bool;
+}
+
+extern "C" {
+ pub static mut stderr: *mut FILE;
+}
+
+pub const TIMEOUTS_PENDING: i32 = 16;
+pub const TIMEOUTS_EXPIRED: i32 = 32;
+pub const TIMEOUTS_ALL: i32 = 48;
+pub const TIMEOUTS_CLEAR: i32 = 64;
+
+// Macro definitions originally in C can be converted into C-compatible functions.
+#[no_mangle]
+pub extern "C" fn TIMEOUTS_IT_INITIALIZER(flags: c_int) -> timeouts_it {
+ timeouts_it {
+ flags: flags,
+ pc: 0,
+ i: 0,
+ j: 0,
+ to: null_mut(),
+ }
+}
+// Macro definitions originally in C can be converted into C-compatible functions.
+#[no_mangle]
+pub extern "C" fn TIMEOUTS_IT_INIT(cur: &mut timeouts_it, flags: i32) {
+ cur.flags = flags;
+ cur.pc = 0;
+}
+
+#[repr(C)]
+#[derive(Debug, Clone)]
+pub struct timeouts_it {
+ pub flags: c_int,
+ pub pc: c_uint,
+ pub i: c_uint,
+ pub j: c_uint,
+ pub to: *mut timeout,
+}
+
+impl Default for timeouts_it {
+ fn default() -> Self {
+ timeouts_it {
+ flags: 0,
+ pc: 0,
+ i: 0,
+ j: 0,
+ to: null_mut(),
+ }
+ }
+}
+
+/* return next timeout in pending wheel or expired queue. caller can delete
+ * the returned timeout, but should not otherwise manipulate the timing
+ * wheel. in particular, caller SHOULD NOT delete any other timeout as that
+ * could invalidate cursor state and trigger a use-after-free.
+ */
+// #[link(name = "timeout")]
+extern "C" {
+ pub fn timeouts_next(arg1: *mut timeouts, arg2: *mut timeouts_it) -> *mut timeout;
+}
+
+/*
+ * Calculate the interval our caller can wait before needing to process
+ * events.
+ */
+
+// extern "C" {
+// pub fn timeouts_int(T: *mut timeouts) -> timeout_t;
+// }
+// pub fn timeouts_timeout(t: *mut timeouts) -> timeout_t {
+// unsafe {
+// if !((*t).expired.tqh_first.is_null()) {
+// return 0;
+// }
+// timeouts_int(t)
+// }
+// }
+
+// B O N U S W H E E L I N T E R F A C E S
+// just use for lua, not use for rust.
diff --git a/bindings/rs-timeout/src/timeout_bind_test.rs b/bindings/rs-timeout/src/timeout_bind_test.rs
new file mode 100644
index 0000000..2438592
--- /dev/null
+++ b/bindings/rs-timeout/src/timeout_bind_test.rs
@@ -0,0 +1,266 @@
+use std::{
+ mem::{align_of, size_of, MaybeUninit},
+ ptr::addr_of,
+};
+
+use crate::timeout_bind::*;
+use std::ffi::CStr;
+
+#[cfg(test)]
+// V E R S I O N I N T E R F A C E S
+#[test]
+fn test_timeout_version() {
+ let version = unsafe { timeout_version() };
+ assert_eq!(version, TIMEOUT_V_REL as i32);
+}
+
+#[test]
+fn test_timeout_vendor() {
+ let vendor = unsafe { CStr::from_ptr(timeout_vendor()).to_bytes() };
+ assert_eq!(vendor, TIMEOUT_VENDOR);
+}
+
+#[test]
+fn test_timeout_v_rel() {
+ let v_rel = unsafe { timeout_v_rel() };
+ assert_eq!(v_rel, TIMEOUT_V_REL as i32);
+}
+
+#[test]
+fn test_timeout_v_abi() {
+ let v_abi = unsafe { timeout_v_abi() };
+ assert_eq!(v_abi, TIMEOUT_V_ABI as i32);
+}
+
+#[test]
+fn test_timeout_v_api() {
+ let v_api = unsafe { timeout_v_api() };
+ assert_eq!(v_api, TIMEOUT_V_API as i32);
+}
+
+// C A L L B A C K I N T E R F A C E
+
+#[test]
+fn bindgen_test_layout_timeout_cb() {
+ const UNINIT: MaybeUninit<timeout_cb> = MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ size_of::<timeout_cb>(),
+ 16usize,
+ concat!("Size of: ", stringify!(timeout_cb))
+ );
+ assert_eq!(
+ align_of::<timeout_cb>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(timeout_cb))
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).fn_ptr) as usize - ptr as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout_cb),
+ "::",
+ stringify!(fn_)
+ )
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).arg) as usize - ptr as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout_cb),
+ "::",
+ stringify!(arg)
+ )
+ );
+}
+
+/// T I M E O U T I N T E R F A C E S
+
+#[test]
+fn bindgen_test_layout_timeout_TAILQ_ENTRY() {
+ const UNINIT: MaybeUninit<timeout_TAILQ_ENTRY> = MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ size_of::<timeout_TAILQ_ENTRY>(),
+ 16usize,
+ concat!("Size of: ", stringify!(timeout__bindgen_ty_1))
+ );
+ assert_eq!(
+ align_of::<timeout_TAILQ_ENTRY>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(timeout__bindgen_ty_1))
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).tqe_next) as usize - ptr as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout__bindgen_ty_1),
+ "::",
+ stringify!(tqe_next)
+ )
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).tqe_prev) as usize - ptr as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout__bindgen_ty_1),
+ "::",
+ stringify!(tqe_prev)
+ )
+ );
+}
+#[test]
+fn bindgen_test_layout_timeout() {
+ const UNINIT: MaybeUninit<timeout> = MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ size_of::<timeout>(),
+ 72usize,
+ concat!("Size of: ", stringify!(timeout))
+ );
+ assert_eq!(
+ align_of::<timeout>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(timeout))
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).flags) as usize - ptr as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout),
+ "::",
+ stringify!(flags)
+ )
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).expires) as usize - ptr as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout),
+ "::",
+ stringify!(expires)
+ )
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).pending) as usize - ptr as usize },
+ 16usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout),
+ "::",
+ stringify!(pending)
+ )
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).tqe) as usize - ptr as usize },
+ 24usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout),
+ "::",
+ stringify!(tqe)
+ )
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).callback) as usize - ptr as usize },
+ 40usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout),
+ "::",
+ stringify!(callback)
+ )
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).interval) as usize - ptr as usize },
+ 56usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout),
+ "::",
+ stringify!(interval)
+ )
+ );
+ assert_eq!(
+ unsafe { addr_of!((*ptr).timeouts) as usize - ptr as usize },
+ 64usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeout),
+ "::",
+ stringify!(timeouts)
+ )
+ );
+}
+
+#[test]
+fn bindgen_test_layout_timeouts_it() {
+ const UNINIT: MaybeUninit<timeouts_it> = MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ size_of::<timeouts_it>(),
+ 24usize,
+ concat!("Size of: ", stringify!(timeouts_it))
+ );
+ assert_eq!(
+ align_of::<timeouts_it>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(timeouts_it))
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).flags) as usize - ptr as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeouts_it),
+ "::",
+ stringify!(flags)
+ )
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).pc) as usize - ptr as usize },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeouts_it),
+ "::",
+ stringify!(pc)
+ )
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).i) as usize - ptr as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeouts_it),
+ "::",
+ stringify!(i)
+ )
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).j) as usize - ptr as usize },
+ 12usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeouts_it),
+ "::",
+ stringify!(j)
+ )
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).to) as usize - ptr as usize },
+ 16usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(timeouts_it),
+ "::",
+ stringify!(to)
+ )
+ );
+}
diff --git a/bindings/rs-timeout/tests/test_timeout.rs b/bindings/rs-timeout/tests/test_timeout.rs
new file mode 100644
index 0000000..520e276
--- /dev/null
+++ b/bindings/rs-timeout/tests/test_timeout.rs
@@ -0,0 +1,793 @@
+use libc::free;
+use rand::Rng;
+
+use rs_timeout::timeout_bind::timeout_version;
+use rs_timeout::timeout_bind::*;
+use std::{
+ ffi::CStr,
+ io::{self, Write},
+ usize,
+};
+
+static mut n_failed: i32 = 0;
+const THE_END_OF_TIME: u64 = std::u64::MAX;
+
+macro_rules! DO {
+ ($fn:expr) => {{
+ print!(".");
+ io::stdout().flush().unwrap();
+ if $fn {
+ unsafe {
+ n_failed += 1;
+ }
+ println!("{} failed", stringify!($fn));
+ }
+ }};
+}
+
+macro_rules! DO_N {
+ ($n:expr, $fn:expr) => {{
+ for j in 0..$n {
+ DO!($fn);
+ }
+ }};
+}
+
+macro_rules! fail {
+ () => {{
+ println!("Failure on line {}", line!());
+ return true;
+ }};
+}
+
+fn check_misc() -> bool {
+ if (TIMEOUT_V_REL as i32) != unsafe { timeout_version() } {
+ return true;
+ }
+ if (TIMEOUT_V_REL as i32) != unsafe { timeout_v_rel() } {
+ return true;
+ }
+ if (TIMEOUT_V_API as i32) != unsafe { timeout_v_api() } {
+ return true;
+ }
+ if (TIMEOUT_V_ABI as i32) != unsafe { timeout_v_abi() } {
+ return true;
+ }
+ if unsafe { CStr::from_ptr(timeout_vendor()).to_bytes() } != TIMEOUT_VENDOR {
+ return true;
+ }
+ false
+}
+
+fn check_open_close(hz_set: timeout_t, hz_expect: timeout_t) -> bool {
+ let mut err = 0 as usize;
+ let tos: *mut timeouts = unsafe { timeouts_open(hz_set, &mut err) };
+
+ if tos.is_null() {
+ return true;
+ }
+ if err != 0 {
+ return true;
+ }
+ if hz_expect != unsafe { timeouts_hz(tos) } {
+ return true;
+ }
+ false
+}
+
+/* configuration for check_randomized */
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+struct rand_cfg {
+ /* When creating timeouts, smallest possible delay */
+ min_timeout: timeout_t,
+ /* When creating timeouts, largest possible delay */
+ max_timeout: timeout_t,
+ /* First time to start the clock at. */
+ start_at: timeout_t,
+ /* Do not advance the clock past this time. */
+ end_at: timeout_t,
+ /* Number of timeouts to create and monitor. */
+ n_timeouts: usize,
+ /* Advance the clock by no more than this each step. */
+ max_step: timeout_t,
+ /* Use relative timers and stepping */
+ // 实际上是 bool 值
+ relative: usize,
+ /* Every time the clock ticks, try removing this many timeouts at
+ * random. */
+ try_removing: usize,
+ /* When we're done, advance the clock to the end of time. */
+ finalize: usize,
+}
+
+/* Not very random */
+// fn random_to(min: timeout_t, max: timeout_t) -> timeout_t {
+// let mut rng = rand::thread_rng();
+// if max <= min {
+// return min;
+// }
+// /* Not actually all that random, but should exercise the code. */
+// let rand64 = rng.gen::<u64>() * (i32::MAX as timeout_t) + rng.gen::<u64>();
+// min + (rand64 % (max - min))
+// }
+
+fn random_to(min: u64, max: u64) -> u64 {
+ if max <= min {
+ return min;
+ }
+ let rand64 = rand::thread_rng().gen::<u64>();
+ min + (rand64 % (max - min))
+}
+
+fn random() -> usize {
+ rand::thread_rng().gen::<usize>()
+}
+
+fn check_randomized(cfg: &rand_cfg) -> bool {
+ let (i, rv) = (0, 0);
+ let mut err = 0 as usize;
+
+ let mut t: Vec<timeout> = vec![timeout::default(); cfg.n_timeouts];
+ let mut timeouts: Vec<timeout_t> = vec![0; cfg.n_timeouts];
+ let mut fired: Vec<u8> = vec![0; cfg.n_timeouts];
+ let mut found: Vec<u8> = vec![0; cfg.n_timeouts];
+ let mut deleted: Vec<u8> = vec![0; cfg.n_timeouts];
+
+ let mut tos = unsafe { Some(timeouts_open(0, &mut err)) }; // manager 的角色
+ let mut now = cfg.start_at;
+
+ let (mut n_added_pending, mut cnt_added_pending, mut n_added_expired, mut cnt_added_expired) =
+ (0, 0, 0, 0);
+
+ let (mut it_p, mut it_e, mut it_all) = (
+ timeouts_it::default(),
+ timeouts_it::default(),
+ timeouts_it::default(),
+ );
+
+ let (mut p_done, mut e_done, mut all_done) = (false, false, false);
+ // let mut to: Option<&mut timeout> = None;
+ let mut to: *mut timeout = std::ptr::null_mut();
+ let rel = cfg.relative;
+
+ // 对应 done
+ let cleanup = |tos,
+ t: Vec<timeout>,
+ timeouts: Vec<timeout_t>,
+ fired: Vec<u8>,
+ found: Vec<u8>,
+ deleted: Vec<u8>| {
+ if let Some(tos) = tos {
+ unsafe { timeouts_close(tos) };
+ }
+ if t.is_empty() {
+ drop(t);
+ // unsafe { free(t) };
+ }
+ if !timeouts.is_empty() {
+ drop(timeouts);
+ // unsafe { free(timeouts) };
+ }
+ if !fired.is_empty() {
+ drop(fired);
+ // unsafe { free(fired) };
+ }
+ if !found.is_empty() {
+ drop(found);
+ // unsafe { free(found) };
+ }
+ if !deleted.is_empty() {
+ drop(deleted);
+ // unsafe { free(deleted) };
+ }
+ };
+
+ if t.is_empty()
+ || timeouts.is_empty()
+ || tos.is_none()
+ || fired.is_empty()
+ || found.is_empty()
+ || deleted.is_empty()
+ {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ if let Some(mut tos) = tos {
+ unsafe { timeouts_update(tos, cfg.start_at) }; // manger 写入开始时间
+ }
+
+ // test-timeout.c line 98
+ for i in 0..cfg.n_timeouts {
+ // 初始化 timeout
+ if &t[i] as *const _
+ != unsafe { timeout_init(&mut t[i], if rel > 0 { 0 } else { TIMEOUT_ABS }) } as *const _
+ {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ // 检查 timeout 的状态
+ if unsafe { timeout_pending(&mut t[i]) || timeout_expired(&mut t[i]) } {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+
+ timeouts[i] = random_to(cfg.min_timeout, cfg.max_timeout); // 随机取超时时间 [min_timeout, max_timeout)]
+
+ unsafe {
+ if let Some(temp) = tos {
+ timeouts_add(
+ temp, // manger
+ &mut t[i], // timeout
+ timeouts[i] - (if rel > 0 { now } else { 0 }), // 超时时间
+ )
+ }
+ }
+ // 超时时间 小于 开始时间?
+ if timeouts[i] <= cfg.start_at {
+ // 如果还在等待 且 没有过期
+ if unsafe { timeout_pending(&mut t[i]) || !timeout_expired(&mut t[i]) } {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ n_added_expired += 1; // 计数
+ } else {
+ // 如果 没有等待 且 已经过期了
+ if unsafe { !timeout_pending(&mut t[i]) || timeout_expired(&mut t[i]) } {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ n_added_pending += 1; // 计数
+ }
+ }
+
+ // test-timeout.c line 124
+ // 检查 等待事件数量 与 计数 是否相等
+ if unsafe {
+ if let Some(temp) = tos {
+ (n_added_pending != 0) != timeouts_pending(temp)
+ } else {
+ true
+ }
+ } {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ // test-timeout.c line 126
+ // 检查 过期事件数量 与 计数 是否相等
+ if unsafe {
+ if let Some(temp) = tos {
+ (n_added_expired != 0) != timeouts_expired(temp)
+ } else {
+ true
+ }
+ } {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+
+ TIMEOUTS_IT_INIT(&mut it_p, TIMEOUTS_PENDING); // 等待
+ TIMEOUTS_IT_INIT(&mut it_e, TIMEOUTS_EXPIRED); // 过期
+ TIMEOUTS_IT_INIT(&mut it_all, TIMEOUTS_ALL); // 全部
+
+ // timeout.c line 133
+ while !(p_done && e_done && all_done) {
+ if !p_done {
+ if let Some(temp) = tos {
+ let to = unsafe { timeouts_next(temp, &mut it_p) }; // 等待队列的下一个
+ if !to.is_null() {
+ // c 语言中的指针运算,这里只能以 内存地址长度/ timeout 类型长度,来进行运算
+ // 找到 to 在 t[] 的位置
+ let i =
+ (to as usize - &t[0] as *const _ as usize) / std::mem::size_of::<timeout>();
+ found[i] += 1; // 事件的状态
+ cnt_added_pending += 1;
+ } else {
+ // to 为空,没有等待的 timeout 了
+ p_done = true;
+ }
+ }
+ }
+ if !e_done {
+ if let Some(temp) = tos {
+ let to = unsafe { timeouts_next(temp, &mut it_e) }; // 过期队列的下一个
+ if !to.is_null() {
+ // c 语言中的指针运算,这里只能以 内存地址长度/ timeout 类型长度,来进行运算
+ let i =
+ (to as usize - &t[0] as *const _ as usize) / std::mem::size_of::<timeout>();
+ found[i] += 1;
+ cnt_added_expired += 1;
+ } else {
+ // to 为空,没有过期的 timeout 了
+ e_done = true;
+ }
+ }
+ }
+ if !all_done {
+ if let Some(temp) = tos {
+ let to = unsafe { timeouts_next(temp, &mut it_all) };
+ if !to.is_null() {
+ // c 语言中的指针运算,这里只能以 内存地址长度/ timeout 类型长度,来进行运算
+ let i =
+ (to as usize - &t[0] as *const _ as usize) / std::mem::size_of::<timeout>();
+ found[i] += 1;
+ } else {
+ // to 为空,没有 timeout 了
+ all_done = true;
+ }
+ }
+ }
+ }
+ // timeout.c line 164
+ for i in 0..cfg.n_timeouts {
+ // 事件经历 等待 or 超时 + all 两次
+ if found[i] != 2 {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ }
+ if cnt_added_expired != n_added_expired {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ if cnt_added_pending != n_added_pending {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ // timeout.c line 174
+ if let Some(temp) = tos {
+ loop {
+ let to: *mut timeout = unsafe { timeouts_get(temp) }; // 任何已经过期的事件, 直到没有超时事件
+ if to.is_null() {
+ break;
+ }
+ let i = (to as usize - &t[0] as *const _ as usize) / std::mem::size_of::<timeout>(); // 找到 to 在 t[] 的位置
+ assert_eq!(&t[i] as *const _, to);
+ if timeouts[i] > cfg.start_at {
+ //已过期的 事件绝对
+ /* shouldn't have happened yet */
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ n_added_expired -= 1; /* drop expired timeouts. */
+ fired[i] += 1;
+ }
+ }
+ // 所有 过期事件 已经经过遍历了
+ if n_added_expired != 0 {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+
+ // test-timeout.c line 187
+ // 时间流逝到 end_at
+ while now < cfg.end_at {
+ let mut n_fired_this_time = 0;
+
+ // test-timeout.c line 189
+ // timeouts_timeout 取到一定程度会返回 u64 最大值,
+ // c 语言中 max_u64 + now == now -1 ; 但 rust 会直接 panic;
+ let temp = unsafe { timeouts_timeout(tos.unwrap()) }; // 下一次更新所需要的间隔 ??
+ let first_at = match temp.checked_add(now) {
+ Some(v) => v,
+ None => now - 1, // 溢出则按照 C 语言执行结果处理 -1;
+ }; // 过期时间 第一个事件的 时间
+
+ let oldtime = now; // 记录上一次的时间
+ let step = random_to(1, cfg.max_step); // 随机取步长 [1, max_step)
+ now += step; // 时间流逝
+
+ if rel != 0 {
+ unsafe { timeouts_step(tos.unwrap(), step) }; // 相对时间更新 计时轮
+ } else {
+ unsafe { timeouts_update(tos.unwrap(), now) }; // 绝对时间更新 计时轮
+ }
+ // 每次时钟 滴答时,尝试 随机删除 try_removing 个 timeout
+ for _i in 0..cfg.try_removing {
+ let idx = random() % cfg.n_timeouts; // 随机取个 timeout
+ // 超时已经被遍历过了
+ if !(fired[idx] == 0) {
+ unsafe {
+ timeout_del(&mut t[idx]);
+ }
+ deleted[idx] += 1;
+ }
+ }
+
+ let mut another = unsafe { timeouts_timeout(tos.unwrap()) } == 0; // 下一次更新所需要的间隔 是否 == 0,
+
+ loop {
+ let to = unsafe { timeouts_get(tos.unwrap()) }; // 任何已经过期的事件, 直到没有超时事件
+ if to.is_null() {
+ break;
+ }
+ if !another {
+ /* Thought we saw the last one! */
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+
+ let i = (to as usize - &t[0] as *const _ as usize) / std::mem::size_of::<timeout>(); // 找到 to 在 t[] 的位置
+ assert_eq!(&t[i] as *const _, to);
+ // 已经过期时间 超时时间不会比现在还小
+ if timeouts[i] > now {
+ /* shouldn't have happened yet */
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ // 已经过期时间 超时时间不会比上一次的时间还小 | 上次的过期事件绝对在上次已经处理完毕了.
+ if timeouts[i] <= oldtime {
+ /* should have happened already*/
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ // 已经过期时间 超时时间不会比 这次更新需要的最小 时间间隔还小
+ if timeouts[i] < first_at {
+ /* first_at should've been earlier */
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ fired[i] += 1; // 过期数组 计数
+ n_fired_this_time += 1; //
+ another = (unsafe { timeouts_timeout(tos.unwrap()) } == 0);
+ }
+ // 这轮处理过 超时事件 且 第一个超时事件的时间点 > now
+ if (n_fired_this_time != 0) && (first_at > now) {
+ /* first_at should've been earlier */
+ // 这里的处理逻辑是,如果有超时事件,那么第一个超时事件的时间点应该是 <= now
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ // 这轮处理过 超时事件 且 下一次更新所需要的间隔 == 0, 不可能出现.
+ if another {
+ /* Huh? We think there are more? */
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ // 有效??
+ if unsafe { !timeouts_check(tos.unwrap(), stderr) } {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ }
+
+ // test-timout.c line 233
+ for i in 0..cfg.n_timeouts {
+ // 过期 了两次
+ if fired[i] > 1 {
+ /* Nothing fired twice. */
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ // 超时时间 小于 开始时间?
+ if timeouts[i] <= now {
+ // 已经过期了, 却 还没有过期 且 没有删除 ?
+ if (fired[i] == 0) && (deleted[i] == 0) {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ } else {
+ // 没有过期, 但被标记为 已经过期了
+ if fired[i] != 0 {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ }
+ // 过期了 且 删除了
+ if (fired[i] != 0) && (deleted[i] != 0) {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ // 完成后 将时钟 更新到 timeout 的最大值
+ if cfg.finalize > 1 {
+ if !(fired[i] != 0) {
+ unsafe {
+ timeout_del(&mut t[i]);
+ }
+ }
+ }
+ }
+
+ // test-timeout.c line 251
+ /* Now nothing more should fire between now and the end of time. */
+ // 从 end_t 到 max_t 不应该再有事件了
+ if cfg.finalize > 0 {
+ // 时间移动到 timeout 的尽头
+ unsafe { timeouts_update(tos.unwrap(), THE_END_OF_TIME) }
+ //
+ if cfg.finalize > 1 {
+ // 任何已经过期的事件
+ let tmpe = unsafe { timeouts_get(tos.unwrap()) };
+ // 如果还有 事件
+ if !tmpe.is_null() {
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ let mut _it = TIMEOUTS_IT_INITIALIZER(TIMEOUTS_ALL); // 所有队列的事件
+ loop {
+ to = unsafe { timeouts_next(tos.unwrap(), &mut _it) };
+ if to.is_null() {
+ break;
+ }
+ // 如何还有事件
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ fail!();
+ }
+ }
+ }
+
+ cleanup(tos, t, timeouts, fired, found, deleted);
+ return false;
+}
+
+struct intervals_cfg<'a> {
+ timeouts: &'a [timeout_t],
+ n_timeouts: usize,
+ start_at: timeout_t,
+ end_at: timeout_t,
+ skip: timeout_t,
+}
+
+fn check_intervals(cfg: &intervals_cfg) -> bool {
+ let (mut i, rv) = (0, 0);
+ let mut err = 0 as usize;
+
+ let mut to: *mut timeout = std::ptr::null_mut();
+ let mut t: Vec<timeout> = vec![timeout::default(); cfg.n_timeouts as usize];
+ let mut fired: Vec<u32> = vec![0; cfg.n_timeouts];
+ let mut tos = unsafe { Some(timeouts_open(0, &mut err)) }; // manger
+
+ let mut now = cfg.start_at;
+
+ // 对应 done
+ let cleanup = |t: Vec<timeout>, tos: Option<*mut timeouts>, fired: Vec<u32>| {
+ if t.is_empty() {
+ drop(t);
+ // unsafe { free(t) };
+ }
+ if let Some(tos) = tos {
+ unsafe { timeouts_close(tos) };
+ }
+ if !fired.is_empty() {
+ drop(fired);
+ // unsafe { free(fired) };
+ }
+ };
+
+ if t.is_empty() || tos.is_none() || fired.is_empty() {
+ cleanup(t, tos, fired);
+ fail!();
+ }
+
+ unsafe { timeouts_update(tos.unwrap(), now) }; // 时钟行进到 now
+ // test-timeout.c line 300
+ for i in 0..cfg.n_timeouts {
+ // 初始化 按照绝对时间 计算超时
+ if &t[i] as *const _ != unsafe { timeout_init(&mut t[i], TIMEOUT_INT) } as *const _ {
+ cleanup(t, tos, fired);
+ fail!();
+ }
+ // timeout 等待(在时间轮上) 或 过期
+ if unsafe { timeout_pending(&mut t[i]) || timeout_expired(&mut t[i]) } {
+ cleanup(t, tos, fired);
+ fail!();
+ }
+ // 添加 timeout 到 时间轮上
+ unsafe { timeouts_add(tos.unwrap(), &mut t[i], cfg.timeouts[i]) }
+ // 没有在时间轮上 且 已过期
+ if unsafe { (!timeout_pending(&mut t[i])) || timeout_expired(&mut t[i]) } {
+ cleanup(t, tos, fired);
+ fail!();
+ }
+ }
+ // test-timeout.c line 315
+ while now < cfg.end_at {
+ let mut delay = unsafe { timeouts_timeout(tos.unwrap()) }; // 触发下一个 超时事件 最小时间间隔
+ // 有 跳过间歇 且 delay < 间歇
+ if (cfg.skip != 0) && (delay < cfg.skip) {
+ delay = cfg.skip; // delay = 间歇
+ }
+ unsafe { timeouts_step(tos.unwrap(), delay) } // 更新时钟 (相对方式)
+ now += delay; // 时间流逝
+
+ loop {
+ let to = unsafe { timeouts_get(tos.unwrap()) }; //
+ if to.is_null() {
+ break;
+ }
+ i = (to as usize - &t[0] as *const _ as usize) / std::mem::size_of::<timeout>();
+ assert_eq!(&t[i] as *const _, to);
+
+ fired[i] += 1;
+
+ if 0 != (unsafe { (*to).expires - cfg.start_at } % cfg.timeouts[i]) {
+ cleanup(t, tos, fired);
+ fail!();
+ }
+ if unsafe { (*to).expires <= now } {
+ cleanup(t, tos, fired);
+ fail!();
+ }
+ if unsafe { (*to).expires > now + cfg.timeouts[i] } {
+ cleanup(t, tos, fired);
+ fail!();
+ }
+ }
+ if unsafe { !timeouts_check(tos.unwrap(), stderr) } {
+ cleanup(t, tos, fired);
+ fail!();
+ }
+ }
+
+ let duration = now - cfg.start_at;
+ for i in 0..cfg.n_timeouts {
+ if cfg.skip != 0 {
+ if fired[i] as u64 > (duration / cfg.timeouts[i]) {
+ cleanup(t, tos, fired);
+ fail!();
+ }
+ } else {
+ if fired[i] as u64 != (duration / cfg.timeouts[i]) {
+ println!("{} != {}", fired[i], duration / cfg.timeouts[i]);
+ cleanup(t, tos, fired);
+ fail!();
+ }
+ }
+ if unsafe { !timeout_pending(&mut t[i]) } {
+ cleanup(t, tos, fired);
+ fail!();
+ }
+ }
+
+ return false;
+}
+
+#[test]
+fn main() {
+ unsafe {
+ n_failed = 0;
+ }
+ DO!(check_misc());
+ DO!(check_open_close(1000, 1000));
+ DO!(check_open_close(0, TIMEOUT_mHZ));
+
+ let cfg1 = rand_cfg {
+ min_timeout: 1,
+ max_timeout: 100,
+ start_at: 5,
+ end_at: 1000,
+ n_timeouts: 1000,
+ max_step: 10,
+ relative: 0,
+ try_removing: 0,
+ finalize: 2,
+ };
+ DO_N!(300, check_randomized(&cfg1));
+
+ let cfg2 = rand_cfg {
+ min_timeout: 20,
+ max_timeout: 1000,
+ start_at: 5,
+ end_at: 100,
+ n_timeouts: 1000,
+ max_step: 5,
+ relative: 1,
+ try_removing: 0,
+ finalize: 2,
+ };
+ DO_N!(300, check_randomized(&cfg2));
+
+ let cfg2b = rand_cfg {
+ min_timeout: 20,
+ max_timeout: 1000,
+ start_at: 10,
+ end_at: 100,
+ n_timeouts: 1000,
+ max_step: 5,
+ relative: 1,
+ try_removing: 0,
+ finalize: 1,
+ };
+ DO_N!(300, check_randomized(&cfg2b));
+
+ let cfg2c = rand_cfg {
+ min_timeout: 20,
+ max_timeout: 1000,
+ start_at: 10,
+ end_at: 100,
+ n_timeouts: 1000,
+ max_step: 5,
+ relative: 1,
+ try_removing: 0,
+ finalize: 0,
+ };
+ DO_N!(300, check_randomized(&cfg2c));
+
+ let cfg3 = rand_cfg {
+ min_timeout: 2000,
+ max_timeout: 1 << 50,
+ start_at: 100,
+ end_at: 1 << 49,
+ n_timeouts: 1000,
+ max_step: 1 << 31,
+ relative: 0,
+ try_removing: 0,
+ finalize: 2,
+ };
+ DO_N!(10, check_randomized(&cfg3));
+
+ let cfg3b = rand_cfg {
+ min_timeout: 1 << 50,
+ max_timeout: 1 << 52,
+ start_at: 100,
+ end_at: 1 << 53,
+ n_timeouts: 1000,
+ max_step: 1 << 48,
+ relative: 0,
+ try_removing: 0,
+ finalize: 2,
+ };
+ DO_N!(10, check_randomized(&cfg3b));
+
+ let cfg4 = rand_cfg {
+ min_timeout: 2000,
+ max_timeout: 1 << 30,
+ start_at: 100,
+ end_at: 1 << 26,
+ n_timeouts: 10000,
+ max_step: 1 << 16,
+ relative: 0,
+ try_removing: 0,
+ finalize: 2,
+ };
+ DO_N!(10, check_randomized(&cfg4));
+
+ const primes: [u64; 25] = [
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89,
+ 97,
+ ];
+ const factors_of_1337: [u64; 4] = [1, 7, 191, 1337];
+ const multiples_of_five: [u64; 10] = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50];
+
+ let icfg1 = intervals_cfg {
+ timeouts: &primes,
+ n_timeouts: primes.len(),
+ start_at: 50,
+ end_at: 5322,
+ skip: 0,
+ };
+ DO!(check_intervals(&icfg1));
+
+ let icfg2 = intervals_cfg {
+ timeouts: &factors_of_1337,
+ n_timeouts: factors_of_1337.len(),
+ start_at: 50,
+ end_at: 50000,
+ skip: 0,
+ };
+ DO!(check_intervals(&icfg2));
+
+ let icfg3 = intervals_cfg {
+ timeouts: &multiples_of_five,
+ n_timeouts: multiples_of_five.len(),
+ start_at: 49,
+ end_at: 5333,
+ skip: 0,
+ };
+ DO!(check_intervals(&icfg3));
+
+ let icfg4 = intervals_cfg {
+ timeouts: &primes,
+ n_timeouts: primes.len(),
+ start_at: 50,
+ end_at: 5322,
+ skip: 16,
+ };
+ DO!(check_intervals(&icfg4));
+
+ if unsafe { n_failed } != 0 {
+ println!("{} tests failed", unsafe { n_failed });
+ } else {
+ println!("OK");
+ }
+}
diff --git a/bindings/rs-timeout/timeout/Makefile b/bindings/rs-timeout/timeout/Makefile
new file mode 100644
index 0000000..fb58783
--- /dev/null
+++ b/bindings/rs-timeout/timeout/Makefile
@@ -0,0 +1,79 @@
+# NOTE: GNU Make 3.81 won't export MAKEFLAGS if .POSIX is specified, but
+# Solaris make won't export MAKEFLAGS unless .POSIX is specified.
+$(firstword ignore).POSIX:
+
+.DEFAULT_GOAL = all
+
+.SUFFIXES:
+
+all:
+
+#
+# USER-MODIFIABLE MACROS
+#
+top_srcdir = .
+top_builddir = .
+
+CFLAGS = -O0 -march=native -g -Wall -Wextra -Wno-unused-parameter -Wno-unused-function
+SOFLAGS = $$(auto_soflags)
+LIBS = $$(auto_libs)
+
+ALL_CPPFLAGS = -I$(top_srcdir) -DWHEEL_BIT=$(WHEEL_BIT) -DWHEEL_NUM=$(WHEEL_NUM) $(CPPFLAGS)
+ALL_CFLAGS = $(CFLAGS)
+ALL_SOFLAGS = $(SOFLAGS)
+ALL_LDFLAGS = $(LDFLAGS)
+ALL_LIBS = $(LIBS)
+
+LUA_API = 5.3
+LUA = lua
+LUA51_CPPFLAGS = $(LUA_CPPFLAGS)
+LUA52_CPPFLAGS = $(LUA_CPPFLAGS)
+LUA53_CPPFLAGS = $(LUA_CPPFLAGS)
+
+WHEEL_BIT = 6
+WHEEL_NUM = 4
+
+RM = rm -f
+
+# END MACROS
+
+SHRC = \
+ top_srcdir="$(top_srcdir)"; \
+ top_builddir="$(top_builddir)"; \
+ . "$${top_srcdir}/Rules.shrc"
+
+LUA_APIS = 5.1 5.2 5.3
+
+include $(top_srcdir)/lua/Rules.mk
+include $(top_srcdir)/bench/Rules.mk
+
+all: test-timeout
+
+timeout.o: $(top_srcdir)/timeout.c
+test-timeout.o: $(top_srcdir)/test-timeout.c
+
+timeout.o test-timeout.o:
+ @$(SHRC); echo_cmd $(CC) $(ALL_CFLAGS) -c -g -o $@ $${top_srcdir}/$(@F:%.o=%.c) $(ALL_CPPFLAGS)
+
+test-timeout: timeout.o test-timeout.o
+ @$(SHRC); echo_cmd $(CC) $(ALL_CPPFLAGS) $(ALL_CFLAGS) -o $@ timeout.o test-timeout.o
+
+static: timeout.o
+ @$(SHRC); echo_cmd ar rcs $(top_srcdir)/libtimeout.a $(top_srcdir)/timeout.o
+
+so: timeout.c
+ @$(SHRC); echo_cmd $(CC) $(ALL_CFLAGS) -shared -o $(top_srcdir)/timeout.so $(top_srcdir)/timeout.c
+
+install: so
+ @$(SHRC); \
+ echo_cmd cp $(top_srcdir)/timeout.so /usr/local/lib/libtimeout.so \
+ && ldconfig
+
+.PHONY: clean clean~
+
+clean:
+ $(RM) $(top_builddir)/test-timeout $(top_builddir)/*.o
+ $(RM) -r $(top_builddir)/*.dSYM
+
+clean~:
+ find $(top_builddir) $(top_srcdir) -name "*~" -exec $(RM) -- {} "+"
diff --git a/bindings/rs-timeout/timeout/Rules.shrc b/bindings/rs-timeout/timeout/Rules.shrc
new file mode 100644
index 0000000..ece75d4
--- /dev/null
+++ b/bindings/rs-timeout/timeout/Rules.shrc
@@ -0,0 +1,40 @@
+# convert to absolute paths
+top_srcdir="$(cd "${top_srcdir}" && pwd -L)"
+top_builddir="$(cd "${top_builddir}" && pwd -L)"
+
+# Paths for Lua modules (benchmarks and installed modules)
+export LUA_CPATH="${top_builddir}/lua/5.1/?.so;${top_builddir}/bench/?.so;;"
+export LUA_PATH="${top_srcdir}/lua/?.lua;${top_srcdir}/bench/?.lua;;"
+export LUA_CPATH_5_2="${top_builddir}/lua/5.2/?.so;${top_builddir}/bench/?.so;;"
+export LUA_PATH_5_2="${top_srcdir}/lua/?.lua;${top_srcdir}/bench/?.lua;;"
+export LUA_CPATH_5_3="${top_builddir}/lua/5.3/?.so;${top_builddir}/bench/?.so;;"
+export LUA_PATH_5_3="${top_srcdir}/lua/?.lua;${top_srcdir}/bench/?.lua;;"
+
+# preserve stdout so we can print commands to terminal
+exec 9>&1;
+echo_cmd() {
+ printf "%s\n" "$*" >&9;
+ "$@";
+}
+
+auto_soflags() {
+ case "$(uname -s)" in
+ Darwin)
+ printf -- "-bundle -undefined dynamic_lookup"
+ ;;
+ *)
+ printf -- "-fPIC -shared"
+ ;;
+ esac
+}
+
+auto_libs() {
+ case "$(uname -s)" in
+ Linux)
+ printf -- "-lrt"
+ ;;
+ *)
+ ;;
+ esac
+}
+
diff --git a/bindings/rs-timeout/timeout/bench/Rules.mk b/bindings/rs-timeout/timeout/bench/Rules.mk
new file mode 100644
index 0000000..3ee72f3
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/Rules.mk
@@ -0,0 +1,49 @@
+BENCH_MODS = bench.so $(BENCH_ALGOS:%=bench-%.so)
+BENCH_ALGOS = wheel heap llrb
+BENCH_OPS = add del expire
+
+$(top_builddir)/bench/bench.so: $(top_srcdir)/bench/bench.c
+$(top_builddir)/bench/bench-wheel.so: $(top_srcdir)/bench/bench-wheel.c
+$(top_builddir)/bench/bench-heap.so: $(top_srcdir)/bench/bench-heap.c
+$(top_builddir)/bench/bench-llrb.so: $(top_srcdir)/bench/bench-llrb.c
+
+$(BENCH_MODS:%=$(top_builddir)/bench/%): $(top_srcdir)/timeout.h $(top_srcdir)/timeout.c $(top_srcdir)/bench/bench.h
+ mkdir -p $(@D)
+ @$(SHRC); echo_cmd $(CC) -o $@ $(top_srcdir)/bench/$(@F:%.so=%.c) $(ALL_CPPFLAGS) $(ALL_CFLAGS) $(ALL_SOFLAGS) $(ALL_LDFLAGS) $(ALL_LIBS)
+
+$(BENCH_OPS:%=$(top_builddir)/bench/wheel-%.dat): $(top_builddir)/bench/bench-wheel.so $(top_builddir)/bench/bench.so $(top_srcdir)/bench/bench-aux.lua
+$(BENCH_OPS:%=$(top_builddir)/bench/heap-%.dat): $(top_builddir)/bench/bench-heap.so $(top_builddir)/bench/bench.so $(top_srcdir)/bench/bench-aux.lua
+$(BENCH_OPS:%=$(top_builddir)/bench/llrb-%.dat): $(top_builddir)/bench/bench-llrb.so $(top_builddir)/bench/bench.so $(top_srcdir)/bench/bench-aux.lua
+
+$(BENCH_ALGOS:%=$(top_builddir)/bench/%-add.dat): $(top_srcdir)/bench/bench-add.lua
+ @$(SHRC); echo_cmd cd $(@D) && echo_cmd $(LUA) $${top_srcdir}/bench/bench-add.lua $${top_builddir}/bench/bench-$(@F:%-add.dat=%).so > $(@F).tmp
+
+$(BENCH_ALGOS:%=$(top_builddir)/bench/%-del.dat): $(top_srcdir)/bench/bench-del.lua
+ @$(SHRC); echo_cmd cd $(@D) && echo_cmd $(LUA) $${top_srcdir}/bench/bench-del.lua $${top_builddir}/bench/bench-$(@F:%-del.dat=%).so > $(@F).tmp
+
+$(BENCH_ALGOS:%=$(top_builddir)/bench/%-expire.dat): $(top_srcdir)/bench/bench-expire.lua
+ @$(SHRC); echo_cmd cd $(@D) && echo_cmd $(LUA) $${top_srcdir}/bench/bench-expire.lua $${top_builddir}/bench/bench-$(@F:%-expire.dat=%).so > $(@F).tmp
+
+$(top_builddir)/bench/bench.eps: \
+ $(BENCH_OPS:%=$(top_builddir)/bench/wheel-%.dat) \
+ $(BENCH_OPS:%=$(top_builddir)/bench/heap-%.dat)
+# $(BENCH_OPS:%=$(top_builddir)/bench/llrb-%.dat)
+
+$(top_builddir)/bench/bench.eps: $(top_srcdir)/bench/bench.plt
+ @$(SHRC); echo_cmd cd $(@D) && echo_cmd gnuplot $${top_srcdir}/bench/bench.plt > $(@F).tmp
+
+$(top_builddir)/bench/bench.pdf: $(top_builddir)/bench/bench.eps
+ @$(SHRC); echo_cmd ps2pdf $${top_builddir}/bench/bench.eps $@
+
+bench-mods: $(BENCH_MODS:%=$(top_builddir)/bench/%)
+
+bench-all: $(top_builddir)/bench/bench.pdf
+
+bench-clean:
+ $(RM) -r $(top_builddir)/bench/*.so $(top_builddir)/bench/*.dSYM
+ $(RM) $(top_builddir)/bench/*.dat $(top_builddir)/bench/*.tmp
+ $(RM) $(top_builddir)/bench/bench.{eps,pdf}
diff --git a/bindings/rs-timeout/timeout/bench/bench-add.lua b/bindings/rs-timeout/timeout/bench/bench-add.lua
new file mode 100644
index 0000000..64a921d
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/bench-add.lua
@@ -0,0 +1,30 @@
+#!/usr/bin/env lua
+
+local bench = require"bench"
+local aux = require"bench-aux"
+
+local lib = ... or aux.optenv("BENCH_L", "bench-wheel.so")
+local limit = tonumber(aux.optenv("BENCH_N", 1000000))
+local step = tonumber(aux.optenv("BENCH_S", limit / 100))
+local exp_step = tonumber(aux.optenv("BENCH_E", 1.0))
+local verbose = aux.toboolean(os.getenv("BENCH_V", false))
+
+local B = bench.new(lib, count, nil, verbose)
+local fill_count, fill_last = B:fill(limit)
+
+for i=0,limit,step do
+ local exp_elapsed, fill_elapsed, fill_rate
+
+ -- expire all timeouts
+ --exp_elapsed = aux.time(B.expire, B, fill_count, fill_last * exp_step)
+ exp_elapsed = aux.time(B.del, B, 0, fill_count)
+ assert(B:empty())
+
+ -- add i timeouts
+ fill_elapsed, fill_count, fill_last = aux.time(B.fill, B, i)
+ assert(fill_count == i)
+ fill_rate = fill_elapsed > 0 and (fill_count / fill_elapsed) or 0
+
+ local fmt = verbose and "%d\t%f\t(%d/s)\t(exp:%f)" or "%d\t%f"
+ aux.say(fmt, i, fill_elapsed, fill_rate, exp_elapsed)
+end
diff --git a/bindings/rs-timeout/timeout/bench/bench-aux.lua b/bindings/rs-timeout/timeout/bench/bench-aux.lua
new file mode 100644
index 0000000..6321247
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/bench-aux.lua
@@ -0,0 +1,30 @@
+local bench = require"bench"
+local clock = bench.clock
+
+local aux = {}
+
+local function time_return(begun, ...)
+ local duration = clock() - begun
+ return duration, ...
+end
+
+function aux.time(f, ...)
+ local begun = clock()
+ return time_return(begun, f(...))
+end
+
+function aux.say(...)
+ print(string.format(...))
+end
+
+function aux.toboolean(s)
+ return tostring(s):match("^[1TtYy]") and true or false
+end
+
+function aux.optenv(k, def)
+ local s = os.getenv(k)
+
+ return (s and #s > 0 and s) or def
+end
+
+return aux
diff --git a/bindings/rs-timeout/timeout/bench/bench-del.lua b/bindings/rs-timeout/timeout/bench/bench-del.lua
new file mode 100644
index 0000000..4306745
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/bench-del.lua
@@ -0,0 +1,25 @@
+#!/usr/bin/env lua
+
+local bench = require"bench"
+local aux = require"bench-aux"
+
+local lib = ... or aux.optenv("BENCH_L", "bench-wheel.so")
+local limit = tonumber(aux.optenv("BENCH_N", 1000000))
+local step = tonumber(aux.optenv("BENCH_S", limit / 100))
+local verbose = aux.toboolean(os.getenv("BENCH_V", false))
+
+local B = bench.new(lib, count)
+
+for i=0,limit,step do
+ -- add i timeouts
+ local fill_elapsed, fill_count = aux.time(B.fill, B, i, 60 * 1000000)
+ assert(i == fill_count)
+
+ --- delete i timeouts
+ local del_elapsed = aux.time(B.del, B, 0, fill_count)
+ assert(B:empty())
+ local del_rate = i > 0 and i / del_elapsed or 0
+
+ local fmt = verbose and "%d\t%f\t(%d/s)\t(fill:%f)" or "%d\t%f"
+ aux.say(fmt, i, del_elapsed, del_rate, fill_elapsed)
+end
diff --git a/bindings/rs-timeout/timeout/bench/bench-expire.lua b/bindings/rs-timeout/timeout/bench/bench-expire.lua
new file mode 100644
index 0000000..3e6374e
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/bench-expire.lua
@@ -0,0 +1,29 @@
+#!/usr/bin/env lua
+
+local bench = require"bench"
+local aux = require"bench-aux"
+
+local lib = ... or aux.optenv("BENCH_L", "bench-wheel.so")
+local limit = tonumber(aux.optenv("BENCH_N", 1000000))
+local step = tonumber(aux.optenv("BENCH_S", limit / 100))
+-- expire 1/1000 * #timeouts per clock update
+local exp_step = tonumber(aux.optenv("BENCH_E", 0.0001))
+local verbose = aux.toboolean(os.getenv("BENCH_V", false))
+
+local B = require"bench".new(lib, count)
+
+for i=0,limit,step do
+ -- add i timeouts
+ local fill_elapsed, fill_count, fill_last = aux.time(B.fill, B, i)
+
+ -- expire timeouts by iteratively updating clock. exp_step is the
+ -- approximate number of timeouts (as a fraction of the total number
+ -- of timeouts) that will expire per update.
+ local exp_elapsed, exp_count = aux.time(B.expire, B, fill_count, math.floor(fill_last * exp_step))
+ assert(exp_count == i)
+ assert(B:empty())
+ local exp_rate = i > 0 and i / exp_elapsed or 0
+
+ local fmt = verbose and "%d\t%f\t(%d/s)\t(fill:%f)" or "%d\t%f"
+ aux.say(fmt, i, exp_elapsed, exp_rate, fill_elapsed)
+end
diff --git a/bindings/rs-timeout/timeout/bench/bench-heap.c b/bindings/rs-timeout/timeout/bench/bench-heap.c
new file mode 100644
index 0000000..f1166a4
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/bench-heap.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2006 Maxim Yegorushkin <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _MIN_HEAP_H_
+#define _MIN_HEAP_H_
+
+#include <stdlib.h>
+#include <err.h>
+#include "timeout.h"
+#include "bench.h"
+
+#define min_heap_idx interval
+
+typedef timeout_t min_heap_idx_t;
+
+typedef struct min_heap
+{
+ struct timeout** p;
+ unsigned n, a;
+ timeout_t curtime;
+} min_heap_t;
+
+static inline void min_heap_ctor(min_heap_t* s);
+static inline void min_heap_dtor(min_heap_t* s);
+static inline void min_heap_elem_init(struct timeout* e);
+static inline int min_heap_elem_greater(struct timeout *a, struct timeout *b);
+static inline int min_heap_empty(min_heap_t* s);
+static inline unsigned min_heap_size(min_heap_t* s);
+static inline struct timeout* min_heap_top(min_heap_t* s);
+static inline int min_heap_reserve(min_heap_t* s, unsigned n);
+static inline int min_heap_push(min_heap_t* s, struct timeout* e);
+static inline struct timeout* min_heap_pop(min_heap_t* s);
+static inline int min_heap_erase(min_heap_t* s, struct timeout* e);
+static inline void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, struct timeout* e);
+static inline void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, struct timeout* e);
+
+int min_heap_elem_greater(struct timeout *a, struct timeout *b)
+{
+ return a->expires > b->expires;
+}
+
+void min_heap_ctor(min_heap_t* s) { s->p = 0; s->n = 0; s->a = 0; }
+void min_heap_dtor(min_heap_t* s) { if(s->p) free(s->p); }
+void min_heap_elem_init(struct timeout* e) { e->min_heap_idx = -1; }
+int min_heap_empty(min_heap_t* s) { return 0u == s->n; }
+unsigned min_heap_size(min_heap_t* s) { return s->n; }
+struct timeout* min_heap_top(min_heap_t* s) { return s->n ? *s->p : 0; }
+
+int min_heap_push(min_heap_t* s, struct timeout* e)
+{
+ if(min_heap_reserve(s, s->n + 1))
+ return -1;
+ min_heap_shift_up_(s, s->n++, e);
+ return 0;
+}
+
+struct timeout* min_heap_pop(min_heap_t* s)
+{
+ if(s->n)
+ {
+ struct timeout* e = *s->p;
+ min_heap_shift_down_(s, 0u, s->p[--s->n]);
+ e->min_heap_idx = -1;
+ return e;
+ }
+ return 0;
+}
+
+int min_heap_erase(min_heap_t* s, struct timeout* e)
+{
+ if(((min_heap_idx_t)-1) != e->min_heap_idx)
+ {
+ struct timeout *last = s->p[--s->n];
+ unsigned parent = (e->min_heap_idx - 1) / 2;
+ /* we replace e with the last element in the heap. We might need to
+ shift it upward if it is less than its parent, or downward if it is
+ greater than one or both its children. Since the children are known
+ to be less than the parent, it can't need to shift both up and
+ down. */
+ if (e->min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], last))
+ min_heap_shift_up_(s, e->min_heap_idx, last);
+ else
+ min_heap_shift_down_(s, e->min_heap_idx, last);
+ e->min_heap_idx = -1;
+ return 0;
+ }
+ return -1;
+}
+
+int min_heap_reserve(min_heap_t* s, unsigned n)
+{
+ if(s->a < n)
+ {
+ struct timeout** p;
+ unsigned a = s->a ? s->a * 2 : 8;
+ if(a < n)
+ a = n;
+ if(!(p = (struct timeout**)realloc(s->p, a * sizeof *p)))
+ return -1;
+ s->p = p;
+ s->a = a;
+ }
+ return 0;
+}
+
+void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, struct timeout* e)
+{
+ unsigned parent = (hole_index - 1) / 2;
+ while(hole_index && min_heap_elem_greater(s->p[parent], e))
+ {
+ (s->p[hole_index] = s->p[parent])->min_heap_idx = hole_index;
+ hole_index = parent;
+ parent = (hole_index - 1) / 2;
+ }
+ (s->p[hole_index] = e)->min_heap_idx = hole_index;
+}
+
+void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, struct timeout* e)
+{
+ unsigned min_child = 2 * (hole_index + 1);
+ while(min_child <= s->n)
+ {
+ min_child -= min_child == s->n || min_heap_elem_greater(s->p[min_child], s->p[min_child - 1]);
+ if(!(min_heap_elem_greater(e, s->p[min_child])))
+ break;
+ (s->p[hole_index] = s->p[min_child])->min_heap_idx = hole_index;
+ hole_index = min_child;
+ min_child = 2 * (hole_index + 1);
+ }
+ min_heap_shift_up_(s, hole_index, e);
+}
+
+#endif /* _MIN_HEAP_H_ */
+
+
+static void *init(struct timeout *timeout, size_t count, int verbose) {
+ min_heap_t *H;
+ size_t i;
+
+ H = calloc(1, sizeof *H);
+
+ min_heap_ctor(H);
+ if (0 != min_heap_reserve(H, count))
+ err(1, "realloc");
+
+ for (i = 0; i < count; i++) {
+ min_heap_elem_init(&timeout[i]);
+ }
+
+ return H;
+} /* init() */
+
+
+static void add(void *ctx, struct timeout *to, timeout_t expires) {
+ min_heap_t *H = ctx;
+ min_heap_erase(H, to);
+ to->expires = H->curtime + expires;
+ if (0 != min_heap_push(H, to))
+ err(1, "realloc");
+} /* add() */
+
+
+static void del(void *ctx, struct timeout *to) {
+ min_heap_erase(ctx, to);
+} /* del() */
+
+
+static struct timeout *get(void *ctx) {
+ min_heap_t *H = ctx;
+ struct timeout *to;
+
+ if ((to = min_heap_top(H)) && to->expires <= H->curtime)
+ return min_heap_pop(H);
+
+ return NULL;
+} /* get() */
+
+
+static void update(void *ctx, timeout_t ts) {
+ min_heap_t *H = ctx;
+ H->curtime = ts;
+} /* update() */
+
+
+static void check(void *ctx) {
+ return;
+} /* check() */
+
+
+static int empty(void *ctx) {
+ min_heap_t *H = ctx;
+
+ return (NULL == min_heap_top(H));
+} /* empty() */
+
+
+static void destroy(void *H) {
+ free(H);
+ return;
+} /* destroy() */
+
+
+const struct benchops benchops = {
+ .init = &init,
+ .add = &add,
+ .del = &del,
+ .get = &get,
+ .update = &update,
+ .check = &check,
+ .empty = &empty,
+ .destroy = &destroy,
+};
+
diff --git a/bindings/rs-timeout/timeout/bench/bench-llrb.c b/bindings/rs-timeout/timeout/bench/bench-llrb.c
new file mode 100644
index 0000000..bdb02f0
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/bench-llrb.c
@@ -0,0 +1,425 @@
+/* ==========================================================================
+ * llrb.h - Iterative Left-leaning Red-Black Tree.
+ * --------------------------------------------------------------------------
+ * Copyright (c) 2011, 2013 William Ahern <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * --------------------------------------------------------------------------
+ * CREDITS:
+ * o Algorithm courtesy of Robert Sedgewick, "Left-leaning Red-Black
+ * Trees" (September 2008); and Robert Sedgewick and Kevin Wayne,
+ * Algorithms (4th ed. 2011).
+ *
+ * Sedgewick touts the simplicity of the recursive implementation,
+ * but at least for the 2-3 tree variant the iterative approach is
+ * almost line-for-line identical. The magic of C pointers helps;
+ * it'd be uglier with Java.
+ *
+ * A couple of missing NULL checks were added to Sedgewick's deletion
+ * example, and insert was optimized to short-circuit rotations when
+ * walking up the tree.
+ *
+ * o Code implemented in the fashion of Niels Provos' excellent *BSD
+ * sys/tree.h pre-processor library.
+ *
+ * Regarding relative performance, I've refrained from sharing my own
+ * benchmarks. Differences in run-time speed were too correlated to
+ * compiler options and other external factors.
+ *
+ * Provos' delete implementation doesn't need to start at the root of
+ * the tree. However, RB_REMOVE must be passed the actual node to be
+ * removed. LLRB_REMOVE merely requires a key, much like
+ * RB_FIND/LLRB_FIND.
+ * ==========================================================================
+ */
+#ifndef LLRB_H
+#define LLRB_H
+
+#define LLRB_VENDOR "[email protected]"
+#define LLRB_VERSION 0x20130925
+
+#ifndef LLRB_STATIC
+#ifdef __GNUC__
+#define LLRB_STATIC __attribute__((__unused__)) static
+#else
+#define LLRB_STATIC static
+#endif
+#endif
+
+#define LLRB_HEAD(name, type) \
+struct name { struct type *rbh_root; }
+
+#define LLRB_INITIALIZER(root) { 0 }
+
+#define LLRB_INIT(root) do { (root)->rbh_root = 0; } while (0)
+
+#define LLRB_BLACK 0
+#define LLRB_RED 1
+
+#define LLRB_ENTRY(type) \
+struct { struct type *rbe_left, *rbe_right, *rbe_parent; _Bool rbe_color; }
+
+#define LLRB_LEFT(elm, field) (elm)->field.rbe_left
+#define LLRB_RIGHT(elm, field) (elm)->field.rbe_right
+#define LLRB_PARENT(elm, field) (elm)->field.rbe_parent
+#define LLRB_EDGE(head, elm, field) (((elm) == LLRB_ROOT(head))? &LLRB_ROOT(head) : ((elm) == LLRB_LEFT(LLRB_PARENT((elm), field), field))? &LLRB_LEFT(LLRB_PARENT((elm), field), field) : &LLRB_RIGHT(LLRB_PARENT((elm), field), field))
+#define LLRB_COLOR(elm, field) (elm)->field.rbe_color
+#define LLRB_ROOT(head) (head)->rbh_root
+#define LLRB_EMPTY(head) ((head)->rbh_root == 0)
+#define LLRB_ISRED(elm, field) ((elm) && LLRB_COLOR((elm), field) == LLRB_RED)
+
+#define LLRB_PROTOTYPE(name, type, field, cmp) \
+ LLRB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
+#define LLRB_PROTOTYPE_STATIC(name, type, field, cmp) \
+ LLRB_PROTOTYPE_INTERNAL(name, type, field, cmp, LLRB_STATIC)
+#define LLRB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \
+attr struct type *name##_LLRB_INSERT(struct name *, struct type *); \
+attr struct type *name##_LLRB_DELETE(struct name *, struct type *); \
+attr struct type *name##_LLRB_FIND(struct name *, struct type *); \
+attr struct type *name##_LLRB_MIN(struct type *); \
+attr struct type *name##_LLRB_MAX(struct type *); \
+attr struct type *name##_LLRB_NEXT(struct type *);
+
+#define LLRB_GENERATE(name, type, field, cmp) \
+ LLRB_GENERATE_INTERNAL(name, type, field, cmp,)
+#define LLRB_GENERATE_STATIC(name, type, field, cmp) \
+ LLRB_GENERATE_INTERNAL(name, type, field, cmp, LLRB_STATIC)
+#define LLRB_GENERATE_INTERNAL(name, type, field, cmp, attr) \
+static inline void name##_LLRB_ROTL(struct type **pivot) { \
+ struct type *a = *pivot; \
+ struct type *b = LLRB_RIGHT(a, field); \
+ if ((LLRB_RIGHT(a, field) = LLRB_LEFT(b, field))) \
+ LLRB_PARENT(LLRB_RIGHT(a, field), field) = a; \
+ LLRB_LEFT(b, field) = a; \
+ LLRB_COLOR(b, field) = LLRB_COLOR(a, field); \
+ LLRB_COLOR(a, field) = LLRB_RED; \
+ LLRB_PARENT(b, field) = LLRB_PARENT(a, field); \
+ LLRB_PARENT(a, field) = b; \
+ *pivot = b; \
+} \
+static inline void name##_LLRB_ROTR(struct type **pivot) { \
+ struct type *b = *pivot; \
+ struct type *a = LLRB_LEFT(b, field); \
+ if ((LLRB_LEFT(b, field) = LLRB_RIGHT(a, field))) \
+ LLRB_PARENT(LLRB_LEFT(b, field), field) = b; \
+ LLRB_RIGHT(a, field) = b; \
+ LLRB_COLOR(a, field) = LLRB_COLOR(b, field); \
+ LLRB_COLOR(b, field) = LLRB_RED; \
+ LLRB_PARENT(a, field) = LLRB_PARENT(b, field); \
+ LLRB_PARENT(b, field) = a; \
+ *pivot = a; \
+} \
+static inline void name##_LLRB_FLIP(struct type *root) { \
+ LLRB_COLOR(root, field) = !LLRB_COLOR(root, field); \
+ LLRB_COLOR(LLRB_LEFT(root, field), field) = !LLRB_COLOR(LLRB_LEFT(root, field), field); \
+ LLRB_COLOR(LLRB_RIGHT(root, field), field) = !LLRB_COLOR(LLRB_RIGHT(root, field), field); \
+} \
+static inline void name##_LLRB_FIXUP(struct type **root) { \
+ if (LLRB_ISRED(LLRB_RIGHT(*root, field), field) && !LLRB_ISRED(LLRB_LEFT(*root, field), field)) \
+ name##_LLRB_ROTL(root); \
+ if (LLRB_ISRED(LLRB_LEFT(*root, field), field) && LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*root, field), field), field)) \
+ name##_LLRB_ROTR(root); \
+ if (LLRB_ISRED(LLRB_LEFT(*root, field), field) && LLRB_ISRED(LLRB_RIGHT(*root, field), field)) \
+ name##_LLRB_FLIP(*root); \
+} \
+attr struct type *name##_LLRB_INSERT(struct name *head, struct type *elm) { \
+ struct type **root = &LLRB_ROOT(head); \
+ struct type *parent = 0; \
+ while (*root) { \
+ int comp = (cmp)((elm), (*root)); \
+ parent = *root; \
+ if (comp < 0) \
+ root = &LLRB_LEFT(*root, field); \
+ else if (comp > 0) \
+ root = &LLRB_RIGHT(*root, field); \
+ else \
+ return *root; \
+ } \
+ LLRB_LEFT((elm), field) = 0; \
+ LLRB_RIGHT((elm), field) = 0; \
+ LLRB_COLOR((elm), field) = LLRB_RED; \
+ LLRB_PARENT((elm), field) = parent; \
+ *root = (elm); \
+ while (parent && (LLRB_ISRED(LLRB_LEFT(parent, field), field) || LLRB_ISRED(LLRB_RIGHT(parent, field), field))) { \
+ root = LLRB_EDGE(head, parent, field); \
+ parent = LLRB_PARENT(parent, field); \
+ name##_LLRB_FIXUP(root); \
+ } \
+ LLRB_COLOR(LLRB_ROOT(head), field) = LLRB_BLACK; \
+ return 0; \
+} \
+static inline void name##_LLRB_MOVL(struct type **pivot) { \
+ name##_LLRB_FLIP(*pivot); \
+ if (LLRB_ISRED(LLRB_LEFT(LLRB_RIGHT(*pivot, field), field), field)) { \
+ name##_LLRB_ROTR(&LLRB_RIGHT(*pivot, field)); \
+ name##_LLRB_ROTL(pivot); \
+ name##_LLRB_FLIP(*pivot); \
+ } \
+} \
+static inline void name##_LLRB_MOVR(struct type **pivot) { \
+ name##_LLRB_FLIP(*pivot); \
+ if (LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*pivot, field), field), field)) { \
+ name##_LLRB_ROTR(pivot); \
+ name##_LLRB_FLIP(*pivot); \
+ } \
+} \
+static inline struct type *name##_DELETEMIN(struct name *head, struct type **root) { \
+ struct type **pivot = root, *deleted, *parent; \
+ while (LLRB_LEFT(*pivot, field)) { \
+ if (!LLRB_ISRED(LLRB_LEFT(*pivot, field), field) && !LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*pivot, field), field), field)) \
+ name##_LLRB_MOVL(pivot); \
+ pivot = &LLRB_LEFT(*pivot, field); \
+ } \
+ deleted = *pivot; \
+ parent = LLRB_PARENT(*pivot, field); \
+ *pivot = 0; \
+ while (root != pivot) { \
+ pivot = LLRB_EDGE(head, parent, field); \
+ parent = LLRB_PARENT(parent, field); \
+ name##_LLRB_FIXUP(pivot); \
+ } \
+ return deleted; \
+} \
+attr struct type *name##_LLRB_DELETE(struct name *head, struct type *elm) { \
+ struct type **root = &LLRB_ROOT(head), *parent = 0, *deleted = 0; \
+ int comp; \
+ while (*root) { \
+ parent = LLRB_PARENT(*root, field); \
+ comp = (cmp)(elm, *root); \
+ if (comp < 0) { \
+ if (LLRB_LEFT(*root, field) && !LLRB_ISRED(LLRB_LEFT(*root, field), field) && !LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*root, field), field), field)) \
+ name##_LLRB_MOVL(root); \
+ root = &LLRB_LEFT(*root, field); \
+ } else { \
+ if (LLRB_ISRED(LLRB_LEFT(*root, field), field)) { \
+ name##_LLRB_ROTR(root); \
+ comp = (cmp)(elm, *root); \
+ } \
+ if (!comp && !LLRB_RIGHT(*root, field)) { \
+ deleted = *root; \
+ *root = 0; \
+ break; \
+ } \
+ if (LLRB_RIGHT(*root, field) && !LLRB_ISRED(LLRB_RIGHT(*root, field), field) && !LLRB_ISRED(LLRB_LEFT(LLRB_RIGHT(*root, field), field), field)) { \
+ name##_LLRB_MOVR(root); \
+ comp = (cmp)(elm, *root); \
+ } \
+ if (!comp) { \
+ struct type *orphan = name##_DELETEMIN(head, &LLRB_RIGHT(*root, field)); \
+ LLRB_COLOR(orphan, field) = LLRB_COLOR(*root, field); \
+ LLRB_PARENT(orphan, field) = LLRB_PARENT(*root, field); \
+ if ((LLRB_RIGHT(orphan, field) = LLRB_RIGHT(*root, field))) \
+ LLRB_PARENT(LLRB_RIGHT(orphan, field), field) = orphan; \
+ if ((LLRB_LEFT(orphan, field) = LLRB_LEFT(*root, field))) \
+ LLRB_PARENT(LLRB_LEFT(orphan, field), field) = orphan; \
+ deleted = *root; \
+ *root = orphan; \
+ parent = *root; \
+ break; \
+ } else \
+ root = &LLRB_RIGHT(*root, field); \
+ } \
+ } \
+ while (parent) { \
+ root = LLRB_EDGE(head, parent, field); \
+ parent = LLRB_PARENT(parent, field); \
+ name##_LLRB_FIXUP(root); \
+ } \
+ if (LLRB_ROOT(head)) \
+ LLRB_COLOR(LLRB_ROOT(head), field) = LLRB_BLACK; \
+ return deleted; \
+} \
+attr struct type *name##_LLRB_FIND(struct name *head, struct type *key) { \
+ struct type *elm = LLRB_ROOT(head); \
+ while (elm) { \
+ int comp = (cmp)(key, elm); \
+ if (comp < 0) \
+ elm = LLRB_LEFT(elm, field); \
+ else if (comp > 0) \
+ elm = LLRB_RIGHT(elm, field); \
+ else \
+ return elm; \
+ } \
+ return 0; \
+} \
+attr struct type *name##_LLRB_MIN(struct type *elm) { \
+ while (elm && LLRB_LEFT(elm, field)) \
+ elm = LLRB_LEFT(elm, field); \
+ return elm; \
+} \
+attr struct type *name##_LLRB_MAX(struct type *elm) { \
+ while (elm && LLRB_RIGHT(elm, field)) \
+ elm = LLRB_RIGHT(elm, field); \
+ return elm; \
+} \
+attr struct type *name##_LLRB_NEXT(struct type *elm) { \
+ if (LLRB_RIGHT(elm, field)) { \
+ return name##_LLRB_MIN(LLRB_RIGHT(elm, field)); \
+ } else if (LLRB_PARENT(elm, field)) { \
+ if (elm == LLRB_LEFT(LLRB_PARENT(elm, field), field)) \
+ return LLRB_PARENT(elm, field); \
+ while (LLRB_PARENT(elm, field) && elm == LLRB_RIGHT(LLRB_PARENT(elm, field), field)) \
+ elm = LLRB_PARENT(elm, field); \
+ return LLRB_PARENT(elm, field); \
+ } else return 0; \
+}
+
+#define LLRB_INSERT(name, head, elm) name##_LLRB_INSERT((head), (elm))
+#define LLRB_DELETE(name, head, elm) name##_LLRB_DELETE((head), (elm))
+#define LLRB_REMOVE(name, head, elm) name##_LLRB_DELETE((head), (elm))
+#define LLRB_FIND(name, head, elm) name##_LLRB_FIND((head), (elm))
+#define LLRB_MIN(name, head) name##_LLRB_MIN(LLRB_ROOT((head)))
+#define LLRB_MAX(name, head) name##_LLRB_MAX(LLRB_ROOT((head)))
+#define LLRB_NEXT(name, head, elm) name##_LLRB_NEXT((elm))
+
+#define LLRB_FOREACH(elm, name, head) \
+for ((elm) = LLRB_MIN(name, head); (elm); (elm) = name##_LLRB_NEXT((elm)))
+
+#endif /* LLRB_H */
+
+
+#include <stdlib.h>
+
+#include "timeout.h"
+#include "bench.h"
+
+
+struct rbtimeout {
+ timeout_t expires;
+
+ int pending;
+
+ LLRB_ENTRY(rbtimeout) rbe;
+};
+
+struct rbtimeouts {
+ timeout_t curtime;
+ LLRB_HEAD(tree, rbtimeout) tree;
+};
+
+
+static int timeoutcmp(struct rbtimeout *a, struct rbtimeout *b) {
+ if (a->expires < b->expires) {
+ return -1;
+ } else if (a->expires > b->expires) {
+ return 1;
+ } else if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return 1;
+ } else {
+ return 0;
+ }
+} /* timeoutcmp() */
+
+LLRB_GENERATE_STATIC(tree, rbtimeout, rbe, timeoutcmp)
+
+static void *init(struct timeout *timeout, size_t count, int verbose) {
+ struct rbtimeouts *T;
+ size_t i;
+
+ T = malloc(sizeof *T);
+ T->curtime = 0;
+ LLRB_INIT(&T->tree);
+
+ for (i = 0; i < count; i++) {
+ struct rbtimeout *to = (void *)&timeout[i];
+ to->expires = 0;
+ to->pending = 0;
+ }
+
+ return T;
+} /* init() */
+
+
+static void add(void *ctx, struct timeout *_to, timeout_t expires) {
+ struct rbtimeouts *T = ctx;
+ struct rbtimeout *to = (void *)_to;
+
+ if (to->pending)
+ LLRB_REMOVE(tree, &T->tree, to);
+
+ to->expires = T->curtime + expires;
+ LLRB_INSERT(tree, &T->tree, to);
+ to->pending = 1;
+} /* add() */
+
+
+static void del(void *ctx, struct timeout *_to) {
+ struct rbtimeouts *T = ctx;
+ struct rbtimeout *to = (void *)_to;
+
+ LLRB_REMOVE(tree, &T->tree, to);
+ to->pending = 0;
+ to->expires = 0;
+} /* del() */
+
+
+static struct timeout *get(void *ctx) {
+ struct rbtimeouts *T = ctx;
+ struct rbtimeout *to;
+
+ if ((to = LLRB_MIN(tree, &T->tree)) && to->expires <= T->curtime) {
+ LLRB_REMOVE(tree, &T->tree, to);
+ to->pending = 0;
+ to->expires = 0;
+
+ return (void *)to;
+ }
+
+ return NULL;
+} /* get() */
+
+
+static void update(void *ctx, timeout_t ts) {
+ struct rbtimeouts *T = ctx;
+ T->curtime = ts;
+} /* update() */
+
+
+static void check(void *ctx) {
+ return;
+} /* check() */
+
+
+static int empty(void *ctx) {
+ struct rbtimeouts *T = ctx;
+
+ return LLRB_EMPTY(&T->tree);
+} /* empty() */
+
+
+static void destroy(void *ctx) {
+ free(ctx);
+ return;
+} /* destroy() */
+
+
+const struct benchops benchops = {
+ .init = &init,
+ .add = &add,
+ .del = &del,
+ .get = &get,
+ .update = &update,
+ .check = &check,
+ .empty = &empty,
+ .destroy = &destroy,
+};
+
diff --git a/bindings/rs-timeout/timeout/bench/bench-wheel.c b/bindings/rs-timeout/timeout/bench/bench-wheel.c
new file mode 100644
index 0000000..0cba1af
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/bench-wheel.c
@@ -0,0 +1,81 @@
+#include <stdlib.h>
+
+#define TIMEOUT_PUBLIC static
+
+#include "timeout.h"
+#include "timeout.c"
+#include "bench.h"
+
+
+static void *init(struct timeout *timeout, size_t count, int verbose) {
+ struct timeouts *T;
+ size_t i;
+ int error;
+
+ T = timeouts_open(TIMEOUT_mHZ, &error);
+
+ for (i = 0; i < count; i++) {
+ timeout_init(&timeout[i], 0);
+ }
+
+#if TIMEOUT_DEBUG - 0
+ timeout_debug = verbose;
+#endif
+
+ return T;
+} /* init() */
+
+
+static void add(void *T, struct timeout *to, timeout_t expires) {
+ timeouts_add(T, to, expires);
+} /* add() */
+
+
+static void del(void *T, struct timeout *to) {
+ timeouts_del(T, to);
+} /* del() */
+
+
+static struct timeout *get(void *T) {
+ return timeouts_get(T);
+} /* get() */
+
+
+static void update(void *T, timeout_t ts) {
+ timeouts_update(T, ts);
+} /* update() */
+
+
+static void (check)(void *T) {
+ if (!timeouts_check(T, stderr))
+ _Exit(1);
+} /* check() */
+
+
+static int empty(void *T) {
+ return !(timeouts_pending(T) || timeouts_expired(T));
+} /* empty() */
+
+
+static struct timeout *next(void *T, struct timeouts_it *it) {
+ return timeouts_next(T, it);
+} /* next() */
+
+
+static void destroy(void *T) {
+ timeouts_close(T);
+} /* destroy() */
+
+
+const struct benchops benchops = {
+ .init = &init,
+ .add = &add,
+ .del = &del,
+ .get = &get,
+ .update = &update,
+ .check = &check,
+ .empty = &empty,
+ .next = &next,
+ .destroy = &destroy
+};
+
diff --git a/bindings/rs-timeout/timeout/bench/bench.c b/bindings/rs-timeout/timeout/bench/bench.c
new file mode 100644
index 0000000..0d4cee4
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/bench.c
@@ -0,0 +1,293 @@
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <dlfcn.h>
+
+#if __APPLE__
+#include <mach/mach_time.h>
+#endif
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "timeout.h"
+#include "bench.h"
+
+#if LUA_VERSION_NUM < 502
+static int lua_absindex(lua_State *L, int idx) {
+ return (idx > 0 || idx <= LUA_REGISTRYINDEX)? idx : lua_gettop(L) + idx + 1;
+} /* lua_absindex() */
+
+static void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) {
+ int i, t = lua_absindex(L, -1 - nup);
+
+ for (; l->name; l++) {
+ for (i = 0; i < nup; i++)
+ lua_pushvalue(L, -nup);
+ lua_pushcclosure(L, l->func, nup);
+ lua_setfield(L, t, l->name);
+ }
+
+ lua_pop(L, nup);
+} /* luaL_setfuncs() */
+
+#define luaL_newlibtable(L, l) \
+ lua_createtable(L, 0, (sizeof (l) / sizeof *(l)) - 1)
+
+#define luaL_newlib(L, l) \
+ (luaL_newlibtable((L), (l)), luaL_setfuncs((L), (l), 0))
+#endif
+
+#ifndef MAX
+#define MAX(a, b) (((a) > (b))? (a) : (b))
+#endif
+
+
+struct bench {
+ const char *path;
+ void *solib;
+ size_t count;
+ timeout_t timeout_max;
+ int verbose;
+
+ void *state;
+ struct timeout *timeout;
+ struct benchops ops;
+ timeout_t curtime;
+}; /* struct bench */
+
+
+#if __APPLE__
+static mach_timebase_info_data_t timebase;
+#endif
+
+
+static int long long monotime(void) {
+#if __APPLE__
+ unsigned long long abt;
+
+ abt = mach_absolute_time();
+ abt = abt * timebase.numer / timebase.denom;
+
+ return abt / 1000LL;
+#else
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ return (ts.tv_sec * 1000000L) + (ts.tv_nsec / 1000L);
+#endif
+} /* monotime() */
+
+
+static int bench_clock(lua_State *L) {
+ lua_pushnumber(L, (double)monotime() / 1000000L);
+
+ return 1;
+} /* bench_clock() */
+
+
+static int bench_new(lua_State *L) {
+ const char *path = luaL_checkstring(L, 1);
+ size_t count = luaL_optinteger(L, 2, 1000000);
+ timeout_t timeout_max = luaL_optinteger(L, 3, 300 * 1000000L);
+ int verbose = (lua_isnone(L, 4))? 0 : lua_toboolean(L, 4);
+ struct bench *B;
+ struct benchops *ops;
+
+ B = lua_newuserdata(L, sizeof *B);
+ memset(B, 0, sizeof *B);
+
+ luaL_getmetatable(L, "BENCH*");
+ lua_setmetatable(L, -2);
+
+ B->count = count;
+ B->timeout_max = timeout_max;
+ B->verbose = verbose;
+
+ if (!(B->timeout = calloc(count, sizeof *B->timeout)))
+ return luaL_error(L, "%s", strerror(errno));
+
+ if (!(B->solib = dlopen(path, RTLD_NOW|RTLD_LOCAL)))
+ return luaL_error(L, "%s: %s", path, dlerror());
+
+ if (!(ops = dlsym(B->solib, "benchops")))
+ return luaL_error(L, "%s: %s", path, dlerror());
+
+ B->ops = *ops;
+ B->state = B->ops.init(B->timeout, B->count, B->verbose);
+
+ return 1;
+} /* bench_new() */
+
+
+static int bench_add(lua_State *L) {
+ struct bench *B = lua_touserdata(L, 1);
+ unsigned i;
+ timeout_t t;
+
+ i = (lua_isnoneornil(L, 2))? random() % B->count : (unsigned)luaL_checkinteger(L, 2);
+ t = (lua_isnoneornil(L, 3))? random() % B->timeout_max : (unsigned)luaL_checkinteger(L, 3);
+
+ B->ops.add(B->state, &B->timeout[i], t);
+
+ return 0;
+} /* bench_add() */
+
+
+static int bench_del(lua_State *L) {
+ struct bench *B = lua_touserdata(L, 1);
+ size_t i = luaL_optinteger(L, 2, random() % B->count);
+ size_t j = luaL_optinteger(L, 3, i);
+
+ while (i <= j && i < B->count) {
+ B->ops.del(B->state, &B->timeout[i]);
+ ++i;
+ }
+
+ return 0;
+} /* bench_del() */
+
+
+static int bench_fill(lua_State *L) {
+ struct bench *B = lua_touserdata(L, 1);
+ size_t count = luaL_optinteger(L, 2, B->count);
+ long timeout_inc = luaL_optinteger(L, 3, -1), timeout_max = 0, timeout;
+ size_t i;
+
+ if (timeout_inc < 0) {
+ for (i = 0; i < count; i++) {
+ timeout = random() % B->timeout_max;
+ B->ops.add(B->state, &B->timeout[i], timeout);
+ timeout_max = MAX(timeout, timeout_max);
+ }
+ } else {
+ for (i = 0; i < count; i++) {
+ timeout = timeout_inc + i;
+ B->ops.add(B->state, &B->timeout[i], timeout_inc + i);
+ timeout_max = MAX(timeout, timeout_max);
+ }
+ }
+
+ lua_pushinteger(L, (lua_Integer)count);
+ lua_pushinteger(L, (lua_Integer)timeout_max);
+
+ return 2;
+} /* bench_fill() */
+
+
+static int bench_expire(lua_State *L) {
+ struct bench *B = lua_touserdata(L, 1);
+ unsigned count = luaL_optinteger(L, 2, B->count);
+ unsigned step = luaL_optinteger(L, 3, 300000);
+ size_t i = 0;
+
+ while (i < count && !B->ops.empty(B->state)) {
+ B->curtime += step;
+ B->ops.update(B->state, B->curtime);
+
+ while (B->ops.get(B->state))
+ i++;
+ }
+
+ lua_pushinteger(L, (lua_Integer)i);
+
+ return 1;
+} /* bench_expire() */
+
+
+static int bench_empty(lua_State *L) {
+ struct bench *B = lua_touserdata(L, 1);
+
+ lua_pushboolean(L, B->ops.empty(B->state));
+
+ return 1;
+} /* bench_empty() */
+
+
+static int bench__next(lua_State *L) {
+ struct bench *B = lua_touserdata(L, lua_upvalueindex(1));
+ struct timeouts_it *it = lua_touserdata(L, lua_upvalueindex(2));
+ struct timeout *to;
+
+ if (!B->ops.next || !(to = B->ops.next(B->state, it)))
+ return 0;
+
+ lua_pushinteger(L, luaL_optinteger(L, 2, 0) + 1);
+
+ lua_newtable(L);
+ lua_pushinteger(L, to->expires);
+ lua_setfield(L, -2, "expires");
+
+ return 2;
+} /* bench__next() */
+
+static int bench__pairs(lua_State *L) {
+ struct timeouts_it *it;
+
+ lua_settop(L, 1);
+
+ it = lua_newuserdata(L, sizeof *it);
+ TIMEOUTS_IT_INIT(it, TIMEOUTS_ALL);
+
+ lua_pushcclosure(L, &bench__next, 2);
+ lua_pushvalue(L, 1);
+ lua_pushinteger(L, 0);
+
+ return 3;
+} /* bench__pairs() */
+
+
+static int bench__gc(lua_State *L) {
+ struct bench *B = lua_touserdata(L, 1);
+
+ if (B->state) {
+ B->ops.destroy(B->state);
+ B->state = NULL;
+ }
+
+ return 0;
+} /* bench__gc() */
+
+
+static const luaL_Reg bench_methods[] = {
+ { "add", &bench_add },
+ { "del", &bench_del },
+ { "fill", &bench_fill },
+ { "expire", &bench_expire },
+ { "empty", &bench_empty },
+ { "close", &bench__gc },
+ { NULL, NULL }
+};
+
+static const luaL_Reg bench_metatable[] = {
+ { "__pairs", &bench__pairs },
+ { "__gc", &bench__gc },
+ { NULL, NULL }
+};
+
+static const luaL_Reg bench_globals[] = {
+ { "new", &bench_new },
+ { "clock", &bench_clock },
+ { NULL, NULL }
+};
+
+int luaopen_bench(lua_State *L) {
+#if __APPLE__
+ mach_timebase_info(&timebase);
+#endif
+
+ if (luaL_newmetatable(L, "BENCH*")) {
+ luaL_setfuncs(L, bench_metatable, 0);
+ luaL_newlib(L, bench_methods);
+ lua_setfield(L, -2, "__index");
+ }
+
+ luaL_newlib(L, bench_globals);
+
+ return 1;
+} /* luaopen_bench() */
+
diff --git a/bindings/rs-timeout/timeout/bench/bench.h b/bindings/rs-timeout/timeout/bench/bench.h
new file mode 100644
index 0000000..bc1f7cf
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/bench.h
@@ -0,0 +1,11 @@
+struct benchops {
+ void *(*init)(struct timeout *, size_t, int);
+ void (*add)(void *, struct timeout *, timeout_t);
+ void (*del)(void *, struct timeout *);
+ struct timeout *(*get)(void *);
+ void (*update)(void *, timeout_t);
+ void (*check)(void *);
+ int (*empty)(void *);
+ struct timeout *(*next)(void *, struct timeouts_it *);
+ void (*destroy)(void *);
+}; /* struct benchops() */
diff --git a/bindings/rs-timeout/timeout/bench/bench.plt b/bindings/rs-timeout/timeout/bench/bench.plt
new file mode 100644
index 0000000..6e143c6
--- /dev/null
+++ b/bindings/rs-timeout/timeout/bench/bench.plt
@@ -0,0 +1,19 @@
+set terminal postscript color
+
+set key top left
+set xlabel "Number of timeouts"
+set ylabel "Time\n(microseconds)"
+#set logscale x
+
+set title "Time spent installing timeouts" font ",20"
+plot 'heap-add.dat' using 1:($2*1000000) title "min-heap" with lines ls 1 lw 3 lc "red", \
+ 'wheel-add.dat' using 1:($2*1000000) title "hierarchical wheel" with lines ls 1 lw 3 lc "forest-green"
+
+set title "Time spent deleting timeouts" font ",20"
+plot 'heap-del.dat' using 1:($2*1000000) title "min-heap" with lines ls 1 lw 3 lc "red", \
+ 'wheel-del.dat' using 1:($2*1000000) title "hierarchical wheel" with lines ls 1 lw 3 lc "forest-green"
+
+set title "Time spent expiring timeouts\n(by iteratively updating clock ~1000 times)" font ",20"
+plot 'heap-expire.dat' using 1:($2*1000000) title "min-heap" with lines ls 1 lw 3 lc "red", \
+ 'wheel-expire.dat' using 1:($2*1000000) title "hierarchical wheel" with lines ls 1 lw 3 lc "forest-green"
+
diff --git a/bindings/rs-timeout/timeout/libtimeout.a b/bindings/rs-timeout/timeout/libtimeout.a
new file mode 100644
index 0000000..412b188
--- /dev/null
+++ b/bindings/rs-timeout/timeout/libtimeout.a
Binary files differ
diff --git a/bindings/rs-timeout/timeout/lua/Rules.mk b/bindings/rs-timeout/timeout/lua/Rules.mk
new file mode 100644
index 0000000..0f06fce
--- /dev/null
+++ b/bindings/rs-timeout/timeout/lua/Rules.mk
@@ -0,0 +1,20 @@
+$(LUA_APIS:%=$(top_builddir)/lua/%/timeout.so): $(top_srcdir)/lua/timeout-lua.c $(top_srcdir)/timeout.h $(top_srcdir)/timeout.c
+ mkdir -p $(@D)
+ @$(SHRC); echo_cmd $(CC) -o $@ $(top_srcdir)/lua/timeout-lua.c -I$(top_srcdir) -DWHEEL_BIT=$(WHEEL_BIT) -DWHEEL_NUM=$(WHEEL_NUM) $(LUA53_CPPFLAGS) $(ALL_CPPFLAGS) $(ALL_CFLAGS) $(ALL_SOFLAGS) $(ALL_LDFLAGS) $(ALL_LIBS)
+
+$(top_builddir)/lua/5.1/timeouts.so: $(top_builddir)/lua/5.1/timeout.so
+$(top_builddir)/lua/5.2/timeouts.so: $(top_builddir)/lua/5.2/timeout.so
+$(top_builddir)/lua/5.3/timeouts.so: $(top_builddir)/lua/5.3/timeout.so
+
+$(LUA_APIS:%=$(top_builddir)/lua/%/timeouts.so):
+ cd $(@D) && ln -fs timeout.so timeouts.so
+
+lua-5.1: $(top_builddir)/lua/5.1/timeout.so $(top_builddir)/lua/5.1/timeouts.so
+lua-5.2: $(top_builddir)/lua/5.2/timeout.so $(top_builddir)/lua/5.2/timeouts.so
+lua-5.3: $(top_builddir)/lua/5.3/timeout.so $(top_builddir)/lua/5.3/timeouts.so
+
+lua-clean:
+ $(RM) -r $(top_builddir)/lua/5.?
+
+clean: lua-clean
+
diff --git a/bindings/rs-timeout/timeout/lua/timeout-lua.c b/bindings/rs-timeout/timeout/lua/timeout-lua.c
new file mode 100644
index 0000000..4d4e54c
--- /dev/null
+++ b/bindings/rs-timeout/timeout/lua/timeout-lua.c
@@ -0,0 +1,396 @@
+#include <assert.h>
+#include <string.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#if LUA_VERSION_NUM != 503
+#error only Lua 5.3 supported
+#endif
+
+#define TIMEOUT_PUBLIC static
+#include "timeout.h"
+#include "timeout.c"
+
+#define TIMEOUT_METANAME "struct timeout"
+#define TIMEOUTS_METANAME "struct timeouts*"
+
+static struct timeout *
+to_checkudata(lua_State *L, int index)
+{
+ return luaL_checkudata(L, index, TIMEOUT_METANAME);
+}
+
+static struct timeouts *
+tos_checkudata(lua_State *L, int index)
+{
+ return *(struct timeouts **)luaL_checkudata(L, index, TIMEOUTS_METANAME);
+}
+
+static void
+tos_bind(lua_State *L, int tos_index, int to_index)
+{
+ lua_getuservalue(L, tos_index);
+ lua_pushlightuserdata(L, to_checkudata(L, to_index));
+ lua_pushvalue(L, to_index);
+ lua_rawset(L, -3);
+ lua_pop(L, 1);
+}
+
+static void
+tos_unbind(lua_State *L, int tos_index, int to_index)
+{
+ lua_getuservalue(L, tos_index);
+ lua_pushlightuserdata(L, to_checkudata(L, to_index));
+ lua_pushnil(L);
+ lua_rawset(L, -3);
+ lua_pop(L, 1);
+}
+
+static int
+to__index(lua_State *L)
+{
+ struct timeout *to = to_checkudata(L, 1);
+
+ if (lua_type(L, 2 == LUA_TSTRING)) {
+ const char *key = lua_tostring(L, 2);
+
+ if (!strcmp(key, "flags")) {
+ lua_pushinteger(L, to->flags);
+
+ return 1;
+ } else if (!strcmp(key, "expires")) {
+ lua_pushinteger(L, to->expires);
+
+ return 1;
+ }
+ }
+
+ if (LUA_TNIL != lua_getuservalue(L, 1)) {
+ lua_pushvalue(L, 2);
+ if (LUA_TNIL != lua_rawget(L, -2))
+ return 1;
+ }
+
+ lua_pushvalue(L, 2);
+ if (LUA_TNIL != lua_rawget(L, lua_upvalueindex(1)))
+ return 1;
+
+ return 0;
+}
+
+static int
+to__newindex(lua_State *L)
+{
+ if (LUA_TNIL == lua_getuservalue(L, 1)) {
+ lua_newtable(L);
+ lua_pushvalue(L, -1);
+ lua_setuservalue(L, 1);
+ }
+
+ lua_pushvalue(L, 2);
+ lua_pushvalue(L, 3);
+ lua_rawset(L, -3);
+
+ return 0;
+}
+
+static int
+to__gc(lua_State *L)
+{
+ struct timeout *to = to_checkudata(L, 1);
+
+ /*
+ * NB: On script exit it's possible for a timeout to still be
+ * associated with a timeouts object, particularly when the timeouts
+ * object was created first.
+ */
+ timeout_del(to);
+
+ return 0;
+}
+
+static int
+to_new(lua_State *L)
+{
+ int flags = luaL_optinteger(L, 1, 0);
+ struct timeout *to;
+
+ to = lua_newuserdata(L, sizeof *to);
+ timeout_init(to, flags);
+ luaL_setmetatable(L, TIMEOUT_METANAME);
+
+ return 1;
+}
+
+static const luaL_Reg to_methods[] = {
+ { NULL, NULL },
+};
+
+static const luaL_Reg to_metatable[] = {
+ { "__index", &to__index },
+ { "__newindex", &to__newindex },
+ { "__gc", &to__gc },
+ { NULL, NULL },
+};
+
+static const luaL_Reg to_globals[] = {
+ { "new", &to_new },
+ { NULL, NULL },
+};
+
+static void
+to_newmetatable(lua_State *L)
+{
+ if (luaL_newmetatable(L, TIMEOUT_METANAME)) {
+ /*
+ * fill metamethod table, capturing the methods table as an
+ * upvalue for use by __index metamethod
+ */
+ luaL_newlib(L, to_methods);
+ luaL_setfuncs(L, to_metatable, 1);
+ }
+}
+
+int
+luaopen_timeout(lua_State *L)
+{
+ to_newmetatable(L);
+
+ luaL_newlib(L, to_globals);
+ lua_pushinteger(L, TIMEOUT_INT);
+ lua_setfield(L, -2, "INT");
+ lua_pushinteger(L, TIMEOUT_ABS);
+ lua_setfield(L, -2, "ABS");
+
+ return 1;
+}
+
+static int
+tos_update(lua_State *L)
+{
+ struct timeouts *T = tos_checkudata(L, 1);
+ lua_Number n = luaL_checknumber(L, 2);
+
+ timeouts_update(T, timeouts_f2i(T, n));
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int
+tos_step(lua_State *L)
+{
+ struct timeouts *T = tos_checkudata(L, 1);
+ lua_Number n = luaL_checknumber(L, 2);
+
+ timeouts_step(T, timeouts_f2i(T, n));
+
+ lua_pushvalue(L, 1);
+
+ return 1;
+}
+
+static int
+tos_timeout(lua_State *L)
+{
+ struct timeouts *T = tos_checkudata(L, 1);
+
+ lua_pushnumber(L, timeouts_i2f(T, timeouts_timeout(T)));
+
+ return 1;
+}
+
+static int
+tos_add(lua_State *L)
+{
+ struct timeouts *T = tos_checkudata(L, 1);
+ struct timeout *to = to_checkudata(L, 2);
+ lua_Number timeout = luaL_checknumber(L, 3);
+
+ tos_bind(L, 1, 2);
+ timeouts_addf(T, to, timeout);
+
+ return lua_pushvalue(L, 1), 1;
+}
+
+static int
+tos_del(lua_State *L)
+{
+ struct timeouts *T = tos_checkudata(L, 1);
+ struct timeout *to = to_checkudata(L, 2);
+
+ timeouts_del(T, to);
+ tos_unbind(L, 1, 2);
+
+ return lua_pushvalue(L, 1), 1;
+}
+
+static int
+tos_get(lua_State *L)
+{
+ struct timeouts *T = tos_checkudata(L, 1);
+ struct timeout *to;
+
+ if (!(to = timeouts_get(T)))
+ return 0;
+
+ lua_getuservalue(L, 1);
+ lua_rawgetp(L, -1, to);
+
+ if (!timeout_pending(to))
+ tos_unbind(L, 1, lua_absindex(L, -1));
+
+ return 1;
+}
+
+static int
+tos_pending(lua_State *L)
+{
+ struct timeouts *T = tos_checkudata(L, 1);
+
+ lua_pushboolean(L, timeouts_pending(T));
+
+ return 1;
+}
+
+static int
+tos_expired(lua_State *L)
+{
+ struct timeouts *T = tos_checkudata(L, 1);
+
+ lua_pushboolean(L, timeouts_expired(T));
+
+ return 1;
+}
+
+static int
+tos_check(lua_State *L)
+{
+ struct timeouts *T = tos_checkudata(L, 1);
+
+ lua_pushboolean(L, timeouts_check(T, NULL));
+
+ return 1;
+}
+
+static int
+tos__next(lua_State *L)
+{
+ struct timeouts *T = tos_checkudata(L, lua_upvalueindex(1));
+ struct timeouts_it *it = lua_touserdata(L, lua_upvalueindex(2));
+ struct timeout *to;
+
+ if (!(to = timeouts_next(T, it)))
+ return 0;
+
+ lua_getuservalue(L, lua_upvalueindex(1));
+ lua_rawgetp(L, -1, to);
+
+ return 1;
+}
+
+static int
+tos_timeouts(lua_State *L)
+{
+ int flags = luaL_checkinteger(L, 2);
+ struct timeouts_it *it;
+
+ tos_checkudata(L, 1);
+ lua_pushvalue(L, 1);
+ it = lua_newuserdata(L, sizeof *it);
+ TIMEOUTS_IT_INIT(it, flags);
+ lua_pushcclosure(L, &tos__next, 2);
+
+ return 1;
+}
+
+static int
+tos__gc(lua_State *L)
+{
+ struct timeouts **tos = luaL_checkudata(L, 1, TIMEOUTS_METANAME);
+ struct timeout *to;
+
+ TIMEOUTS_FOREACH(to, *tos, TIMEOUTS_ALL) {
+ timeouts_del(*tos, to);
+ }
+
+ timeouts_close(*tos);
+ *tos = NULL;
+
+ return 0;
+}
+
+static int
+tos_new(lua_State *L)
+{
+ timeout_t hz = luaL_optinteger(L, 1, 0);
+ struct timeouts **T;
+ int error;
+
+ T = lua_newuserdata(L, sizeof *T);
+ luaL_setmetatable(L, TIMEOUTS_METANAME);
+
+ lua_newtable(L);
+ lua_setuservalue(L, -2);
+
+ if (!(*T = timeouts_open(hz, &error)))
+ return luaL_error(L, "%s", strerror(error));
+
+ return 1;
+}
+
+static const luaL_Reg tos_methods[] = {
+ { "update", &tos_update },
+ { "step", &tos_step },
+ { "timeout", &tos_timeout },
+ { "add", &tos_add },
+ { "del", &tos_del },
+ { "get", &tos_get },
+ { "pending", &tos_pending },
+ { "expired", &tos_expired },
+ { "check", &tos_check },
+ { "timeouts", &tos_timeouts },
+ { NULL, NULL },
+};
+
+static const luaL_Reg tos_metatable[] = {
+ { "__gc", &tos__gc },
+ { NULL, NULL },
+};
+
+static const luaL_Reg tos_globals[] = {
+ { "new", &tos_new },
+ { NULL, NULL },
+};
+
+static void
+tos_newmetatable(lua_State *L)
+{
+ if (luaL_newmetatable(L, TIMEOUTS_METANAME)) {
+ luaL_setfuncs(L, tos_metatable, 0);
+ luaL_newlib(L, tos_methods);
+ lua_setfield(L, -2, "__index");
+ }
+}
+
+int
+luaopen_timeouts(lua_State *L)
+{
+ to_newmetatable(L);
+ tos_newmetatable(L);
+
+ luaL_newlib(L, tos_globals);
+ lua_pushinteger(L, TIMEOUTS_PENDING);
+ lua_setfield(L, -2, "PENDING");
+ lua_pushinteger(L, TIMEOUTS_EXPIRED);
+ lua_setfield(L, -2, "EXPIRED");
+ lua_pushinteger(L, TIMEOUTS_ALL);
+ lua_setfield(L, -2, "ALL");
+ lua_pushinteger(L, TIMEOUTS_CLEAR);
+ lua_setfield(L, -2, "CLEAR");
+
+ return 1;
+}
diff --git a/bindings/rs-timeout/timeout/test-timeout b/bindings/rs-timeout/timeout/test-timeout
new file mode 100644
index 0000000..738d100
--- /dev/null
+++ b/bindings/rs-timeout/timeout/test-timeout
Binary files differ
diff --git a/bindings/rs-timeout/timeout/test-timeout.c b/bindings/rs-timeout/timeout/test-timeout.c
new file mode 100644
index 0000000..ab9f72d
--- /dev/null
+++ b/bindings/rs-timeout/timeout/test-timeout.c
@@ -0,0 +1,530 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <limits.h>
+
+#include "timeout.h"
+
+#define THE_END_OF_TIME ((timeout_t)-1)
+
+static int check_misc(void) {
+ if (TIMEOUT_VERSION != timeout_version())
+ return 1;
+ if (TIMEOUT_V_REL != timeout_v_rel())
+ return 1;
+ if (TIMEOUT_V_API != timeout_v_api())
+ return 1;
+ if (TIMEOUT_V_ABI != timeout_v_abi())
+ return 1;
+ if (strcmp(timeout_vendor(), TIMEOUT_VENDOR))
+ return 1;
+ return 0;
+}
+
+static int check_open_close(timeout_t hz_set, timeout_t hz_expect) {
+ int err=0;
+ struct timeouts *tos = timeouts_open(hz_set, &err);
+ if (!tos)
+ return 1;
+ if (err)
+ return 1;
+ if (hz_expect != timeouts_hz(tos))
+ return 1;
+ timeouts_close(tos);
+ return 0;
+}
+
+/* Not very random */
+static timeout_t random_to(timeout_t min, timeout_t max)
+{
+ if (max <= min)
+ return min;
+ /* Not actually all that random, but should exercise the code. */
+ timeout_t rand64 = random() * (timeout_t)INT_MAX + random();
+ return min + (rand64 % (max-min));
+}
+
+/* configuration for check_randomized */
+struct rand_cfg {
+ /* When creating timeouts, smallest possible delay */
+ timeout_t min_timeout;
+ /* When creating timeouts, largest possible delay */
+ timeout_t max_timeout;
+ /* First time to start the clock at. */
+ timeout_t start_at;
+ /* Do not advance the clock past this time. */
+ timeout_t end_at;
+ /* Number of timeouts to create and monitor. */
+ int n_timeouts;
+ /* Advance the clock by no more than this each step. */
+ timeout_t max_step;
+ /* Use relative timers and stepping */
+ int relative;
+ /* Every time the clock ticks, try removing this many timeouts at
+ * random. */
+ int try_removing;
+ /* When we're done, advance the clock to the end of time. */
+ int finalize;
+};
+
+static int check_randomized(const struct rand_cfg *cfg)
+{
+#define FAIL() do { \
+ printf("Failure on line %d\n", __LINE__); \
+ goto done; \
+ } while (0)
+
+ int i, err;
+ int rv = 1;
+ struct timeout *t = calloc(cfg->n_timeouts, sizeof(struct timeout));
+ timeout_t *timeouts = calloc(cfg->n_timeouts, sizeof(timeout_t));
+ uint8_t *fired = calloc(cfg->n_timeouts, sizeof(uint8_t));
+ uint8_t *found = calloc(cfg->n_timeouts, sizeof(uint8_t));
+ uint8_t *deleted = calloc(cfg->n_timeouts, sizeof(uint8_t));
+ struct timeouts *tos = timeouts_open(0, &err);
+ timeout_t now = cfg->start_at;
+ int n_added_pending = 0, cnt_added_pending = 0;
+ int n_added_expired = 0, cnt_added_expired = 0;
+ struct timeouts_it it_p, it_e, it_all;
+ int p_done = 0, e_done = 0, all_done = 0;
+ struct timeout *to = NULL;
+ const int rel = cfg->relative;
+
+ if (!t || !timeouts || !tos || !fired || !found || !deleted)
+ FAIL();
+ timeouts_update(tos, cfg->start_at);
+
+ for (i = 0; i < cfg->n_timeouts; ++i) {
+ if (&t[i] != timeout_init(&t[i], rel ? 0 : TIMEOUT_ABS))
+ FAIL();
+ if (timeout_pending(&t[i]))
+ FAIL();
+ if (timeout_expired(&t[i]))
+ FAIL();
+
+ timeouts[i] = random_to(cfg->min_timeout, cfg->max_timeout);
+
+ timeouts_add(tos, &t[i], timeouts[i] - (rel ? now : 0));
+ if (timeouts[i] <= cfg->start_at) {
+ if (timeout_pending(&t[i]))
+ FAIL();
+ if (! timeout_expired(&t[i]))
+ FAIL();
+ ++n_added_expired;
+ } else {
+ if (! timeout_pending(&t[i]))
+ FAIL();
+ if (timeout_expired(&t[i]))
+ FAIL();
+ ++n_added_pending;
+ }
+ }
+
+ if (!!n_added_pending != timeouts_pending(tos))
+ FAIL();
+ if (!!n_added_expired != timeouts_expired(tos))
+ FAIL();
+
+ /* Test foreach, interleaving a few iterators. */
+ TIMEOUTS_IT_INIT(&it_p, TIMEOUTS_PENDING);
+ TIMEOUTS_IT_INIT(&it_e, TIMEOUTS_EXPIRED);
+ TIMEOUTS_IT_INIT(&it_all, TIMEOUTS_ALL);
+ while (! (p_done && e_done && all_done)) {
+ if (!p_done) {
+ to = timeouts_next(tos, &it_p);
+ if (to) {
+ i = to - &t[0];
+ ++found[i];
+ ++cnt_added_pending;
+ } else {
+ p_done = 1;
+ }
+ }
+ if (!e_done) {
+ to = timeouts_next(tos, &it_e);
+ if (to) {
+ i = to - &t[0];
+ ++found[i];
+ ++cnt_added_expired;
+ } else {
+ e_done = 1;
+ }
+ }
+ if (!all_done) {
+ to = timeouts_next(tos, &it_all);
+ if (to) {
+ i = to - &t[0];
+ ++found[i];
+ } else {
+ all_done = 1;
+ }
+ }
+ }
+
+ for (i = 0; i < cfg->n_timeouts; ++i) {
+ if (found[i] != 2)
+ FAIL();
+ }
+ if (cnt_added_expired != n_added_expired)
+ FAIL();
+ if (cnt_added_pending != n_added_pending)
+ FAIL();
+
+ while (NULL != (to = timeouts_get(tos))) {
+ i = to - &t[0];
+ assert(&t[i] == to);
+ if (timeouts[i] > cfg->start_at)
+ FAIL(); /* shouldn't have happened yet */
+
+ --n_added_expired; /* drop expired timeouts. */
+ ++fired[i];
+ }
+
+ if (n_added_expired != 0)
+ FAIL();
+
+ while (now < cfg->end_at) {
+ int n_fired_this_time = 0;
+ timeout_t first_at = timeouts_timeout(tos) + now;
+
+ timeout_t oldtime = now;
+ timeout_t step = random_to(1, cfg->max_step);
+ int another;
+ now += step;
+ if (rel)
+ timeouts_step(tos, step);
+ else
+ timeouts_update(tos, now);
+
+ for (i = 0; i < cfg->try_removing; ++i) {
+ int idx = random() % cfg->n_timeouts;
+ if (! fired[idx]) {
+ timeout_del(&t[idx]);
+ ++deleted[idx];
+ }
+ }
+
+ another = (timeouts_timeout(tos) == 0);
+
+ while (NULL != (to = timeouts_get(tos))) {
+ if (! another)
+ FAIL(); /* Thought we saw the last one! */
+ i = to - &t[0];
+ assert(&t[i] == to);
+ if (timeouts[i] > now)
+ FAIL(); /* shouldn't have happened yet */
+ if (timeouts[i] <= oldtime)
+ FAIL(); /* should have happened already */
+ if (timeouts[i] < first_at)
+ FAIL(); /* first_at should've been earlier */
+ fired[i]++;
+ n_fired_this_time++;
+ another = (timeouts_timeout(tos) == 0);
+ }
+ if (n_fired_this_time && first_at > now)
+ FAIL(); /* first_at should've been earlier */
+ if (another)
+ FAIL(); /* Huh? We think there are more? */
+ if (!timeouts_check(tos, stderr))
+ FAIL();
+ }
+
+ for (i = 0; i < cfg->n_timeouts; ++i) {
+ if (fired[i] > 1)
+ FAIL(); /* Nothing fired twice. */
+ if (timeouts[i] <= now) {
+ if (!(fired[i] || deleted[i]))
+ FAIL();
+ } else {
+ if (fired[i])
+ FAIL();
+ }
+ if (fired[i] && deleted[i])
+ FAIL();
+ if (cfg->finalize > 1) {
+ if (!fired[i])
+ timeout_del(&t[i]);
+ }
+ }
+
+ /* Now nothing more should fire between now and the end of time. */
+ if (cfg->finalize) {
+ timeouts_update(tos, THE_END_OF_TIME);
+ if (cfg->finalize > 1) {
+ if (timeouts_get(tos))
+ FAIL();
+ TIMEOUTS_FOREACH(to, tos, TIMEOUTS_ALL)
+ FAIL();
+ }
+ }
+ rv = 0;
+
+ done:
+ if (tos) timeouts_close(tos);
+ if (t) free(t);
+ if (timeouts) free(timeouts);
+ if (fired) free(fired);
+ if (found) free(found);
+ if (deleted) free(deleted);
+ return rv;
+}
+
+struct intervals_cfg {
+ const timeout_t *timeouts;
+ int n_timeouts;
+ timeout_t start_at;
+ timeout_t end_at;
+ timeout_t skip;
+};
+
+int
+check_intervals(struct intervals_cfg *cfg)
+{
+ int i, err;
+ int rv = 1;
+ struct timeout *to;
+ struct timeout *t = calloc(cfg->n_timeouts, sizeof(struct timeout));
+ unsigned *fired = calloc(cfg->n_timeouts, sizeof(unsigned));
+ struct timeouts *tos = timeouts_open(0, &err);
+
+ timeout_t now = cfg->start_at;
+ if (!t || !tos || !fired)
+ FAIL();
+
+ timeouts_update(tos, now);
+
+ for (i = 0; i < cfg->n_timeouts; ++i) {
+ if (&t[i] != timeout_init(&t[i], TIMEOUT_INT))
+ FAIL();
+ if (timeout_pending(&t[i]))
+ FAIL();
+ if (timeout_expired(&t[i]))
+ FAIL();
+
+ timeouts_add(tos, &t[i], cfg->timeouts[i]);
+ if (! timeout_pending(&t[i]))
+ FAIL();
+ if (timeout_expired(&t[i]))
+ FAIL();
+ }
+
+ while (now < cfg->end_at) {
+ timeout_t delay = timeouts_timeout(tos);
+ if (cfg->skip && delay < cfg->skip)
+ delay = cfg->skip;
+ timeouts_step(tos, delay);
+ now += delay;
+
+ while (NULL != (to = timeouts_get(tos))) {
+ i = to - &t[0];
+ assert(&t[i] == to);
+ fired[i]++;
+ if (0 != (to->expires - cfg->start_at) % cfg->timeouts[i])
+ FAIL();
+ if (to->expires <= now)
+ FAIL();
+ if (to->expires > now + cfg->timeouts[i])
+ FAIL();
+ }
+ if (!timeouts_check(tos, stderr))
+ FAIL();
+ }
+
+ timeout_t duration = now - cfg->start_at;
+ for (i = 0; i < cfg->n_timeouts; ++i) {
+ if (cfg->skip) {
+ if (fired[i] > duration / cfg->timeouts[i])
+ FAIL();
+ } else {
+ if (fired[i] != duration / cfg->timeouts[i])
+ FAIL();
+ }
+ if (!timeout_pending(&t[i]))
+ FAIL();
+ }
+
+ rv = 0;
+ done:
+ if (t) free(t);
+ if (fired) free(fired);
+ if (tos) free(tos);
+ return rv;
+}
+
+int
+main(int argc, char **argv)
+{
+ int j;
+ int n_failed = 0;
+#define DO(fn) do { \
+ printf("."); fflush(stdout); \
+ if (fn) { \
+ ++n_failed; \
+ printf("%s failed\n", #fn); \
+ } \
+ } while (0)
+
+#define DO_N(n, fn) do { \
+ for (j = 0; j < (n); ++j) { \
+ DO(fn); \
+ } \
+ } while (0)
+
+ DO(check_misc());
+ DO(check_open_close(1000, 1000));
+ DO(check_open_close(0, TIMEOUT_mHZ));
+
+ struct rand_cfg cfg1 = {
+ .min_timeout = 1,
+ .max_timeout = 100,
+ .start_at = 5,
+ .end_at = 1000,
+ .n_timeouts = 1000,
+ .max_step = 10,
+ .relative = 0,
+ .try_removing = 0,
+ .finalize = 2,
+ };
+ DO_N(300,check_randomized(&cfg1));
+
+ struct rand_cfg cfg2 = {
+ .min_timeout = 20,
+ .max_timeout = 1000,
+ .start_at = 10,
+ .end_at = 100,
+ .n_timeouts = 1000,
+ .max_step = 5,
+ .relative = 1,
+ .try_removing = 0,
+ .finalize = 2,
+ };
+ DO_N(300,check_randomized(&cfg2));
+
+ struct rand_cfg cfg2b = {
+ .min_timeout = 20,
+ .max_timeout = 1000,
+ .start_at = 10,
+ .end_at = 100,
+ .n_timeouts = 1000,
+ .max_step = 5,
+ .relative = 1,
+ .try_removing = 0,
+ .finalize = 1,
+ };
+ DO_N(300,check_randomized(&cfg2b));
+
+ struct rand_cfg cfg2c = {
+ .min_timeout = 20,
+ .max_timeout = 1000,
+ .start_at = 10,
+ .end_at = 100,
+ .n_timeouts = 1000,
+ .max_step = 5,
+ .relative = 1,
+ .try_removing = 0,
+ .finalize = 0,
+ };
+ DO_N(300,check_randomized(&cfg2c));
+
+ struct rand_cfg cfg3 = {
+ .min_timeout = 2000,
+ .max_timeout = ((uint64_t)1) << 50,
+ .start_at = 100,
+ .end_at = ((uint64_t)1) << 49,
+ .n_timeouts = 1000,
+ .max_step = ((uint64_t)1) << 31,
+ .relative = 0,
+ .try_removing = 0,
+ .finalize = 2,
+ };
+ DO_N(10,check_randomized(&cfg3));
+
+ struct rand_cfg cfg3b = {
+ .min_timeout = ((uint64_t)1) << 50,
+ .max_timeout = ((uint64_t)1) << 52,
+ .start_at = 100,
+ .end_at = ((uint64_t)1) << 53,
+ .n_timeouts = 1000,
+ .max_step = ((uint64_t)1)<<48,
+ .relative = 0,
+ .try_removing = 0,
+ .finalize = 2,
+ };
+ DO_N(10,check_randomized(&cfg3b));
+
+ struct rand_cfg cfg4 = {
+ .min_timeout = 2000,
+ .max_timeout = ((uint64_t)1) << 30,
+ .start_at = 100,
+ .end_at = ((uint64_t)1) << 26,
+ .n_timeouts = 10000,
+ .max_step = 1<<16,
+ .relative = 0,
+ .try_removing = 3,
+ .finalize = 2,
+ };
+ DO_N(10,check_randomized(&cfg4));
+
+ const timeout_t primes[] = {
+ 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,
+ 59,61,67,71,73,79,83,89,97
+ };
+ const timeout_t factors_of_1337[] = {
+ 1, 7, 191, 1337
+ };
+ const timeout_t multiples_of_five[] = {
+ 5, 10, 15, 20, 25, 30, 35, 40, 45, 50
+ };
+
+ struct intervals_cfg icfg1 = {
+ .timeouts = primes,
+ .n_timeouts = sizeof(primes)/sizeof(timeout_t),
+ .start_at = 50,
+ .end_at = 5322,
+ .skip = 0,
+ };
+ DO(check_intervals(&icfg1));
+
+ struct intervals_cfg icfg2 = {
+ .timeouts = factors_of_1337,
+ .n_timeouts = sizeof(factors_of_1337)/sizeof(timeout_t),
+ .start_at = 50,
+ .end_at = 50000,
+ .skip = 0,
+ };
+ DO(check_intervals(&icfg2));
+
+ struct intervals_cfg icfg3 = {
+ .timeouts = multiples_of_five,
+ .n_timeouts = sizeof(multiples_of_five)/sizeof(timeout_t),
+ .start_at = 49,
+ .end_at = 5333,
+ .skip = 0,
+ };
+ DO(check_intervals(&icfg3));
+
+ struct intervals_cfg icfg4 = {
+ .timeouts = primes,
+ .n_timeouts = sizeof(primes)/sizeof(timeout_t),
+ .start_at = 50,
+ .end_at = 5322,
+ .skip = 16,
+ };
+ DO(check_intervals(&icfg4));
+
+ if (n_failed) {
+ puts("\nFAIL");
+ } else {
+ puts("\nOK");
+ }
+ return !!n_failed;
+}
+
+/* TODO:
+
+ * Solve PR#3.
+
+ * Investigate whether any untaken branches are possible.
+
+ */
diff --git a/bindings/rs-timeout/timeout/timeout-bitops.c b/bindings/rs-timeout/timeout/timeout-bitops.c
new file mode 100644
index 0000000..d8325db
--- /dev/null
+++ b/bindings/rs-timeout/timeout/timeout-bitops.c
@@ -0,0 +1,249 @@
+#include <stdint.h>
+#ifdef _MSC_VER
+#include <intrin.h> /* _BitScanForward, _BitScanReverse */
+#endif
+
+/* First define ctz and clz functions; these are compiler-dependent if
+ * you want them to be fast. */
+#if defined(__GNUC__) && !defined(TIMEOUT_DISABLE_GNUC_BITOPS)
+
+/* On GCC and clang and some others, we can use __builtin functions. They
+ * are not defined for n==0, but timeout.s never calls them with n==0. */
+
+#define ctz64(n) __builtin_ctzll(n)
+#define clz64(n) __builtin_clzll(n)
+#if LONG_BITS == 32
+#define ctz32(n) __builtin_ctzl(n)
+#define clz32(n) __builtin_clzl(n)
+#else
+#define ctz32(n) __builtin_ctz(n)
+#define clz32(n) __builtin_clz(n)
+#endif
+
+#elif defined(_MSC_VER) && !defined(TIMEOUT_DISABLE_MSVC_BITOPS)
+
+/* On MSVC, we have these handy functions. We can ignore their return
+ * values, since we will never supply val == 0. */
+
+static __inline int ctz32(unsigned long val)
+{
+ DWORD zeros = 0;
+ _BitScanForward(&zeros, val);
+ return zeros;
+}
+static __inline int clz32(unsigned long val)
+{
+ DWORD zeros = 0;
+ _BitScanReverse(&zeros, val);
+ return zeros;
+}
+#ifdef _WIN64
+/* According to the documentation, these only exist on Win64. */
+static __inline int ctz64(uint64_t val)
+{
+ DWORD zeros = 0;
+ _BitScanForward64(&zeros, val);
+ return zeros;
+}
+static __inline int clz64(uint64_t val)
+{
+ DWORD zeros = 0;
+ _BitScanReverse64(&zeros, val);
+ return zeros;
+}
+#else
+static __inline int ctz64(uint64_t val)
+{
+ uint32_t lo = (uint32_t) val;
+ uint32_t hi = (uint32_t) (val >> 32);
+ return lo ? ctz32(lo) : 32 + ctz32(hi);
+}
+static __inline int clz64(uint64_t val)
+{
+ uint32_t lo = (uint32_t) val;
+ uint32_t hi = (uint32_t) (val >> 32);
+ return hi ? clz32(hi) : 32 + clz32(lo);
+}
+#endif
+
+/* End of MSVC case. */
+
+#else
+
+/* TODO: There are more clever ways to do this in the generic case. */
+
+
+#define process_(one, cz_bits, bits) \
+ if (x < ( one << (cz_bits - bits))) { rv += bits; x <<= bits; }
+
+#define process64(bits) process_((UINT64_C(1)), 64, (bits))
+static inline int clz64(uint64_t x)
+{
+ int rv = 0;
+
+ process64(32);
+ process64(16);
+ process64(8);
+ process64(4);
+ process64(2);
+ process64(1);
+ return rv;
+}
+#define process32(bits) process_((UINT32_C(1)), 32, (bits))
+static inline int clz32(uint32_t x)
+{
+ int rv = 0;
+
+ process32(16);
+ process32(8);
+ process32(4);
+ process32(2);
+ process32(1);
+ return rv;
+}
+
+#undef process_
+#undef process32
+#undef process64
+#define process_(one, bits) \
+ if ((x & ((one << (bits))-1)) == 0) { rv += bits; x >>= bits; }
+
+#define process64(bits) process_((UINT64_C(1)), bits)
+static inline int ctz64(uint64_t x)
+{
+ int rv = 0;
+
+ process64(32);
+ process64(16);
+ process64(8);
+ process64(4);
+ process64(2);
+ process64(1);
+ return rv;
+}
+
+#define process32(bits) process_((UINT32_C(1)), bits)
+static inline int ctz32(uint32_t x)
+{
+ int rv = 0;
+
+ process32(16);
+ process32(8);
+ process32(4);
+ process32(2);
+ process32(1);
+ return rv;
+}
+
+#undef process32
+#undef process64
+#undef process_
+
+/* End of generic case */
+
+#endif /* End of defining ctz */
+
+#ifdef TEST_BITOPS
+#include <stdio.h>
+#include <stdlib.h>
+
+static uint64_t testcases[] = {
+ 13371337 * 10,
+ 100,
+ 385789752,
+ 82574,
+ (((uint64_t)1)<<63) + (((uint64_t)1)<<31) + 10101
+};
+
+static int
+naive_clz(int bits, uint64_t v)
+{
+ int r = 0;
+ uint64_t bit = ((uint64_t)1) << (bits-1);
+ while (bit && 0 == (v & bit)) {
+ r++;
+ bit >>= 1;
+ }
+ /* printf("clz(%d,%lx) -> %d\n", bits, v, r); */
+ return r;
+}
+
+static int
+naive_ctz(int bits, uint64_t v)
+{
+ int r = 0;
+ uint64_t bit = 1;
+ while (bit && 0 == (v & bit)) {
+ r++;
+ bit <<= 1;
+ if (r == bits)
+ break;
+ }
+ /* printf("ctz(%d,%lx) -> %d\n", bits, v, r); */
+ return r;
+}
+
+static int
+check(uint64_t vv)
+{
+ uint32_t v32 = (uint32_t) vv;
+
+ if (vv == 0)
+ return 1; /* c[tl]z64(0) is undefined. */
+
+ if (ctz64(vv) != naive_ctz(64, vv)) {
+ printf("mismatch with ctz64: %d\n", ctz64(vv));
+ exit(1);
+ return 0;
+ }
+ if (clz64(vv) != naive_clz(64, vv)) {
+ printf("mismatch with clz64: %d\n", clz64(vv));
+ exit(1);
+ return 0;
+ }
+
+ if (v32 == 0)
+ return 1; /* c[lt]z(0) is undefined. */
+
+ if (ctz32(v32) != naive_ctz(32, v32)) {
+ printf("mismatch with ctz32: %d\n", ctz32(v32));
+ exit(1);
+ return 0;
+ }
+ if (clz32(v32) != naive_clz(32, v32)) {
+ printf("mismatch with clz32: %d\n", clz32(v32));
+ exit(1);
+ return 0;
+ }
+ return 1;
+}
+
+int
+main(int c, char **v)
+{
+ unsigned int i;
+ const unsigned int n = sizeof(testcases)/sizeof(testcases[0]);
+ int result = 0;
+
+ for (i = 0; i <= 63; ++i) {
+ uint64_t x = 1 << i;
+ if (!check(x))
+ result = 1;
+ --x;
+ if (!check(x))
+ result = 1;
+ }
+
+ for (i = 0; i < n; ++i) {
+ if (! check(testcases[i]))
+ result = 1;
+ }
+ if (result) {
+ puts("FAIL");
+ } else {
+ puts("OK");
+ }
+ return result;
+}
+#endif
+
diff --git a/bindings/rs-timeout/timeout/timeout-debug.h b/bindings/rs-timeout/timeout/timeout-debug.h
new file mode 100644
index 0000000..fc727a6
--- /dev/null
+++ b/bindings/rs-timeout/timeout/timeout-debug.h
@@ -0,0 +1,77 @@
+/*
+ * D E B U G R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#if TIMEOUT_DEBUG - 0
+#include <stdlib.h>
+#include <stdio.h>
+
+#undef TIMEOUT_DEBUG
+#define TIMEOUT_DEBUG 1
+#define DEBUG_LEVEL timeout_debug
+
+static int timeout_debug;
+
+#define SAYit_(lvl, fmt, ...) do { \
+ if (DEBUG_LEVEL >= (lvl)) \
+ fprintf(stderr, fmt "%s", __FILE__, __LINE__, __func__, __VA_ARGS__); \
+} while (0)
+
+#define SAYit(lvl, ...) SAYit_((lvl), "%s:%d:%s: " __VA_ARGS__, "\n")
+
+#define PANIC(...) do { \
+ SAYit(0, __VA_ARGS__); \
+ _Exit(EXIT_FAILURE); \
+} while (0)
+#else
+#undef TIMEOUT_DEBUG
+#define TIMEOUT_DEBUG 0
+#define DEBUG_LEVEL 0
+
+#define SAYit(...) (void)0
+#endif
+
+#define SAY(...) SAYit(1, __VA_ARGS__)
+#define HAI SAY("HAI")
+
+
+static inline char *fmt_(char *buf, uint64_t ts, int wheel_bit, int wheel_num) {
+ char *p = buf;
+ int wheel, n, i;
+
+ for (wheel = wheel_num - 2; wheel >= 0; wheel--) {
+ n = ((1 << wheel_bit) - 1) & (ts >> (wheel * WHEEL_BIT));
+
+ for (i = wheel_bit - 1; i >= 0; i--) {
+ *p++ = '0' + !!(n & (1 << i));
+ }
+
+ if (wheel != 0)
+ *p++ = ':';
+ }
+
+ *p = 0;
+
+ return buf;
+} /* fmt_() */
+
+#define fmt(ts) fmt_(((char[((1 << WHEEL_BIT) * WHEEL_NUM) + WHEEL_NUM + 1]){ 0 }), (ts), WHEEL_BIT, WHEEL_NUM)
+
+
+static inline char *bin64_(char *buf, uint64_t n, int wheel_bit) {
+ char *p = buf;
+ int i;
+
+ for (i = 0; i < (1 << wheel_bit); i++) {
+ *p++ = "01"[0x1 & (n >> (((1 << wheel_bit) - 1) - i))];
+ }
+
+ *p = 0;
+
+ return buf;
+} /* bin64_() */
+
+#define bin64(ts) bin64_(((char[((1 << WHEEL_BIT) * WHEEL_NUM) + 1]){ 0 }), (ts), WHEEL_BIT)
+
+
diff --git a/bindings/rs-timeout/timeout/timeout.c b/bindings/rs-timeout/timeout/timeout.c
new file mode 100644
index 0000000..e78f57d
--- /dev/null
+++ b/bindings/rs-timeout/timeout/timeout.c
@@ -0,0 +1,744 @@
+/* ==========================================================================
+ * timeout.c - Tickless hierarchical timing wheel.
+ * --------------------------------------------------------------------------
+ * Copyright (c) 2013, 2014 William Ahern
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * ==========================================================================
+ */
+#include <limits.h> /* CHAR_BIT */
+
+#include <stddef.h> /* NULL */
+#include <stdlib.h> /* malloc(3) free(3) */
+#include <stdio.h> /* FILE fprintf(3) */
+
+#include <inttypes.h> /* UINT64_C uint64_t */
+
+#include <string.h> /* memset(3) */
+
+#include <errno.h> /* errno */
+
+#include <sys/queue.h> /* TAILQ(3) */
+
+#include "timeout.h"
+
+#if TIMEOUT_DEBUG - 0
+#include "timeout-debug.h"
+#endif
+
+#ifdef TIMEOUT_DISABLE_RELATIVE_ACCESS
+#define TO_SET_TIMEOUTS(to, T) ((void)0)
+#else
+#define TO_SET_TIMEOUTS(to, T) ((to)->timeouts = (T))
+#endif
+
+/*
+ * A N C I L L A R Y R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define abstime_t timeout_t /* for documentation purposes */
+#define reltime_t timeout_t /* "" */
+
+#if !defined countof
+#define countof(a) (sizeof (a) / sizeof *(a))
+#endif
+
+#if !defined endof
+#define endof(a) (&(a)[countof(a)])
+#endif
+
+#if !defined MIN
+#define MIN(a, b) (((a) < (b))? (a) : (b))
+#endif
+
+#if !defined MAX
+#define MAX(a, b) (((a) > (b))? (a) : (b))
+#endif
+
+#if !defined TAILQ_CONCAT
+#define TAILQ_CONCAT(head1, head2, field) do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ } \
+} while (0)
+#endif
+
+#if !defined TAILQ_FOREACH_SAFE
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST(head); \
+ (var) && ((tvar) = TAILQ_NEXT(var, field), 1); \
+ (var) = (tvar))
+#endif
+
+
+/*
+ * B I T M A N I P U L A T I O N R O U T I N E S
+ *
+ * The macros and routines below implement wheel parameterization. The
+ * inputs are:
+ *
+ * WHEEL_BIT - The number of value bits mapped in each wheel. The
+ * lowest-order WHEEL_BIT bits index the lowest-order (highest
+ * resolution) wheel, the next group of WHEEL_BIT bits the
+ * higher wheel, etc.
+ *
+ * WHEEL_NUM - The number of wheels. WHEEL_BIT * WHEEL_NUM = the number of
+ * value bits used by all the wheels. For the default of 6 and
+ * 4, only the low 24 bits are processed. Any timeout value
+ * larger than this will cycle through again.
+ *
+ * The implementation uses bit fields to remember which slot in each wheel
+ * is populated, and to generate masks of expiring slots according to the
+ * current update interval (i.e. the "tickless" aspect). The slots to
+ * process in a wheel are (populated-set & interval-mask).
+ *
+ * WHEEL_BIT cannot be larger than 6 bits because 2^6 -> 64 is the largest
+ * number of slots which can be tracked in a uint64_t integer bit field.
+ * WHEEL_BIT cannot be smaller than 3 bits because of our rotr and rotl
+ * routines, which only operate on all the value bits in an integer, and
+ * there's no integer smaller than uint8_t.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#if !defined WHEEL_BIT
+#define WHEEL_BIT 6
+#endif
+
+#if !defined WHEEL_NUM
+#define WHEEL_NUM 4
+#endif
+
+#define WHEEL_LEN (1U << WHEEL_BIT)
+#define WHEEL_MAX (WHEEL_LEN - 1)
+#define WHEEL_MASK (WHEEL_LEN - 1)
+#define TIMEOUT_MAX ((TIMEOUT_C(1) << (WHEEL_BIT * WHEEL_NUM)) - 1)
+
+#include "timeout-bitops.c"
+
+#if WHEEL_BIT == 6
+#define ctz(n) ctz64(n)
+#define clz(n) clz64(n)
+#define fls(n) ((int)(64 - clz64(n)))
+#else
+#define ctz(n) ctz32(n)
+#define clz(n) clz32(n)
+#define fls(n) ((int)(32 - clz32(n)))
+#endif
+
+#if WHEEL_BIT == 6
+#define WHEEL_C(n) UINT64_C(n)
+#define WHEEL_PRIu PRIu64
+#define WHEEL_PRIx PRIx64
+
+typedef uint64_t wheel_t;
+
+#elif WHEEL_BIT == 5
+
+#define WHEEL_C(n) UINT32_C(n)
+#define WHEEL_PRIu PRIu32
+#define WHEEL_PRIx PRIx32
+
+typedef uint32_t wheel_t;
+
+#elif WHEEL_BIT == 4
+
+#define WHEEL_C(n) UINT16_C(n)
+#define WHEEL_PRIu PRIu16
+#define WHEEL_PRIx PRIx16
+
+typedef uint16_t wheel_t;
+
+#elif WHEEL_BIT == 3
+
+#define WHEEL_C(n) UINT8_C(n)
+#define WHEEL_PRIu PRIu8
+#define WHEEL_PRIx PRIx8
+
+typedef uint8_t wheel_t;
+
+#else
+#error invalid WHEEL_BIT value
+#endif
+
+
+static inline wheel_t rotl(const wheel_t v, int c) {
+ if (!(c &= (sizeof v * CHAR_BIT - 1)))
+ return v;
+
+ return (v << c) | (v >> (sizeof v * CHAR_BIT - c));
+} /* rotl() */
+
+
+static inline wheel_t rotr(const wheel_t v, int c) {
+ if (!(c &= (sizeof v * CHAR_BIT - 1)))
+ return v;
+
+ return (v >> c) | (v << (sizeof v * CHAR_BIT - c));
+} /* rotr() */
+
+
+/*
+ * T I M E R R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+TAILQ_HEAD(timeout_list, timeout);
+
+struct timeouts {
+ struct timeout_list wheel[WHEEL_NUM][WHEEL_LEN], expired;
+
+ wheel_t pending[WHEEL_NUM];
+
+ timeout_t curtime;
+ timeout_t hertz;
+}; /* struct timeouts */
+
+
+static struct timeouts *timeouts_init(struct timeouts *T, timeout_t hz) {
+ unsigned i, j;
+
+ for (i = 0; i < countof(T->wheel); i++) {
+ for (j = 0; j < countof(T->wheel[i]); j++) {
+ TAILQ_INIT(&T->wheel[i][j]);
+ }
+ }
+
+ TAILQ_INIT(&T->expired);
+
+ for (i = 0; i < countof(T->pending); i++) {
+ T->pending[i] = 0;
+ }
+
+ T->curtime = 0;
+ T->hertz = (hz)? hz : TIMEOUT_mHZ;
+
+ return T;
+} /* timeouts_init() */
+
+
+TIMEOUT_PUBLIC struct timeouts *timeouts_open(timeout_t hz, int *error) {
+ struct timeouts *T;
+
+ if ((T = malloc(sizeof *T)))
+ return timeouts_init(T, hz);
+
+ *error = errno;
+
+ return NULL;
+} /* timeouts_open() */
+
+
+static void timeouts_reset(struct timeouts *T) {
+ struct timeout_list reset;
+ struct timeout *to;
+ unsigned i, j;
+
+ TAILQ_INIT(&reset);
+
+ for (i = 0; i < countof(T->wheel); i++) {
+ for (j = 0; j < countof(T->wheel[i]); j++) {
+ TAILQ_CONCAT(&reset, &T->wheel[i][j], tqe);
+ }
+ }
+
+ TAILQ_CONCAT(&reset, &T->expired, tqe);
+
+ TAILQ_FOREACH(to, &reset, tqe) {
+ to->pending = NULL;
+ TO_SET_TIMEOUTS(to, NULL);
+ }
+} /* timeouts_reset() */
+
+
+TIMEOUT_PUBLIC void timeouts_close(struct timeouts *T) {
+ /*
+ * NOTE: Delete installed timeouts so timeout_pending() and
+ * timeout_expired() worked as expected.
+ */
+ timeouts_reset(T);
+
+ free(T);
+} /* timeouts_close() */
+
+
+TIMEOUT_PUBLIC timeout_t timeouts_hz(struct timeouts *T) {
+ return T->hertz;
+} /* timeouts_hz() */
+
+
+TIMEOUT_PUBLIC void timeouts_del(struct timeouts *T, struct timeout *to) {
+ if (to->pending) {
+ TAILQ_REMOVE(to->pending, to, tqe);
+
+ if (to->pending != &T->expired && TAILQ_EMPTY(to->pending)) {
+ ptrdiff_t index = to->pending - &T->wheel[0][0];
+ int wheel = index / WHEEL_LEN;
+ int slot = index % WHEEL_LEN;
+
+ T->pending[wheel] &= ~(WHEEL_C(1) << slot);
+ }
+
+ to->pending = NULL;
+ TO_SET_TIMEOUTS(to, NULL);
+ }
+} /* timeouts_del() */
+
+
+static inline reltime_t timeout_rem(struct timeouts *T, struct timeout *to) {
+ return to->expires - T->curtime;
+} /* timeout_rem() */
+
+
+static inline int timeout_wheel(timeout_t timeout) {
+ /* must be called with timeout != 0, so fls input is nonzero */
+ return (fls(MIN(timeout, TIMEOUT_MAX)) - 1) / WHEEL_BIT;
+} /* timeout_wheel() */
+
+
+static inline int timeout_slot(int wheel, timeout_t expires) {
+ return WHEEL_MASK & ((expires >> (wheel * WHEEL_BIT)) - !!wheel);
+} /* timeout_slot() */
+
+
+static void timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t expires) {
+ timeout_t rem;
+ int wheel, slot;
+
+ timeouts_del(T, to);
+
+ to->expires = expires;
+
+ TO_SET_TIMEOUTS(to, T);
+
+ if (expires > T->curtime) {
+ rem = timeout_rem(T, to);
+
+ /* rem is nonzero since:
+ * rem == timeout_rem(T,to),
+ * == to->expires - T->curtime
+ * and above we have expires > T->curtime.
+ */
+ wheel = timeout_wheel(rem);
+ slot = timeout_slot(wheel, to->expires);
+
+ to->pending = &T->wheel[wheel][slot];
+ TAILQ_INSERT_TAIL(to->pending, to, tqe);
+
+ T->pending[wheel] |= WHEEL_C(1) << slot;
+ } else {
+ to->pending = &T->expired;
+ TAILQ_INSERT_TAIL(to->pending, to, tqe);
+ }
+} /* timeouts_sched() */
+
+
+#ifndef TIMEOUT_DISABLE_INTERVALS
+static void timeouts_readd(struct timeouts *T, struct timeout *to) {
+ to->expires += to->interval;
+
+ if (to->expires <= T->curtime) {
+ /* If we've missed the next firing of this timeout, reschedule
+ * it to occur at the next multiple of its interval after
+ * the last time that it fired.
+ */
+ timeout_t n = T->curtime - to->expires;
+ timeout_t r = n % to->interval;
+ to->expires = T->curtime + (to->interval - r);
+ }
+
+ timeouts_sched(T, to, to->expires);
+} /* timeouts_readd() */
+#endif
+
+
+TIMEOUT_PUBLIC void timeouts_add(struct timeouts *T, struct timeout *to, timeout_t timeout) {
+#ifndef TIMEOUT_DISABLE_INTERVALS
+ if (to->flags & TIMEOUT_INT)
+ to->interval = MAX(1, timeout);
+#endif
+
+ if (to->flags & TIMEOUT_ABS)
+ timeouts_sched(T, to, timeout);
+ else
+ timeouts_sched(T, to, T->curtime + timeout);
+} /* timeouts_add() */
+
+
+TIMEOUT_PUBLIC void timeouts_update(struct timeouts *T, abstime_t curtime) {
+ timeout_t elapsed = curtime - T->curtime;
+ struct timeout_list todo;
+ int wheel;
+
+ TAILQ_INIT(&todo);
+
+ /*
+ * There's no avoiding looping over every wheel. It's best to keep
+ * WHEEL_NUM smallish.
+ */
+ for (wheel = 0; wheel < WHEEL_NUM; wheel++) {
+ wheel_t pending;
+
+ /*
+ * Calculate the slots expiring in this wheel
+ *
+ * If the elapsed time is greater than the maximum period of
+ * the wheel, mark every position as expiring.
+ *
+ * Otherwise, to determine the expired slots fill in all the
+ * bits between the last slot processed and the current
+ * slot, inclusive of the last slot. We'll bitwise-AND this
+ * with our pending set below.
+ *
+ * If a wheel rolls over, force a tick of the next higher
+ * wheel.
+ */
+ if ((elapsed >> (wheel * WHEEL_BIT)) > WHEEL_MAX) {
+ pending = (wheel_t)~WHEEL_C(0);
+ } else {
+ wheel_t _elapsed = WHEEL_MASK & (elapsed >> (wheel * WHEEL_BIT));
+ int oslot, nslot;
+
+ /*
+ * TODO: It's likely that at least one of the
+ * following three bit fill operations is redundant
+ * or can be replaced with a simpler operation.
+ */
+ oslot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT));
+ pending = rotl(((UINT64_C(1) << _elapsed) - 1), oslot);
+
+ nslot = WHEEL_MASK & (curtime >> (wheel * WHEEL_BIT));
+ pending |= rotr(rotl(((WHEEL_C(1) << _elapsed) - 1), nslot), _elapsed);
+ pending |= WHEEL_C(1) << nslot;
+ }
+
+ while (pending & T->pending[wheel]) {
+ /* ctz input cannot be zero: loop condition. */
+ int slot = ctz(pending & T->pending[wheel]);
+ TAILQ_CONCAT(&todo, &T->wheel[wheel][slot], tqe);
+ T->pending[wheel] &= ~(UINT64_C(1) << slot);
+ }
+
+ if (!(0x1 & pending))
+ break; /* break if we didn't wrap around end of wheel */
+
+ /* if we're continuing, the next wheel must tick at least once */
+ elapsed = MAX(elapsed, (WHEEL_LEN << (wheel * WHEEL_BIT)));
+ }
+
+ T->curtime = curtime;
+
+ while (!TAILQ_EMPTY(&todo)) {
+ struct timeout *to = TAILQ_FIRST(&todo);
+
+ TAILQ_REMOVE(&todo, to, tqe);
+ to->pending = NULL;
+
+ timeouts_sched(T, to, to->expires);
+ }
+
+ return;
+} /* timeouts_update() */
+
+
+TIMEOUT_PUBLIC void timeouts_step(struct timeouts *T, reltime_t elapsed) {
+ timeouts_update(T, T->curtime + elapsed);
+} /* timeouts_step() */
+
+
+TIMEOUT_PUBLIC bool timeouts_pending(struct timeouts *T) {
+ wheel_t pending = 0;
+ int wheel;
+
+ for (wheel = 0; wheel < WHEEL_NUM; wheel++) {
+ pending |= T->pending[wheel];
+ }
+
+ return !!pending;
+} /* timeouts_pending() */
+
+
+TIMEOUT_PUBLIC bool timeouts_expired(struct timeouts *T) {
+ return !TAILQ_EMPTY(&T->expired);
+} /* timeouts_expired() */
+
+
+/*
+ * Calculate the interval before needing to process any timeouts pending on
+ * any wheel.
+ *
+ * (This is separated from the public API routine so we can evaluate our
+ * wheel invariant assertions irrespective of the expired queue.)
+ *
+ * This might return a timeout value sooner than any installed timeout if
+ * only higher-order wheels have timeouts pending. We can only know when to
+ * process a wheel, not precisely when a timeout is scheduled. Our timeout
+ * accuracy could be off by 2^(N*M)-1 units where N is the wheel number and
+ * M is WHEEL_BIT. Only timeouts which have fallen through to wheel 0 can be
+ * known exactly.
+ *
+ * We should never return a timeout larger than the lowest actual timeout.
+ */
+static timeout_t timeouts_int(struct timeouts *T) {
+ timeout_t timeout = ~TIMEOUT_C(0), _timeout;
+ timeout_t relmask;
+ int wheel, slot;
+
+ relmask = 0;
+
+ for (wheel = 0; wheel < WHEEL_NUM; wheel++) {
+ if (T->pending[wheel]) {
+ slot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT));
+
+ /* ctz input cannot be zero: T->pending[wheel] is
+ * nonzero, so rotr() is nonzero. */
+ _timeout = (ctz(rotr(T->pending[wheel], slot)) + !!wheel) << (wheel * WHEEL_BIT);
+ /* +1 to higher order wheels as those timeouts are one rotation in the future (otherwise they'd be on a lower wheel or expired) */
+
+ _timeout -= relmask & T->curtime;
+ /* reduce by how much lower wheels have progressed */
+
+ timeout = MIN(_timeout, timeout);
+ }
+
+ relmask <<= WHEEL_BIT;
+ relmask |= WHEEL_MASK;
+ }
+
+ return timeout;
+} /* timeouts_int() */
+
+
+/*
+ * Calculate the interval our caller can wait before needing to process
+ * events.
+ */
+TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *T) {
+ if (!TAILQ_EMPTY(&T->expired))
+ return 0;
+
+ return timeouts_int(T);
+} /* timeouts_timeout() */
+
+
+TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *T) {
+ if (!TAILQ_EMPTY(&T->expired)) {
+ struct timeout *to = TAILQ_FIRST(&T->expired);
+
+ TAILQ_REMOVE(&T->expired, to, tqe);
+ to->pending = NULL;
+ TO_SET_TIMEOUTS(to, NULL);
+
+#ifndef TIMEOUT_DISABLE_INTERVALS
+ if ((to->flags & TIMEOUT_INT) && to->interval > 0)
+ timeouts_readd(T, to);
+#endif
+
+ return to;
+ } else {
+ return 0;
+ }
+} /* timeouts_get() */
+
+
+/*
+ * Use dumb looping to locate the earliest timeout pending on the wheel so
+ * our invariant assertions can check the result of our optimized code.
+ */
+static struct timeout *timeouts_min(struct timeouts *T) {
+ struct timeout *to, *min = NULL;
+ unsigned i, j;
+
+ for (i = 0; i < countof(T->wheel); i++) {
+ for (j = 0; j < countof(T->wheel[i]); j++) {
+ TAILQ_FOREACH(to, &T->wheel[i][j], tqe) {
+ if (!min || to->expires < min->expires)
+ min = to;
+ }
+ }
+ }
+
+ return min;
+} /* timeouts_min() */
+
+
+/*
+ * Check some basic algorithm invariants. If these invariants fail then
+ * something is definitely broken.
+ */
+#define report(...) do { \
+ if ((fp)) \
+ fprintf(fp, __VA_ARGS__); \
+} while (0)
+
+#define check(expr, ...) do { \
+ if (!(expr)) { \
+ report(__VA_ARGS__); \
+ return 0; \
+ } \
+} while (0)
+
+TIMEOUT_PUBLIC bool timeouts_check(struct timeouts *T, FILE *fp) {
+ timeout_t timeout;
+ struct timeout *to;
+
+ if ((to = timeouts_min(T))) {
+ check(to->expires > T->curtime, "missed timeout (expires:%" TIMEOUT_PRIu " <= curtime:%" TIMEOUT_PRIu ")\n", to->expires, T->curtime);
+
+ timeout = timeouts_int(T);
+ check(timeout <= to->expires - T->curtime, "wrong soft timeout (soft:%" TIMEOUT_PRIu " > hard:%" TIMEOUT_PRIu ") (expires:%" TIMEOUT_PRIu "; curtime:%" TIMEOUT_PRIu ")\n", timeout, (to->expires - T->curtime), to->expires, T->curtime);
+
+ timeout = timeouts_timeout(T);
+ check(timeout <= to->expires - T->curtime, "wrong soft timeout (soft:%" TIMEOUT_PRIu " > hard:%" TIMEOUT_PRIu ") (expires:%" TIMEOUT_PRIu "; curtime:%" TIMEOUT_PRIu ")\n", timeout, (to->expires - T->curtime), to->expires, T->curtime);
+ } else {
+ timeout = timeouts_timeout(T);
+
+ if (!TAILQ_EMPTY(&T->expired))
+ check(timeout == 0, "wrong soft timeout (soft:%" TIMEOUT_PRIu " != hard:%" TIMEOUT_PRIu ")\n", timeout, TIMEOUT_C(0));
+ else
+ check(timeout == ~TIMEOUT_C(0), "wrong soft timeout (soft:%" TIMEOUT_PRIu " != hard:%" TIMEOUT_PRIu ")\n", timeout, ~TIMEOUT_C(0));
+ }
+
+ return 1;
+} /* timeouts_check() */
+
+
+#define ENTER \
+ do { \
+ static const int pc0 = __LINE__; \
+ switch (pc0 + it->pc) { \
+ case __LINE__: (void)0
+
+#define SAVE_AND_DO(do_statement) \
+ do { \
+ it->pc = __LINE__ - pc0; \
+ do_statement; \
+ case __LINE__: (void)0; \
+ } while (0)
+
+#define YIELD(rv) \
+ SAVE_AND_DO(return (rv))
+
+#define LEAVE \
+ SAVE_AND_DO(break); \
+ } \
+ } while (0)
+
+TIMEOUT_PUBLIC struct timeout *timeouts_next(struct timeouts *T, struct timeouts_it *it) {
+ struct timeout *to;
+
+ ENTER;
+
+ if (it->flags & TIMEOUTS_EXPIRED) {
+ if (it->flags & TIMEOUTS_CLEAR) {
+ while ((to = timeouts_get(T))) {
+ YIELD(to);
+ }
+ } else {
+ TAILQ_FOREACH_SAFE(to, &T->expired, tqe, it->to) {
+ YIELD(to);
+ }
+ }
+ }
+
+ if (it->flags & TIMEOUTS_PENDING) {
+ for (it->i = 0; it->i < countof(T->wheel); it->i++) {
+ for (it->j = 0; it->j < countof(T->wheel[it->i]); it->j++) {
+ TAILQ_FOREACH_SAFE(to, &T->wheel[it->i][it->j], tqe, it->to) {
+ YIELD(to);
+ }
+ }
+ }
+ }
+
+ LEAVE;
+
+ return NULL;
+} /* timeouts_next */
+
+#undef LEAVE
+#undef YIELD
+#undef SAVE_AND_DO
+#undef ENTER
+
+
+/*
+ * T I M E O U T R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+TIMEOUT_PUBLIC struct timeout *timeout_init(struct timeout *to, int flags) {
+ memset(to, 0, sizeof *to);
+
+ to->flags = flags;
+
+ return to;
+} /* timeout_init() */
+
+
+#ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS
+TIMEOUT_PUBLIC bool timeout_pending(struct timeout *to) {
+ return to->pending && to->pending != &to->timeouts->expired;
+} /* timeout_pending() */
+
+
+TIMEOUT_PUBLIC bool timeout_expired(struct timeout *to) {
+ return to->pending && to->pending == &to->timeouts->expired;
+} /* timeout_expired() */
+
+
+TIMEOUT_PUBLIC void timeout_del(struct timeout *to) {
+ timeouts_del(to->timeouts, to);
+} /* timeout_del() */
+#endif
+
+
+/*
+ * V E R S I O N I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+TIMEOUT_PUBLIC int timeout_version(void) {
+ return TIMEOUT_VERSION;
+} /* timeout_version() */
+
+
+TIMEOUT_PUBLIC const char *timeout_vendor(void) {
+ return TIMEOUT_VENDOR;
+} /* timeout_version() */
+
+
+TIMEOUT_PUBLIC int timeout_v_rel(void) {
+ return TIMEOUT_V_REL;
+} /* timeout_version() */
+
+
+TIMEOUT_PUBLIC int timeout_v_abi(void) {
+ return TIMEOUT_V_ABI;
+} /* timeout_version() */
+
+
+TIMEOUT_PUBLIC int timeout_v_api(void) {
+ return TIMEOUT_V_API;
+} /* timeout_version() */
+
diff --git a/bindings/rs-timeout/timeout/timeout.h b/bindings/rs-timeout/timeout/timeout.h
new file mode 100644
index 0000000..3ef76e9
--- /dev/null
+++ b/bindings/rs-timeout/timeout/timeout.h
@@ -0,0 +1,253 @@
+/* ==========================================================================
+ * timeout.h - Tickless hierarchical timing wheel.
+ * --------------------------------------------------------------------------
+ * Copyright (c) 2013, 2014 William Ahern
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * ==========================================================================
+ */
+#ifndef TIMEOUT_H
+#define TIMEOUT_H
+
+#include <stdbool.h> /* bool */
+#include <stdio.h> /* FILE */
+
+#include <inttypes.h> /* PRIu64 PRIx64 PRIX64 uint64_t */
+
+#include <sys/queue.h> /* TAILQ(3) */
+
+
+/*
+ * V E R S I O N I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#if !defined TIMEOUT_PUBLIC
+#define TIMEOUT_PUBLIC
+#endif
+
+#define TIMEOUT_VERSION TIMEOUT_V_REL
+#define TIMEOUT_VENDOR "[email protected]"
+
+#define TIMEOUT_V_REL 0x20160226
+#define TIMEOUT_V_ABI 0x20160224
+#define TIMEOUT_V_API 0x20160226
+
+TIMEOUT_PUBLIC int timeout_version(void);
+
+TIMEOUT_PUBLIC const char *timeout_vendor(void);
+
+TIMEOUT_PUBLIC int timeout_v_rel(void);
+
+TIMEOUT_PUBLIC int timeout_v_abi(void);
+
+TIMEOUT_PUBLIC int timeout_v_api(void);
+
+
+/*
+ * I N T E G E R T Y P E I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define TIMEOUT_C(n) UINT64_C(n)
+#define TIMEOUT_PRIu PRIu64
+#define TIMEOUT_PRIx PRIx64
+#define TIMEOUT_PRIX PRIX64
+
+#define TIMEOUT_mHZ TIMEOUT_C(1000)
+#define TIMEOUT_uHZ TIMEOUT_C(1000000)
+#define TIMEOUT_nHZ TIMEOUT_C(1000000000)
+
+typedef uint64_t timeout_t;
+
+#define timeout_error_t int /* for documentation purposes */
+
+
+/*
+ * C A L L B A C K I N T E R F A C E
+ *
+ * Callback function parameters unspecified to make embedding into existing
+ * applications easier.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef TIMEOUT_CB_OVERRIDE
+struct timeout_cb {
+ void (*fn)();
+ void *arg;
+}; /* struct timeout_cb */
+#endif
+
+/*
+ * T I M E O U T I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef TIMEOUT_DISABLE_INTERVALS
+#define TIMEOUT_INT 0x01 /* interval (repeating) timeout */
+#endif
+#define TIMEOUT_ABS 0x02 /* treat timeout values as absolute */
+
+#define TIMEOUT_INITIALIZER(flags) { (flags) }
+
+#define timeout_setcb(to, fn, arg) do { \
+ (to)->callback.fn = (fn); \
+ (to)->callback.arg = (arg); \
+} while (0)
+
+struct timeout {
+ int flags;
+
+ timeout_t expires;
+ /* absolute expiration time */
+
+ struct timeout_list *pending;
+ /* timeout list if pending on wheel or expiry queue */
+
+ TAILQ_ENTRY(timeout) tqe;
+ /* entry member for struct timeout_list lists */
+
+#ifndef TIMEOUT_DISABLE_CALLBACKS
+ struct timeout_cb callback;
+ /* optional callback information */
+#endif
+
+#ifndef TIMEOUT_DISABLE_INTERVALS
+ timeout_t interval;
+ /* timeout interval if periodic */
+#endif
+
+#ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS
+ struct timeouts *timeouts;
+ /* timeouts collection if member of */
+#endif
+}; /* struct timeout */
+
+
+TIMEOUT_PUBLIC struct timeout *timeout_init(struct timeout *, int);
+/* initialize timeout structure (same as TIMEOUT_INITIALIZER) */
+
+#ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS
+TIMEOUT_PUBLIC bool timeout_pending(struct timeout *);
+/* true if on timing wheel, false otherwise */
+
+TIMEOUT_PUBLIC bool timeout_expired(struct timeout *);
+/* true if on expired queue, false otherwise */
+
+TIMEOUT_PUBLIC void timeout_del(struct timeout *);
+/* remove timeout from any timing wheel (okay if not member of any) */
+#endif
+
+/*
+ * T I M I N G W H E E L I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct timeouts;
+
+TIMEOUT_PUBLIC struct timeouts *timeouts_open(timeout_t, timeout_error_t *);
+/* open a new timing wheel, setting optional HZ (for float conversions) */
+
+TIMEOUT_PUBLIC void timeouts_close(struct timeouts *);
+/* destroy timing wheel */
+
+TIMEOUT_PUBLIC timeout_t timeouts_hz(struct timeouts *);
+/* return HZ setting (for float conversions) */
+
+TIMEOUT_PUBLIC void timeouts_update(struct timeouts *, timeout_t);
+/* update timing wheel with current absolute time */
+
+TIMEOUT_PUBLIC void timeouts_step(struct timeouts *, timeout_t);
+/* step timing wheel by relative time */
+
+TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *);
+/* return interval to next required update */
+
+TIMEOUT_PUBLIC void timeouts_add(struct timeouts *, struct timeout *, timeout_t);
+/* add timeout to timing wheel */
+
+TIMEOUT_PUBLIC void timeouts_del(struct timeouts *, struct timeout *);
+/* remove timeout from any timing wheel or expired queue (okay if on neither) */
+
+TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *);
+/* return any expired timeout (caller should loop until NULL-return) */
+
+TIMEOUT_PUBLIC bool timeouts_pending(struct timeouts *);
+/* return true if any timeouts pending on timing wheel */
+
+TIMEOUT_PUBLIC bool timeouts_expired(struct timeouts *);
+/* return true if any timeouts on expired queue */
+
+TIMEOUT_PUBLIC bool timeouts_check(struct timeouts *, FILE *);
+/* return true if invariants hold. describes failures to optional file handle. */
+
+#define TIMEOUTS_PENDING 0x10
+#define TIMEOUTS_EXPIRED 0x20
+#define TIMEOUTS_ALL (TIMEOUTS_PENDING|TIMEOUTS_EXPIRED)
+#define TIMEOUTS_CLEAR 0x40
+
+#define TIMEOUTS_IT_INITIALIZER(flags) { (flags), 0, 0, 0, 0 }
+
+#define TIMEOUTS_IT_INIT(cur, _flags) do { \
+ (cur)->flags = (_flags); \
+ (cur)->pc = 0; \
+} while (0)
+
+struct timeouts_it {
+ int flags;
+ unsigned pc, i, j;
+ struct timeout *to;
+}; /* struct timeouts_it */
+
+TIMEOUT_PUBLIC struct timeout *timeouts_next(struct timeouts *, struct timeouts_it *);
+/* return next timeout in pending wheel or expired queue. caller can delete
+ * the returned timeout, but should not otherwise manipulate the timing
+ * wheel. in particular, caller SHOULD NOT delete any other timeout as that
+ * could invalidate cursor state and trigger a use-after-free.
+ */
+
+#define TIMEOUTS_FOREACH(var, T, flags) \
+ struct timeouts_it _it = TIMEOUTS_IT_INITIALIZER((flags)); \
+ while (((var) = timeouts_next((T), &_it)))
+
+
+/*
+ * B O N U S W H E E L I N T E R F A C E S
+ *
+ * I usually use floating point timeouts in all my code, but it's cleaner to
+ * separate it to keep the core algorithmic code simple.
+ *
+ * Using macros instead of static inline routines where <math.h> routines
+ * might be used to keep -lm linking optional.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include <math.h> /* ceil(3) */
+
+#define timeouts_f2i(T, f) \
+ ((timeout_t)ceil((f) * timeouts_hz((T)))) /* prefer late expiration over early */
+
+#define timeouts_i2f(T, i) \
+ ((double)(i) / timeouts_hz((T)))
+
+#define timeouts_addf(T, to, timeout) \
+ timeouts_add((T), (to), timeouts_f2i((T), (timeout)))
+
+#endif /* TIMEOUT_H */