{"id":22901065,"url":"https://github.com/mpdn/schedwalk","last_synced_at":"2025-07-30T07:40:45.341Z","repository":{"id":49761323,"uuid":"517771468","full_name":"mpdn/schedwalk","owner":"mpdn","description":"Test futures under all possible polling schedules","archived":false,"fork":false,"pushed_at":"2022-07-25T18:15:05.000Z","size":8,"stargazers_count":18,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-08T01:44:59.881Z","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/mpdn.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}},"created_at":"2022-07-25T18:06:05.000Z","updated_at":"2024-03-24T20:43:11.000Z","dependencies_parsed_at":"2022-08-30T08:20:06.262Z","dependency_job_id":null,"html_url":"https://github.com/mpdn/schedwalk","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpdn%2Fschedwalk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpdn%2Fschedwalk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpdn%2Fschedwalk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpdn%2Fschedwalk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mpdn","download_url":"https://codeload.github.com/mpdn/schedwalk/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252983757,"owners_count":21835759,"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":"2024-12-14T01:31:32.025Z","updated_at":"2025-05-08T01:45:02.515Z","avatar_url":"https://github.com/mpdn.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# schedwalk\n\nTest futures under all possible polling schedules.\n\nConcurrent systems are hard. It can be very easy to accidentally assume progress happens in some\nspecific order (i.e. race conditions). For async systems in Rust, that might be an assumption on the\norder that futures are polled - an assumption of the polling schedule.\n\nMost async test runtimes only executes one schedule for your test and will never cover all possible\nschedules. `schedwalk` is a an async test harness that allows you to reliably test all possible\nschedules.\n\n\n## Example\n\nSuppose we are developing a web application and want to compute the average response time. We might\nmodel that with two tasks like this:\n\n```\n# use std::convert::identity as spawn;\n# futures::executor::block_on(async {\nuse futures::{channel::mpsc, join};\n\nlet (sender, mut receiver) = mpsc::unbounded::\u003cu32\u003e();\n\nlet send_task = spawn(async move {\n    sender.unbounded_send(23).unwrap();\n    sender.unbounded_send(20).unwrap();\n    sender.unbounded_send(54).unwrap();\n});\n\nlet avg_task = spawn(async move {\n    let mut sum = 0;\n    let mut count = 0;\n    while let Some(num) = receiver.try_next().unwrap() {\n        sum += num;\n        count += 1;\n    }\n\n    println!(\"average is {}\", sum / count)\n});\n\njoin!(send_task, avg_task);\n# })\n```\n\nBut this has a race condition bug. What if `avg_task` executes before `send_task`? Then `count` will\nbe 0 and we will thus divide by 0! We have implicitly assumed one task executes before the other.\n\nSo how can we have create a test that trigger the above race condition? We could try executing under\nan async runtime like Tokio, but the problem with this is that it does not actually guarantee that\nthe failing schedule will be executed. And in fact, at time of writing, it seems that the single\nthreaded executor *never* triggers the failure. Using the multithreaded executor *may* trigger the\nfailure, but there is no guarantee of that. At best, we have created a flaky test.\n\nIdeally, we want to test such code in a way where we fail deterministically every time in case of\nsuch bugs.\n\nEnter `schedwalk`: a library for testing futures under all possible schedules. Using `schedwalk` we\ncan create a test like this:\n\n```should_panic\nuse schedwalk::{for_all_schedules, spawn};\nuse futures::{channel::mpsc, join};\n\nfor_all_schedules(|| async {\n    let (sender, mut receiver) = mpsc::unbounded::\u003cu32\u003e();\n\n    let send_task = spawn(async move {\n        sender.unbounded_send(23).unwrap();\n        sender.unbounded_send(20).unwrap();\n        sender.unbounded_send(54).unwrap();\n    });\n\n    let avg_task = spawn(async move {\n        let mut sum = 0;\n        let mut count = 0;\n        while let Some(num) = receiver.try_next().unwrap() {\n            sum += num;\n            count += 1;\n        }\n\n        println!(\"average is {}\", sum / count)\n    });\n\n    join!(send_task, avg_task);\n})\n```\n\n`schedwalk` will then execute the future under all possible schedules. In this case there are just\ntwo: one where `send_task` executes first and one where `avg_task` executes first. This will\nreliably trigger the bug in our tests.\n\nTo make debugging easier, panics and deadlocks will print the polling schedule as a string to\nstandard error. Setting the environment variable `SCHEDULE` to this will execute only the exact\nfailing schedule. The above example will print `panic in SCHEDULE=01`. Executing the test again with\n`SCHEDULE=01 cargo test example` will then execute only that exact schedule.\n\n## Caveats\n\nThere are a few important caveats to `schedwalk`:\n- `schedwalk` assumes determinism. Futures must spawn and poll futures in the same order every time.\n  I.e. there can be no thread-local or global state influencing the order futures are polled and no\n  external IO can influence the system.\n- `schedwalk` will exhaustively walk all possible schedules. In cases of high amounts of\n  futures that can be polled concurrently this can quickly become intractable.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmpdn%2Fschedwalk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmpdn%2Fschedwalk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmpdn%2Fschedwalk/lists"}