{"id":25213587,"url":"https://github.com/hexagonal-sun/trale","last_synced_at":"2025-10-25T12:31:53.841Z","repository":{"id":228874384,"uuid":"775155599","full_name":"hexagonal-sun/trale","owner":"hexagonal-sun","description":"Tiny Rust Async Linux Executor","archived":false,"fork":false,"pushed_at":"2025-02-06T12:01:12.000Z","size":156,"stargazers_count":66,"open_issues_count":0,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-06T13:22:02.560Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hexagonal-sun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-20T21:29:59.000Z","updated_at":"2025-02-05T06:47:01.000Z","dependencies_parsed_at":"2024-07-10T16:43:29.914Z","dependency_job_id":"98d0df44-c8fb-43d8-942a-dca2eb508dcb","html_url":"https://github.com/hexagonal-sun/trale","commit_stats":null,"previous_names":["hexagonal-sun/trale"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hexagonal-sun%2Ftrale","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hexagonal-sun%2Ftrale/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hexagonal-sun%2Ftrale/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hexagonal-sun%2Ftrale/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hexagonal-sun","download_url":"https://codeload.github.com/hexagonal-sun/trale/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238024456,"owners_count":19403837,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-10T16:00:58.708Z","updated_at":"2025-10-25T12:31:48.536Z","avatar_url":"https://github.com/hexagonal-sun.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"# `trale`: Tiny Rust Async Linux Executor\n\n[![Crates.io][crates-badge]][crates-url]\n[![MIT licensed][mit-badge]][mit-url]\n[![Build Status][actions-badge]][actions-url]\n\n[crates-badge]: https://img.shields.io/crates/v/trale.svg\n[crates-url]: https://crates.io/crates/trale\n[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg\n[mit-url]: https://github.com/hexagonal-sun/trale/blob/master/LICENSE\n[actions-badge]: https://github.com/hexagonal-sun/trale/workflows/Rust/badge.svg\n[actions-url]: https://github.com/hexagonal-sun/trale/actions?query=branch%3Amaster\n\nThis project implements a minimalistic asynchronous Rust executor, written in as\nfew lines as possible. Its primary goal is to serve as an educational resource\nfor those studying Rust's async ecosystem. It provides a *real* executor capable\nof running multiple async tasks on a single thread, showcasing a simple yet\nfunctional concrete implementation.\n\nTo achieve this, `trale` tightly integrates with Linux's `io_uring` interface,\nopting for minimal abstractions to prioritise performance. While it sacrifices\nsome abstraction in favour of efficiency, **correctness** is not compromised.\n\n## Supported Features\n\n- **`io_uring`-based reactor**: State-of-the-art reactor utilising Linux's\n  latest I/O userspace\n  interface,[`io_uring`](https://man7.org/linux/man-pages/man7/io_uring.7.html).\n- **Single-threaded executor**: Polls tasks on a runqueue and moves them to an\n  idle queue when waiting for wakeups from the reactor.\n- **Timer using `TimerFd`**: Leverages Linux's\n  [`timerfd_create`](https://linux.die.net/man/2/timerfd_create).\n- **UDP sockets**: Non-blocking `std::net::UdpSocket` support.\n- **TCP sockets**: Basic TCP socket support.\n- **Inter-task events**: Uses [`EventFd`](https://linux.die.net/man/2/eventfd)\n  for inter-task communication.\n- **Task synchronization**: Implements synchronization via a `Mutex` type,\n  backed by `EventFd` as the primitive.\n\nExample Usage\n-----\n\nTo see various examples, see the `examples/` directory in the root of\nthe project.  As a starting point:\n\n```rust\nuse std::time::Duration;\n\nuse trale::{futures::timer::Timer, task::Executor};\n\nfn main() {\n    env_logger::init();\n\n    Executor::spawn(async {\n        Timer::sleep(Duration::from_secs(2)).unwrap().await;\n        println!(\"Hello A!\");\n        Timer::sleep(Duration::from_secs(1)).unwrap().await;\n        println!(\"Hello B!\");\n        Timer::sleep(Duration::from_secs(1)).unwrap().await;\n        println!(\"Hello C!\");\n    });\n\n    Executor::spawn(async {\n        Timer::sleep(Duration::from_secs(2)).unwrap().await;\n        println!(\"Hello a!\");\n        Timer::sleep(Duration::from_secs(1)).unwrap().await;\n        println!(\"Hello b!\");\n        Timer::sleep(Duration::from_secs(1)).unwrap().await;\n        println!(\"Hello c!\");\n    });\n\n    Executor::run();\n}\n\n```\n\n- `timer`: This example spawns two tasks which, both racing to print\n  messages to the terminal.\n- `udp`: This example transfers twenty bytes between two tasks usng\n  UDP sockets on the localhost interface whilst a third task is\n  printing messages to the terminal.\n- `tcp`: This is an implementation of a TCP echo client (connecting to\n  `127.0.0.1:5000`) whilst another task prints out messages to the\n  terminal.\n- `tcp_serv`: This is an implementation of a TCP echo server (waiting for\n  connections to `127.0.0.1:5000`) whilst another task prints out messages to\n  the terminal.\n- `sub_tasks`: This demonstates how a subtask can be spawned from a \n   parent task and the joinhandle can be `.await`ed on without blocking\n   the runqueue.\n\n## Implementation\n\n### Tasks \u0026 Executor\n\nEach `Task` represents an asynchronous computation that needs to be executed. It\ncontains a top-level pinned and boxed future, referred to as the `future`, which\nis typically an `async` block passed to the `spawn()` function. This function\ncreates a `Task` object and places it on the executor's per-thread run queue.\n\nIt is the responsibility of the `Executor` to call `poll()` on a task's\ntop-level future to advance its execution. Since futures are state machines,\neach call to `poll()` modifies the future’s internal state on the heap. If\n`poll()` returns `Poll::Pending`, the future will 'resume' execution from the\nsame point the next time `poll()` is called. This process is recursive: if a\nfuture `await`s other futures, `poll()` will be called on those sub-futures as\nwell. The recursion continues until a \"terminal\" future is reached, which\ntypically interacts with the reactor to schedule a wakeup when execution can\nproceed.\n\nThe `Executor` is responsible for pushing tasks to completion. It consists of a\nrun queue and a wait queue. When synchronous code calls either `Executor::run`\nor `Executor::spawn_blocking`, the `Executor::executor_loop` function is invoked\non the same thread, orchestrating the execution of futures.\n\nThe execution loop follows these steps:\n\n1. **Check for Tasks to Process**: The loop first checks if both the run queue\n   and the wait queue are empty. If both are empty, the loop exits, as there are\n   no more tasks to process.\n2. **Remove a Task**: If there are tasks to process, a task is removed from the\n   run queue.\n   - If the run queue is empty, `io_uring_enter` is invoked, via the reactor, to\n     block the thread until an I/O event completes.\n   - When `io_uring_enter` returns, the corresponding all I/O events that have\n     completed have their corresponding `Waker`s called which places the task on\n     the runqueue.\n3. **Poll the Task**: The `Executor` calls `poll()` on the task's future to make\n   progress.\n   - If `poll()` returns `Poll::Ready`, the task is discarded as it has\n     completed.\n   - If `poll()` returns `Poll::Pending`, the task is placed in the wait queue\n     for later processing.\n\n### Reactor\n\nThe Reactor is responsible for invoking `Task::wake()` whenever a task can make\nprogress. This typically occurs when some I/O operation completes. Each future\nthat requires I/O interaction needs to obtain a handle to the reactor's I/O\ninterface via `Reactor::new_io()`. This handle, represented by `ReactorIo`, is\nused for submitting I/O operations and retrieving their results. The key\nfunction for interacting with the reactor is `submit_or_get_result()`. This\nfunction is designed to be called within the `poll()` method of a future,\nproviding a bridge between the future and the reactor.\n\nThe `submit_or_get_result()` function takes a closure that is responsible for\ncreating an I/O operation, which is encapsulated in an `Entry`, and associates\nit with a `Waker` to notify the task when the operation has completed. The\n`Entry` describes the type of I/O operation, such as a read or write, and\ncontains the necessary arguments to be passed to the kernel. The `Waker` is used\nto wake the task once the I/O operation is ready to be processed.\n\nOne of the most important characteristics of this system is that the closure\nprovided to `submit_or_get_result()` is invoked only once to submit the I/O\nrequest to the kernel. This design isn't just a performance optimisation; it\nalso addresses soundness concerns. Since buffers and other resources are shared\nbetween the user space and the kernel, submitting the same I/O operation\nmultiple times could lead to serious issues. For instance, if a future were\npolled more than once and the I/O request were re-submitted, the original\nsubmission might contain references to deallocated memory, invalid file\ndescriptors, or other corrupted state. By ensuring that the closure is only\ncalled once, we avoid these potential pitfalls. On the first call, the function\nreturns `Poll::Pending`, placing the task in a pending state until the operation\ncompletes. If the task is polled again before the I/O has finished, it simply\nreturns `Poll::Pending` without invoking the closure, as the reactor already\nknows about the pending I/O operation. Once the I/O completes,\n`submit_or_get_result()` returns `Poll::Ready` with the result of the I/O\noperation, encapsulated in a `std::io::Result\u003ci32\u003e`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhexagonal-sun%2Ftrale","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhexagonal-sun%2Ftrale","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhexagonal-sun%2Ftrale/lists"}