{"id":16376640,"url":"https://github.com/udoprog/unicycle","last_synced_at":"2025-04-06T06:12:55.121Z","repository":{"id":41817213,"uuid":"235856047","full_name":"udoprog/unicycle","owner":"udoprog","description":"A futures abstraction that runs a set of futures which may complete in any order.","archived":false,"fork":false,"pushed_at":"2024-08-03T20:41:53.000Z","size":205,"stargazers_count":89,"open_issues_count":4,"forks_count":7,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-29T07:48:10.491Z","etag":null,"topics":["async","futures","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/udoprog.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","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":"2020-01-23T18:11:27.000Z","updated_at":"2024-08-15T01:10:26.000Z","dependencies_parsed_at":"2023-12-16T15:47:01.645Z","dependency_job_id":"5717f35a-942b-485c-b0f1-dda82ff1859d","html_url":"https://github.com/udoprog/unicycle","commit_stats":{"total_commits":141,"total_committers":5,"mean_commits":28.2,"dds":0.09219858156028371,"last_synced_commit":"0d4b8604bd42aca9b2f6a33629f414ce60a4c64b"},"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udoprog%2Funicycle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udoprog%2Funicycle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udoprog%2Funicycle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udoprog%2Funicycle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/udoprog","download_url":"https://codeload.github.com/udoprog/unicycle/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247441060,"owners_count":20939239,"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":["async","futures","rust"],"created_at":"2024-10-11T03:25:32.156Z","updated_at":"2025-04-06T06:12:55.084Z","avatar_url":"https://github.com/udoprog.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# unicycle\n\n[\u003cimg alt=\"github\" src=\"https://img.shields.io/badge/github-udoprog/unicycle-8da0cb?style=for-the-badge\u0026logo=github\" height=\"20\"\u003e](https://github.com/udoprog/unicycle)\n[\u003cimg alt=\"crates.io\" src=\"https://img.shields.io/crates/v/unicycle.svg?style=for-the-badge\u0026color=fc8d62\u0026logo=rust\" height=\"20\"\u003e](https://crates.io/crates/unicycle)\n[\u003cimg alt=\"docs.rs\" src=\"https://img.shields.io/badge/docs.rs-unicycle-66c2a5?style=for-the-badge\u0026logoColor=white\u0026logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K\" height=\"20\"\u003e](https://docs.rs/unicycle)\n[\u003cimg alt=\"build status\" src=\"https://img.shields.io/github/actions/workflow/status/udoprog/unicycle/ci.yml?branch=main\u0026style=for-the-badge\" height=\"20\"\u003e](https://github.com/udoprog/unicycle/actions?query=branch%3Amain)\n\nA scheduler for driving a large number of futures.\n\nUnicycle provides a collection of [Unordered] types:\n\n* [FuturesUnordered]\n* [StreamsUnordered]\n* [IndexedStreamsUnordered]\n\nThese are async abstractions that runs a set of futures or streams which may\ncomplete in any order.\nSimilarly to [FuturesUnordered][futures-rs] from the [futures crate].\nBut we aim to provide a stronger guarantee of fairness (see below), and\nbetter memory locality for the futures being pollled.\n\n**Note:** This project is experimental. It involves some amount of unsafe and\npossibly bad assumptions which needs to be either vetted or removed before you\nshould consider putting it in production.\n\n\u003cbr\u003e\n\n## Features\n\n* `parking-lot` - To enable locking using the [parking_lot] crate (default).\n* `futures-rs` - Enable the used of the Stream type from [futures-rs].\n  This is required to get access to [StreamsUnordered] and\n  [IndexedStreamsUnordered] since these wrap over [futures-rs] types. (default)\n\n\u003cbr\u003e\n\n## Examples\n\n```rust\nuse std::time::Duration;\n\nuse tokio::time;\nuse unicycle::FuturesUnordered;\n\nlet mut futures = FuturesUnordered::new();\n\nfutures.push(time::sleep(Duration::from_secs(2)));\nfutures.push(time::sleep(Duration::from_secs(3)));\nfutures.push(time::sleep(Duration::from_secs(1)));\n\nwhile let Some(_) = futures.next().await {\n    println!(\"tick\");\n}\n\nprintln!(\"done!\");\n```\n\n[Unordered] types can be created from iterators:\n\n```rust\nuse std::time::Duration;\n\nuse tokio::time;\nuse unicycle::FuturesUnordered;\n\nlet mut futures = Vec::new();\n\nfutures.push(time::sleep(Duration::from_secs(2)));\nfutures.push(time::sleep(Duration::from_secs(3)));\nfutures.push(time::sleep(Duration::from_secs(1)));\n\nlet mut futures = futures.into_iter().collect::\u003cFuturesUnordered\u003c_\u003e\u003e();\n\nwhile let Some(_) = futures.next().await {\n    println!(\"tick\");\n}\n\nprintln!(\"done!\");\n```\n\n\u003cbr\u003e\n\n## Fairness\n\nYou can think of abstractions like Unicycle as schedulers. They are provided\na set of child tasks, and try to do their best to drive them to completion.\nIn this regard, it's interesting to talk about _fairness_ in how the tasks\nare being driven.\n\nThe current implementation of [FuturesUnordered][futures-rs] maintains a\nqueue of tasks interested in waking up. As a task is woken up it's added to\nthe head of this queue to signal its interest in being polled. When\n[FuturesUnordered][futures-rs] works it drains this queue in a loop and\npolls the associated task. This process has a side effect where tasks who\naggressively signal interest in waking up will receive priority and be\npolled more frequently. Since there is a higher chance that while the queue\nis being drained, their interest will be re-added at the head of the queue\nimmeidately. This can lead to instances where a small number of tasks can\ncan cause the polling loop of [FuturesUnordered][futures-rs] to [spin\nabnormally]. This issue was [reported by Jon Gjengset] and is improved on by\n[limiting the amount FuturesUnordered is allowed to spin].\n\nUnicycle addresses this by limiting how frequently a child task may be\npolled per _polling cycle_. This is done by tracking polling interest in two\nseparate sets. Once we are polled, we swap out the active set then take the\nswapped out set and use as a basis for what to poll in order while limiting\nourselves to only poll _once_ per child task. Additional wakeups are only\nregistered in the swapped in set which will be polled the next cycle.\n\nThis way we hope to achieve a higher degree of fairness, never favoring the\nbehavior of one particular task.\n\n\u003cbr\u003e\n\n## Architecture\n\nThe [Unordered] type stores all futures being polled in a continuous storage\n[slab] where each future is stored in a separate allocation. The header of\nthis storage is atomically reference counted and can be used to construct a\nwaker without additional allocation.\n\nNext to the slab we maintain two [BitSets][BitSet], one _active_ and one\n_alternate_. When a task registers interest in waking up, the bit associated\nwith its index is set in the active set, and the latest waker passed into\n[Unordered] is called to wake it up. Once [Unordered] is polled, it\natomically swaps the active and alternate [BitSets][BitSet], waits until it\nhas exclusive access to the now _alternate_ [BitSet], and drains it from all\nthe indexes which have been flagged to determine which tasks to poll. Each\ntask is then polled _once_ in order. If the task is [Ready], its result is\nyielded. After we receive control again, we continue draining the alternate\nset in this manner, until it is empty. When this is done we yield once, then\nwe start the cycle over again.\n\n[BitSet]: https://docs.rs/uniset/latest/uniset/struct.BitSet.html\n[futures crate]: https://docs.rs/futures/latest/futures\n[futures-rs]: https://crates.io/crates/futures\n[futures-rs]: https://docs.rs/futures/latest/futures/stream/struct.FuturesUnordered.html\n[FuturesUnordered]: https://docs.rs/unicycle/latest/unicycle/type.FuturesUnordered.html\n[IndexedStreamsUnordered]: https://docs.rs/unicycle/latest/unicycle/type.IndexedStreamsUnordered.html\n[limiting the amount FuturesUnordered is allowed to spin]: https://github.com/rust-lang/futures-rs/pull/2049\n[parking_lot]: https://crates.io/crates/parking_lot\n[pin API]: https://doc.rust-lang.org/std/pin/index.html\n[Ready]: https://doc.rust-lang.org/std/task/enum.Poll.html\n[reported by Jon Gjengset]: https://github.com/rust-lang/futures-rs/issues/2047\n[Slab]: https://docs.rs/slab/latest/slab/struct.Slab.html\n[slab]: https://github.com/carllerche/slab\n[spin abnormally]: https://github.com/udoprog/unicycle/blob/main/tests/spinning_futures_unordered_test.rs\n[StreamsUnordered]: https://docs.rs/unicycle/latest/unicycle/type.StreamsUnordered.html\n[Unordered]: https://docs.rs/unicycle/latest/unicycle/struct.Unordered.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fudoprog%2Funicycle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fudoprog%2Funicycle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fudoprog%2Funicycle/lists"}