diff --git a/.github/ISSUE_TEMPLATE/-shiny-vision--story-issue.md b/.github/ISSUE_TEMPLATE/-shiny-vision--story-issue.md new file mode 100644 index 00000000..ad5fbbbe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/-shiny-vision--story-issue.md @@ -0,0 +1,31 @@ +--- +name: '"Shiny future" story issue' +about: Propose a "shiny future" story idea or look for someone to write it +title: '' +labels: good first issue, help wanted, shiny-vision-story-ideas +assignees: '' + +--- + +### Brief summary + +*Give a brief summary of the story or vision here* + +### Optional details + +* (Optional) Which [character(s)] would be the best fit and why? + * [ ] [Alan]: the experienced "GC'd language" developer, new to Rust + * [ ] [Grace]: the systems programming expert, new to Rust + * [ ] [Niklaus]: new programmer from an unconventional background + * [ ] [Barbara]: the experienced Rust developer +* (Optional) Which [project(s)] would be the best fit and why? + * *List some projects here.* +* (Optional) What are the key points or morals to emphasize? + * *Write some morals here.* + +[character(s)]: https://rust-lang.github.io/wg-async/vision/characters.html +[project(s)]: https://rust-lang.github.io/wg-async/vision/projects.html +[Alan]: https://rust-lang.github.io/wg-async/vision/characters/alan.html +[Grace]: https://rust-lang.github.io/wg-async/vision/characters/grace.html +[Niklaus]: https://rust-lang.github.io/wg-async/vision/characters/niklaus.html +[Barbara]: https://rust-lang.github.io/wg-async/vision/characters/barbara.html diff --git a/.github/ISSUE_TEMPLATE/-status-quo--story-issue.md b/.github/ISSUE_TEMPLATE/-status-quo--story-issue.md index bd1d6dd4..009bf7b3 100644 --- a/.github/ISSUE_TEMPLATE/-status-quo--story-issue.md +++ b/.github/ISSUE_TEMPLATE/-status-quo--story-issue.md @@ -23,9 +23,9 @@ assignees: '' * (Optional) What are the key points or morals to emphasize? * *Write some morals here.* -[character(s)]: https://rust-lang.github.io/wg-async-foundations/vision/characters.html -[project(s)]: https://rust-lang.github.io/wg-async-foundations/vision/projects.html -[Alan]: https://rust-lang.github.io/wg-async-foundations/vision/characters/alan.html -[Grace]: https://rust-lang.github.io/wg-async-foundations/vision/characters/grace.html -[Niklaus]: https://rust-lang.github.io/wg-async-foundations/vision/characters/niklaus.html -[Barbara]: https://rust-lang.github.io/wg-async-foundations/vision/characters/barbara.html +[character(s)]: https://rust-lang.github.io/wg-async/vision/characters.html +[project(s)]: https://rust-lang.github.io/wg-async/vision/projects.html +[Alan]: https://rust-lang.github.io/wg-async/vision/characters/alan.html +[Grace]: https://rust-lang.github.io/wg-async/vision/characters/grace.html +[Niklaus]: https://rust-lang.github.io/wg-async/vision/characters/niklaus.html +[Barbara]: https://rust-lang.github.io/wg-async/vision/characters/barbara.html diff --git a/.github/ISSUE_TEMPLATE/meeting-proposal.md b/.github/ISSUE_TEMPLATE/meeting-proposal.md new file mode 100644 index 00000000..a3841e2c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/meeting-proposal.md @@ -0,0 +1,21 @@ +--- +name: Meeting proposal +about: Propose a topic for a meeting. +title: "(My meeting proposal)" +labels: meeting-proposal, WG-async +assignees: "" +--- + +# Summary + +*Describe your meeting topic here. It doesn't have to be long, but it's always good to try and identify a concrete question to be answered or narrow topic for discussion.* + +# Background reading + +*Include any links to material that people might read before-hand.* + +# About this issue + +This issue is a proposal for a WG-async [meeting][]. + +[meeting]: https://rust-lang.github.io/wg-async/meetings.html diff --git a/.github/ISSUE_TEMPLATE/new-roadmap-goal.md b/.github/ISSUE_TEMPLATE/new-roadmap-goal.md new file mode 100644 index 00000000..dfbf2f92 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new-roadmap-goal.md @@ -0,0 +1,34 @@ +--- +name: New roadmap goal +about: Checklist for owners setting up a new top-level goal on the roadmap +title: "Roadmap goal: [...]" +labels: roadmap +assignees: '' +--- + +This issue tracks the steps for setting up a new top-level goal in the async vision [roadmap]. +Please read the [instructions for owning a goal][owning]. + + + +Goal owner: + +- [ ] Goal has a landing page listing the impact +- [ ] Initiatives are defined +- [ ] All initiatives have a landing page + + - [ ] +- [ ] Goal and all initiatives listed on the [roadmap] with links to landing pages + +Remember, you don't have to do these all at once! + +This issue can be closed when all of the above items are complete. + +[roadmap]: https://rust-lang.github.io/wg-async/vision/roadmap.html +[owning]: https://rust-lang.github.io/wg-async/vision/how_to_vision/owners.html diff --git a/.github/ISSUE_TEMPLATE/new-roadmap-initiative.md b/.github/ISSUE_TEMPLATE/new-roadmap-initiative.md new file mode 100644 index 00000000..79e39197 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new-roadmap-initiative.md @@ -0,0 +1,44 @@ +--- +name: New roadmap initiative +about: Checklist for owners setting up a new initiative on the roadmap +title: "Roadmap initiative: [...]" +labels: roadmap +assignees: '' +--- + + + +This issue tracks the steps for setting up a new initiative in the async vision [roadmap]. +Please read the [instructions for owning an initiative][owning]. + + + +Top-level goal: +Initiative owner: + +Each initiative should have a landing page, linked to from the [roadmap]. This can be a page on this website or a dedicated repo. + +For in-progress initiatives the landing page should include, or have pointers to: + +- [ ] Goals and impact of the initiative +- [ ] Milestones +- [ ] Design notes and documentation (this may be sparse in the beginning) +- [ ] Links to any organizing tools, such as a project board +- [ ] The initiative owner +- [ ] The current set of [stakeholders] and the area(s) they represent +- [ ] Notes on how to get involved +- [ ] For landing pages not on this website, a link back to the overall [roadmap] + +For making a dedicated repo, it's recommended to use this [initiative template][template] as a starting point. + +Remember, you don't have to do these all at once! + +This issue can be closed when all of the above items are complete. + +[roadmap]: https://rust-lang.github.io/wg-async/vision/roadmap.html +[owning]: https://rust-lang.github.io/wg-async/vision/how_to_vision/owners.html +[stakeholders]: https://rust-lang.github.io/wg-async/vision/how_to_vision/stakeholders.html +[template]: https://github.com/rust-lang/initiative-template diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9e5a718..66059a89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,15 +11,38 @@ jobs: name: build and test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install mdbook run: | tag=$(curl -LsSf https://api.github.com/repos/rust-lang/mdBook/releases/latest | jq -r '.tag_name') curl -LsSf https://github.com/rust-lang/mdBook/releases/download/$tag/mdbook-$tag-x86_64-unknown-linux-gnu.tar.gz | tar xzf - + tag=$(curl -LsSf https://api.github.com/repos/badboy/mdbook-mermaid/releases/latest | jq -r '.tag_name') + curl -LsSf https://github.com/badboy/mdbook-mermaid/releases/download/$tag/mdbook-mermaid-$tag-x86_64-unknown-linux-gnu.tar.gz | tar xzf - echo $(pwd) >> $GITHUB_PATH - run: mdbook build - - uses: rust-lang/simpleinfra/github-actions/static-websites@master + - name: Configure git + run: | + git config --global http.postBuffer 50000000 + git config --global https.postBuffer 50000000 + - name: Upload book artifacts + uses: actions/upload-pages-artifact@v3 with: - deploy_dir: book - github_token: ${{ secrets.GITHUB_TOKEN }} + path: book if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository_owner == 'rust-lang' + + # Deploy is run as a separate job as it needs elevated permissions + deploy: + name: deploy + runs-on: ubuntu-latest + needs: test # the `test` job uploads the pages artifact + if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository_owner == 'rust-lang' + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{steps.deployment.outputs.page_url}} + steps: + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 + id: deployment diff --git a/.gitignore b/.gitignore index 7585238e..78f3c965 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ +*.un~ book +target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..8639f8da --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,56 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "camino" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b" + +[[package]] +name = "fixlinks" +version = "0.1.0" +dependencies = [ + "camino", + "pathdiff", + "regex", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "pathdiff" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877630b3de15c0b64cc52f659345724fbf6bdad9bd9566699fc53688f3c34a34" + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..98e687a6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,4 @@ +[workspace] +members = [ + "tools/fixlinks", +] diff --git a/README.md b/README.md index 70063603..4e0e7c56 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# wg-async-foundations +# wg-async Working group dedicated to improving the foundations of async I/O in Rust Please visit our [rendered page] for more information! -[rendered page]: https://rust-lang.github.io/wg-async-foundations/ +[rendered page]: https://rust-lang.github.io/wg-async/ diff --git a/book.toml b/book.toml index 104200b2..5186a831 100644 --- a/book.toml +++ b/book.toml @@ -3,15 +3,100 @@ authors = ["Niko Matsakis"] language = "en" multilingual = false src = "src" -title = "wg-async-foundations" +title = "wg-async" [rust] edition = "2018" [output.html] -site-url = "/wg-async-foundations/" -git-repository-url = "https://github.com/rust-lang/wg-async-foundations" +site-url = "/wg-async/" +git-repository-url = "https://github.com/rust-lang/wg-async" +additional-js =["mermaid.min.js", "mermaid-init.js"] +edit-url-template = "https://github.com/rust-lang/wg-async/edit/master/{path}" [output.html.fold] enable = true level = 0 +[preprocessor] +[preprocessor.mermaid] +command = "mdbook-mermaid" + +[preprocessor.graphviz] +command = "mdbook-graphviz" + +[output.html.redirect] +"/vision/shiny_future/alan_learns_async_on_his_own.html" = "/vision/submitted_stories/shiny_future/alan_learns_async_on_his_own.html" +"/vision/shiny_future/alan_switches_runtimes.html" = "/vision/submitted_stories/shiny_future/alan_switches_runtimes.html" +"/vision/shiny_future/alans_trust_in_the_compiler_is_rewarded.html" = "/vision/submitted_stories/shiny_future/alans_trust_in_the_compiler_is_rewarded.html" +"/vision/shiny_future/barbara_appreciates_performance_tools.html" = "/vision/submitted_stories/shiny_future/barbara_appreciates_performance_tools.html" +"/vision/shiny_future/barbara_enjoys_an_async_sandwich.html" = "/vision/submitted_stories/shiny_future/barbara_enjoys_an_async_sandwich.html" +"/vision/shiny_future/barbara_makes_a_wish.html" = "/vision/submitted_stories/shiny_future/barbara_makes_a_wish.html" +"/vision/shiny_future/barbara_wants_async_rw.html" = "/vision/submitted_stories/shiny_future/barbara_wants_async_rw.html" +"/vision/shiny_future/barbara_wants_async_tracing.html" = "/vision/submitted_stories/shiny_future/barbara_wants_async_tracing.html" +"/vision/shiny_future/template.html" = "/vision/submitted_stories/shiny_future/template.html" +"/vision/status_quo/alan_builds_a_cache.html" = "/vision/submitted_stories/status_quo/alan_builds_a_cache.html" +"/vision/status_quo/alan_builds_a_task_scheduler.html" = "/vision/submitted_stories/status_quo/alan_builds_a_task_scheduler.html" +"/vision/status_quo/alan_creates_a_hanging_alarm.html" = "/vision/submitted_stories/status_quo/alan_creates_a_hanging_alarm.html" +"/vision/status_quo/alan_finds_database_drops_hard.html" = "/vision/submitted_stories/status_quo/alan_finds_database_drops_hard.html" +"/vision/status_quo/alan_has_an_event_loop.html" = "/vision/submitted_stories/status_quo/alan_has_an_event_loop.html" +"/vision/status_quo/alan_hates_writing_a_stream.html" = "/vision/submitted_stories/status_quo/alan_hates_writing_a_stream.html" +"/vision/status_quo/alan_iteratively_regresses.html" = "/vision/submitted_stories/status_quo/alan_iteratively_regresses.html" +"/vision/status_quo/alan_lost_the_world.html" = "/vision/submitted_stories/status_quo/alan_lost_the_world.html" +"/vision/status_quo/alan_misses_c_sharp_async.html" = "/vision/submitted_stories/status_quo/alan_misses_c_sharp_async.html" +"/vision/status_quo/alan_needs_async_in_traits.html" = "/vision/submitted_stories/status_quo/alan_needs_async_in_traits.html" +"/vision/status_quo/alan_picks_web_server.html" = "/vision/submitted_stories/status_quo/alan_picks_web_server.html" +"/vision/status_quo/alan_runs_into_stack_trouble.html" = "/vision/submitted_stories/status_quo/alan_runs_into_stack_trouble.html" +"/vision/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.html" = "/vision/submitted_stories/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.html" +"/vision/status_quo/alan_thinks_he_needs_async_locks.html" = "/vision/submitted_stories/status_quo/alan_thinks_he_needs_async_locks.html" +"/vision/status_quo/alan_tries_a_socket_sink.html" = "/vision/submitted_stories/status_quo/alan_tries_a_socket_sink.html" +"/vision/status_quo/alan_tries_processing_some_files.html" = "/vision/submitted_stories/status_quo/alan_tries_processing_some_files.html" +"/vision/status_quo/alan_tries_to_debug_a_hang.html" = "/vision/submitted_stories/status_quo/alan_tries_to_debug_a_hang.html" +"/vision/status_quo/alan_wants_prefetch_iterator.html" = "/vision/submitted_stories/status_quo/alan_wants_prefetch_iterator.html" +"/vision/status_quo/alan_writes_a_web_framework.html" = "/vision/submitted_stories/status_quo/alan_writes_a_web_framework.html" +"/vision/status_quo/aws_engineer.html" = "/vision/submitted_stories/status_quo/aws_engineer.html" +"/vision/status_quo/aws_engineer/borrow_check_errors.html" = "/vision/submitted_stories/status_quo/aws_engineer/borrow_check_errors.html" +"/vision/status_quo/aws_engineer/coming_from_java.html" = "/vision/submitted_stories/status_quo/aws_engineer/coming_from_java.html" +"/vision/status_quo/aws_engineer/debugging_performance_problems.html" = "/vision/submitted_stories/status_quo/aws_engineer/debugging_performance_problems.html" +"/vision/status_quo/aws_engineer/ecosystem.html" = "/vision/submitted_stories/status_quo/aws_engineer/ecosystem.html" +"/vision/status_quo/aws_engineer/encountering_pin.html" = "/vision/submitted_stories/status_quo/aws_engineer/encountering_pin.html" +"/vision/status_quo/aws_engineer/failure_to_parallelize.html" = "/vision/submitted_stories/status_quo/aws_engineer/failure_to_parallelize.html" +"/vision/status_quo/aws_engineer/figuring_out_the_best_option.html" = "/vision/submitted_stories/status_quo/aws_engineer/figuring_out_the_best_option.html" +"/vision/status_quo/aws_engineer/getting_ready_to_deploy.html" = "/vision/submitted_stories/status_quo/aws_engineer/getting_ready_to_deploy.html" +"/vision/status_quo/aws_engineer/getting_started_with_rust.html" = "/vision/submitted_stories/status_quo/aws_engineer/getting_started_with_rust.html" +"/vision/status_quo/aws_engineer/juggling_error_handling.html" = "/vision/submitted_stories/status_quo/aws_engineer/juggling_error_handling.html" +"/vision/status_quo/aws_engineer/missed_waker_leads_to_lost_performance.html" = "/vision/submitted_stories/status_quo/aws_engineer/missed_waker_leads_to_lost_performance.html" +"/vision/status_quo/aws_engineer/solving_a_deadlock.html" = "/vision/submitted_stories/status_quo/aws_engineer/solving_a_deadlock.html" +"/vision/status_quo/aws_engineer/testing_the_service.html" = "/vision/submitted_stories/status_quo/aws_engineer/testing_the_service.html" +"/vision/status_quo/aws_engineer/using_jni.html" = "/vision/submitted_stories/status_quo/aws_engineer/using_jni.html" +"/vision/status_quo/aws_engineer/using_the_debugger.html" = "/vision/submitted_stories/status_quo/aws_engineer/using_the_debugger.html" +"/vision/status_quo/aws_engineer/writing_a_java_based_service.html" = "/vision/submitted_stories/status_quo/aws_engineer/writing_a_java_based_service.html" +"/vision/status_quo/barbara_anguishes_over_http.html" = "/vision/submitted_stories/status_quo/barbara_anguishes_over_http.html" +"/vision/status_quo/barbara_battles_buffered_streams.html" = "/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.html" +"/vision/status_quo/barbara_benchmarks_async_trait.html" = "/vision/submitted_stories/status_quo/barbara_benchmarks_async_trait.html" +"/vision/status_quo/barbara_bridges_sync_and_async.html" = "/vision/submitted_stories/status_quo/barbara_bridges_sync_and_async.html" +"/vision/status_quo/barbara_builds_an_async_executor.html" = "/vision/submitted_stories/status_quo/barbara_builds_an_async_executor.html" +"/vision/status_quo/barbara_carefully_dismisses_embedded_future.html" = "/vision/submitted_stories/status_quo/barbara_carefully_dismisses_embedded_future.html" +"/vision/status_quo/barbara_compares_some_cpp_code.html" = "/vision/submitted_stories/status_quo/barbara_compares_some_cpp_code.html" +"/vision/status_quo/barbara_gets_burned_by_select.html" = "/vision/submitted_stories/status_quo/barbara_gets_burned_by_select.html" +"/vision/status_quo/barbara_makes_their_first_steps_into_async.html" = "/vision/submitted_stories/status_quo/barbara_makes_their_first_steps_into_async.html" +"/vision/status_quo/barbara_needs_async_helpers.html" = "/vision/submitted_stories/status_quo/barbara_needs_async_helpers.html" +"/vision/status_quo/barbara_plays_with_async.html" = "/vision/submitted_stories/status_quo/barbara_plays_with_async.html" +"/vision/status_quo/barbara_polls_a_mutex.html" = "/vision/submitted_stories/status_quo/barbara_polls_a_mutex.html" +"/vision/status_quo/barbara_tries_async_streams.html" = "/vision/submitted_stories/status_quo/barbara_tries_async_streams.html" +"/vision/status_quo/barbara_tries_unix_socket.html" = "/vision/submitted_stories/status_quo/barbara_tries_unix_socket.html" +"/vision/status_quo/barbara_trims_a_stacktrace.html" = "/vision/submitted_stories/status_quo/barbara_trims_a_stacktrace.html" +"/vision/status_quo/barbara_wants_async_insights.html" = "/vision/submitted_stories/status_quo/barbara_wants_async_insights.html" +"/vision/status_quo/barbara_wants_single_threaded_optimizations.html" = "/vision/submitted_stories/status_quo/barbara_wants_single_threaded_optimizations.html" +"/vision/status_quo/barbara_wants_to_use_ghostcell.html" = "/vision/submitted_stories/status_quo/barbara_wants_to_use_ghostcell.html" +"/vision/status_quo/barbara_wishes_for_easy_runtime_switch.html" = "/vision/submitted_stories/status_quo/barbara_wishes_for_easy_runtime_switch.html" +"/vision/status_quo/barbara_writes_a_runtime_agnostic_lib.html" = "/vision/submitted_stories/status_quo/barbara_writes_a_runtime_agnostic_lib.html" +"/vision/status_quo/grace_deploys_her_service.html" = "/vision/submitted_stories/status_quo/grace_deploys_her_service.html" +"/vision/status_quo/grace_tries_new_libraries.html" = "/vision/submitted_stories/status_quo/grace_tries_new_libraries.html" +"/vision/status_quo/grace_waits_for_gdb_next.html" = "/vision/submitted_stories/status_quo/grace_waits_for_gdb_next.html" +"/vision/status_quo/grace_wants_a_zero_copy_api.html" = "/vision/submitted_stories/status_quo/grace_wants_a_zero_copy_api.html" +"/vision/status_quo/grace_wants_to_integrate_c_api.html" = "/vision/submitted_stories/status_quo/grace_wants_to_integrate_c_api.html" +"/vision/status_quo/grace_writes_a_new_fb_service.html" = "/vision/submitted_stories/status_quo/grace_writes_a_new_fb_service.html" +"/vision/status_quo/niklaus_simulates_hydrodynamics.html" = "/vision/submitted_stories/status_quo/niklaus_simulates_hydrodynamics.html" +"/vision/status_quo/niklaus_wants_to_share_knowledge.html" = "/vision/submitted_stories/status_quo/niklaus_wants_to_share_knowledge.html" +"/vision/status_quo/template.html" = "/vision/submitted_stories/status_quo/template.html" +"/vision/tenets.html" = "/vision/how_it_feels.html" diff --git a/get_contributors.sh b/get_contributors.sh new file mode 100755 index 00000000..e1d8a799 --- /dev/null +++ b/get_contributors.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash + +# Get a list of all contributors that contributed in some way +# either by directly opening a PR, or by participating in issues. +# +# This script is a helper to fill out the "Contributors" chapter. +# It'll only spit out the names and an user must add them to the book. +# +# This script takes two arguments: +# - `username`: Your github username used to authenticate the GitHub API +# - `token`: A github Personal Access Token used to authenticate the GitHub API + +set -euo pipefail + +# Check if there are `username` and `token` arguments +if [ $# -eq 0 ] +then + user="$(git config github.user)" + token="$(git config github.oauth-token)" +elif [ $# -eq 2 ] +then + user="$1" + token="$2" +else + user="" + token="" +fi + +if [ "$user" == "" -o "$token" == "" ] +then + echo "github token required. The token is normally loaded from" + echo "git config (github.user, github.oauth-token), but you can" + echo "also use as follows:" + echo "" + echo "Usage: $0 " + exit 1 +fi + +# Check if a command is available, otherwise exit. +function check_bin() { + if ! command -v $1 &> /dev/null + then + echo "'$1' is not installed, but required to run this script." + exit 1 + fi +} + +check_bin curl +check_bin jq + +function get_issue_numbers() { + local result="$(curl -s -u $user:$token -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/rust-lang/wg-async/issues?state=all&labels=$1&per_page=100" \ + | jq '.[].number')" + + if [ "$(echo $result | wc -w)" -ge 100 ]; + then + echo "Found 100 results for $1. Due to GitHub's paging limits, there might be some issues that are missing" + fi + echo $result +} + +# Get a list of users that participated in issues. +function issue_contributors() { + local numbers="$(get_issue_numbers status-quo-story-ideas) $(get_issue_numbers shiny-future)" + local numbers="$(echo $numbers | sort | uniq)" + + for num in $numbers; do + curl -s -u $user:$token -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/rust-lang/wg-async/issues/$num/comments | jq -r \ + '.[] | "[" + .user.login + "](" + .user.html_url + ")"' + done | sort | uniq +} + +# Get a list of direct code contributors +function code_contributors() { + curl -s -u $user:$token -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/rust-lang/wg-async/contributors?per_page=100" | jq -r \ + '.[] | "[" + .login + "](" + .html_url + ")"' | sort | uniq +} + +echo "Issue contributors" +issue_contributors + +echo + +echo "Code contributors" +code_contributors diff --git a/mermaid-init.js b/mermaid-init.js new file mode 100644 index 00000000..313a6e8b --- /dev/null +++ b/mermaid-init.js @@ -0,0 +1 @@ +mermaid.initialize({startOnLoad:true}); diff --git a/mermaid.min.js b/mermaid.min.js new file mode 100644 index 00000000..14ef691f --- /dev/null +++ b/mermaid.min.js @@ -0,0 +1,32 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.mermaid=e():t.mermaid=e()}("undefined"!=typeof self?self:this,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=383)}([function(t,e,n){"use strict";n.r(e);var r=function(t,e){return te?1:t>=e?0:NaN},i=function(t){var e;return 1===t.length&&(e=t,t=function(t,n){return r(e(t),n)}),{left:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r>>1;t(e[a],n)<0?r=a+1:i=a}return r},right:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r>>1;t(e[a],n)>0?i=a:r=a+1}return r}}};var a=i(r),o=a.right,s=a.left,c=o,u=function(t,e){null==e&&(e=l);for(var n=0,r=t.length-1,i=t[0],a=new Array(r<0?0:r);nt?1:e>=t?0:NaN},d=function(t){return null===t?NaN:+t},p=function(t,e){var n,r,i=t.length,a=0,o=-1,s=0,c=0;if(null==e)for(;++o1)return c/(a-1)},g=function(t,e){var n=p(t,e);return n?Math.sqrt(n):n},y=function(t,e){var n,r,i,a=t.length,o=-1;if(null==e){for(;++o=n)for(r=i=n;++on&&(r=n),i=n)for(r=i=n;++on&&(r=n),i0)return[t];if((r=e0)for(t=Math.ceil(t/o),e=Math.floor(e/o),a=new Array(i=Math.ceil(e-t+1));++s=0?(a>=w?10:a>=E?5:a>=T?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(a>=w?10:a>=E?5:a>=T?2:1)}function S(t,e,n){var r=Math.abs(e-t)/Math.max(0,n),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),a=r/i;return a>=w?i*=10:a>=E?i*=5:a>=T&&(i*=2),eh;)f.pop(),--d;var p,g=new Array(d+1);for(i=0;i<=d;++i)(p=g[i]=[]).x0=i>0?f[i-1]:l,p.x1=i=1)return+n(t[r-1],r-1,t);var r,i=(r-1)*e,a=Math.floor(i),o=+n(t[a],a,t);return o+(+n(t[a+1],a+1,t)-o)*(i-a)}},N=function(t,e,n){return t=b.call(t,d).sort(r),Math.ceil((n-e)/(2*(D(t,.75)-D(t,.25))*Math.pow(t.length,-1/3)))},B=function(t,e,n){return Math.ceil((n-e)/(3.5*g(t)*Math.pow(t.length,-1/3)))},L=function(t,e){var n,r,i=t.length,a=-1;if(null==e){for(;++a=n)for(r=n;++ar&&(r=n)}else for(;++a=n)for(r=n;++ar&&(r=n);return r},F=function(t,e){var n,r=t.length,i=r,a=-1,o=0;if(null==e)for(;++a=0;)for(e=(r=t[i]).length;--e>=0;)n[--o]=r[e];return n},j=function(t,e){var n,r,i=t.length,a=-1;if(null==e){for(;++a=n)for(r=n;++an&&(r=n)}else for(;++a=n)for(r=n;++an&&(r=n);return r},R=function(t,e){for(var n=e.length,r=new Array(n);n--;)r[n]=t[e[n]];return r},Y=function(t,e){if(n=t.length){var n,i,a=0,o=0,s=t[o];for(null==e&&(e=r);++a=0&&(n=t.slice(r+1),t=t.slice(0,r)),t&&!e.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))}function ct(t,e){for(var n,r=0,i=t.length;r0)for(var n,r,i=new Array(n),a=0;ae?1:t>=e?0:NaN}var _t="http://www.w3.org/1999/xhtml",kt={svg:"http://www.w3.org/2000/svg",xhtml:_t,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"},wt=function(t){var e=t+="",n=e.indexOf(":");return n>=0&&"xmlns"!==(e=t.slice(0,n))&&(t=t.slice(n+1)),kt.hasOwnProperty(e)?{space:kt[e],local:t}:t};function Et(t){return function(){this.removeAttribute(t)}}function Tt(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Ct(t,e){return function(){this.setAttribute(t,e)}}function At(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function St(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttribute(t):this.setAttribute(t,n)}}function Mt(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}var Ot=function(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView};function Dt(t){return function(){this.style.removeProperty(t)}}function Nt(t,e,n){return function(){this.style.setProperty(t,e,n)}}function Bt(t,e,n){return function(){var r=e.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,n)}}function Lt(t,e){return t.style.getPropertyValue(e)||Ot(t).getComputedStyle(t,null).getPropertyValue(e)}function Ft(t){return function(){delete this[t]}}function Pt(t,e){return function(){this[t]=e}}function It(t,e){return function(){var n=e.apply(this,arguments);null==n?delete this[t]:this[t]=n}}function jt(t){return t.trim().split(/^|\s+/)}function Rt(t){return t.classList||new Yt(t)}function Yt(t){this._node=t,this._names=jt(t.getAttribute("class")||"")}function zt(t,e){for(var n=Rt(t),r=-1,i=e.length;++r=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function Vt(){this.textContent=""}function Gt(t){return function(){this.textContent=t}}function qt(t){return function(){var e=t.apply(this,arguments);this.textContent=null==e?"":e}}function Xt(){this.innerHTML=""}function Zt(t){return function(){this.innerHTML=t}}function Jt(t){return function(){var e=t.apply(this,arguments);this.innerHTML=null==e?"":e}}function Kt(){this.nextSibling&&this.parentNode.appendChild(this)}function Qt(){this.previousSibling&&this.parentNode.insertBefore(this,this.parentNode.firstChild)}function te(t){return function(){var e=this.ownerDocument,n=this.namespaceURI;return n===_t&&e.documentElement.namespaceURI===_t?e.createElement(t):e.createElementNS(n,t)}}function ee(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}var ne=function(t){var e=wt(t);return(e.local?ee:te)(e)};function re(){return null}function ie(){var t=this.parentNode;t&&t.removeChild(this)}function ae(){var t=this.cloneNode(!1),e=this.parentNode;return e?e.insertBefore(t,this.nextSibling):t}function oe(){var t=this.cloneNode(!0),e=this.parentNode;return e?e.insertBefore(t,this.nextSibling):t}var se={},ce=null;"undefined"!=typeof document&&("onmouseenter"in document.documentElement||(se={mouseenter:"mouseover",mouseleave:"mouseout"}));function ue(t,e,n){return t=le(t,e,n),function(e){var n=e.relatedTarget;n&&(n===this||8&n.compareDocumentPosition(this))||t.call(this,e)}}function le(t,e,n){return function(r){var i=ce;ce=r;try{t.call(this,this.__data__,e,n)}finally{ce=i}}}function he(t){return t.trim().split(/^|\s+/).map((function(t){var e="",n=t.indexOf(".");return n>=0&&(e=t.slice(n+1),t=t.slice(0,n)),{type:t,name:e}}))}function fe(t){return function(){var e=this.__on;if(e){for(var n,r=0,i=-1,a=e.length;r=_&&(_=x+1);!(b=v[_])&&++_=0;)(r=i[a])&&(o&&4^r.compareDocumentPosition(o)&&o.parentNode.insertBefore(r,o),o=r);return this},sort:function(t){function e(e,n){return e&&n?t(e.__data__,n.__data__):!e-!n}t||(t=xt);for(var n=this._groups,r=n.length,i=new Array(r),a=0;a1?this.each((null==e?Dt:"function"==typeof e?Bt:Nt)(t,e,null==n?"":n)):Lt(this.node(),t)},property:function(t,e){return arguments.length>1?this.each((null==e?Ft:"function"==typeof e?It:Pt)(t,e)):this.node()[t]},classed:function(t,e){var n=jt(t+"");if(arguments.length<2){for(var r=Rt(this.node()),i=-1,a=n.length;++i>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===n?new qe(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===n?new qe(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=Le.exec(t))?new qe(e[1],e[2],e[3],1):(e=Fe.exec(t))?new qe(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=Pe.exec(t))?He(e[1],e[2],e[3],e[4]):(e=Ie.exec(t))?He(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=je.exec(t))?Ke(e[1],e[2]/100,e[3]/100,1):(e=Re.exec(t))?Ke(e[1],e[2]/100,e[3]/100,e[4]):Ye.hasOwnProperty(t)?We(Ye[t]):"transparent"===t?new qe(NaN,NaN,NaN,0):null}function We(t){return new qe(t>>16&255,t>>8&255,255&t,1)}function He(t,e,n,r){return r<=0&&(t=e=n=NaN),new qe(t,e,n,r)}function Ve(t){return t instanceof Me||(t=$e(t)),t?new qe((t=t.rgb()).r,t.g,t.b,t.opacity):new qe}function Ge(t,e,n,r){return 1===arguments.length?Ve(t):new qe(t,e,n,null==r?1:r)}function qe(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function Xe(){return"#"+Je(this.r)+Je(this.g)+Je(this.b)}function Ze(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function Je(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function Ke(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new en(t,e,n,r)}function Qe(t){if(t instanceof en)return new en(t.h,t.s,t.l,t.opacity);if(t instanceof Me||(t=$e(t)),!t)return new en;if(t instanceof en)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),a=Math.max(e,n,r),o=NaN,s=a-i,c=(a+i)/2;return s?(o=e===a?(n-r)/s+6*(n0&&c<1?0:o,new en(o,s,c,t.opacity)}function tn(t,e,n,r){return 1===arguments.length?Qe(t):new en(t,e,n,null==r?1:r)}function en(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function nn(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}function rn(t,e,n,r,i){var a=t*t,o=a*t;return((1-3*t+3*a-o)*e+(4-6*a+3*o)*n+(1+3*t+3*a-3*o)*r+o*i)/6}Ae(Me,$e,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:ze,formatHex:ze,formatHsl:function(){return Qe(this).formatHsl()},formatRgb:Ue,toString:Ue}),Ae(qe,Ge,Se(Me,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new qe(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new qe(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:Xe,formatHex:Xe,formatRgb:Ze,toString:Ze})),Ae(en,tn,Se(Me,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new en(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new en(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new qe(nn(t>=240?t-240:t+120,i,r),nn(t,i,r),nn(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));var an=function(t){var e=t.length-1;return function(n){var r=n<=0?n=0:n>=1?(n=1,e-1):Math.floor(n*e),i=t[r],a=t[r+1],o=r>0?t[r-1]:2*i-a,s=r180||n<-180?n-360*Math.round(n/360):n):sn(isNaN(t)?e:t)}function ln(t){return 1==(t=+t)?hn:function(e,n){return n-e?function(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}(e,n,t):sn(isNaN(e)?n:e)}}function hn(t,e){var n=e-t;return n?cn(t,n):sn(isNaN(t)?e:t)}var fn=function t(e){var n=ln(e);function r(t,e){var r=n((t=Ge(t)).r,(e=Ge(e)).r),i=n(t.g,e.g),a=n(t.b,e.b),o=hn(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=i(e),t.b=a(e),t.opacity=o(e),t+""}}return r.gamma=t,r}(1);function dn(t){return function(e){var n,r,i=e.length,a=new Array(i),o=new Array(i),s=new Array(i);for(n=0;na&&(i=e.slice(a,i),s[o]?s[o]+=i:s[++o]=i),(n=n[0])===(r=r[0])?s[o]?s[o]+=r:s[++o]=r:(s[++o]=null,c.push({i:o,x:_n(n,r)})),a=En.lastIndex;return a=0&&e._call.call(null,t),e=e._next;--Bn}function Vn(){In=(Pn=Rn.now())+jn,Bn=Ln=0;try{Hn()}finally{Bn=0,function(){var t,e,n=Tn,r=1/0;for(;n;)n._call?(r>n._time&&(r=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:Tn=e);Cn=t,qn(r)}(),In=0}}function Gn(){var t=Rn.now(),e=t-Pn;e>1e3&&(jn-=e,Pn=t)}function qn(t){Bn||(Ln&&(Ln=clearTimeout(Ln)),t-In>24?(t<1/0&&(Ln=setTimeout(Vn,t-Rn.now()-jn)),Fn&&(Fn=clearInterval(Fn))):(Fn||(Pn=Rn.now(),Fn=setInterval(Gn,1e3)),Bn=1,Yn(Vn)))}$n.prototype=Wn.prototype={constructor:$n,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?zn():+n)+(null==e?0:+e),this._next||Cn===this||(Cn?Cn._next=this:Tn=this,Cn=this),this._call=t,this._time=n,qn()},stop:function(){this._call&&(this._call=null,this._time=1/0,qn())}};var Xn=function(t,e,n){var r=new $n;return e=null==e?0:+e,r.restart((function(n){r.stop(),t(n+e)}),e,n),r},Zn=lt("start","end","cancel","interrupt"),Jn=[],Kn=function(t,e,n,r,i,a){var o=t.__transition;if(o){if(n in o)return}else t.__transition={};!function(t,e,n){var r,i=t.__transition;function a(c){var u,l,h,f;if(1!==n.state)return s();for(u in i)if((f=i[u]).name===n.name){if(3===f.state)return Xn(a);4===f.state?(f.state=6,f.timer.stop(),f.on.call("interrupt",t,t.__data__,f.index,f.group),delete i[u]):+u0)throw new Error("too late; already scheduled");return n}function tr(t,e){var n=er(t,e);if(n.state>3)throw new Error("too late; already running");return n}function er(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error("transition not found");return n}var nr,rr,ir,ar,or=function(t,e){var n,r,i,a=t.__transition,o=!0;if(a){for(i in e=null==e?null:e+"",a)(n=a[i]).name===e?(r=n.state>2&&n.state<5,n.state=6,n.timer.stop(),n.on.call(r?"interrupt":"cancel",t,t.__data__,n.index,n.group),delete a[i]):o=!1;o&&delete t.__transition}},sr=180/Math.PI,cr={translateX:0,translateY:0,rotate:0,skewX:0,scaleX:1,scaleY:1},ur=function(t,e,n,r,i,a){var o,s,c;return(o=Math.sqrt(t*t+e*e))&&(t/=o,e/=o),(c=t*n+e*r)&&(n-=t*c,r-=e*c),(s=Math.sqrt(n*n+r*r))&&(n/=s,r/=s,c/=s),t*r180?e+=360:e-t>180&&(t+=360),a.push({i:n.push(i(n)+"rotate(",null,r)-2,x:_n(t,e)})):e&&n.push(i(n)+"rotate("+e+r)}(a.rotate,o.rotate,s,c),function(t,e,n,a){t!==e?a.push({i:n.push(i(n)+"skewX(",null,r)-2,x:_n(t,e)}):e&&n.push(i(n)+"skewX("+e+r)}(a.skewX,o.skewX,s,c),function(t,e,n,r,a,o){if(t!==n||e!==r){var s=a.push(i(a)+"scale(",null,",",null,")");o.push({i:s-4,x:_n(t,n)},{i:s-2,x:_n(e,r)})}else 1===n&&1===r||a.push(i(a)+"scale("+n+","+r+")")}(a.scaleX,a.scaleY,o.scaleX,o.scaleY,s,c),a=o=null,function(t){for(var e,n=-1,r=c.length;++n=0&&(t=t.slice(0,e)),!t||"start"===t}))}(e)?Qn:tr;return function(){var o=a(this,t),s=o.on;s!==r&&(i=(r=s).copy()).on(e,n),o.on=i}}var Br=_e.prototype.constructor;function Lr(t){return function(){this.style.removeProperty(t)}}function Fr(t,e,n){return function(r){this.style.setProperty(t,e.call(this,r),n)}}function Pr(t,e,n){var r,i;function a(){var a=e.apply(this,arguments);return a!==i&&(r=(i=a)&&Fr(t,a,n)),r}return a._value=e,a}function Ir(t){return function(e){this.textContent=t.call(this,e)}}function jr(t){var e,n;function r(){var r=t.apply(this,arguments);return r!==n&&(e=(n=r)&&Ir(r)),e}return r._value=t,r}var Rr=0;function Yr(t,e,n,r){this._groups=t,this._parents=e,this._name=n,this._id=r}function zr(t){return _e().transition(t)}function Ur(){return++Rr}var $r=_e.prototype;function Wr(t){return t*t*t}function Hr(t){return--t*t*t+1}function Vr(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}Yr.prototype=zr.prototype={constructor:Yr,select:function(t){var e=this._name,n=this._id;"function"!=typeof t&&(t=ft(t));for(var r=this._groups,i=r.length,a=new Array(i),o=0;o1&&n.name===e)return new Yr([[t]],Xr,e,+r);return null},Jr=function(t){return function(){return t}},Kr=function(t,e,n){this.target=t,this.type=e,this.selection=n};function Qr(){ce.stopImmediatePropagation()}var ti=function(){ce.preventDefault(),ce.stopImmediatePropagation()},ei={name:"drag"},ni={name:"space"},ri={name:"handle"},ii={name:"center"};function ai(t){return[+t[0],+t[1]]}function oi(t){return[ai(t[0]),ai(t[1])]}function si(t){return function(e){return Dn(e,ce.touches,t)}}var ci={name:"x",handles:["w","e"].map(yi),input:function(t,e){return null==t?null:[[+t[0],e[0][1]],[+t[1],e[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},ui={name:"y",handles:["n","s"].map(yi),input:function(t,e){return null==t?null:[[e[0][0],+t[0]],[e[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},li={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(yi),input:function(t){return null==t?null:oi(t)},output:function(t){return t}},hi={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},fi={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},di={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},pi={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},gi={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function yi(t){return{type:t}}function vi(){return!ce.ctrlKey&&!ce.button}function mi(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function bi(){return navigator.maxTouchPoints||"ontouchstart"in this}function xi(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function _i(t){return t[0][0]===t[1][0]||t[0][1]===t[1][1]}function ki(t){var e=t.__brush;return e?e.dim.output(e.selection):null}function wi(){return Ci(ci)}function Ei(){return Ci(ui)}var Ti=function(){return Ci(li)};function Ci(t){var e,n=mi,r=vi,i=bi,a=!0,o=lt("start","brush","end"),s=6;function c(e){var n=e.property("__brush",g).selectAll(".overlay").data([yi("overlay")]);n.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",hi.overlay).merge(n).each((function(){var t=xi(this).extent;ke(this).attr("x",t[0][0]).attr("y",t[0][1]).attr("width",t[1][0]-t[0][0]).attr("height",t[1][1]-t[0][1])})),e.selectAll(".selection").data([yi("selection")]).enter().append("rect").attr("class","selection").attr("cursor",hi.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var r=e.selectAll(".handle").data(t.handles,(function(t){return t.type}));r.exit().remove(),r.enter().append("rect").attr("class",(function(t){return"handle handle--"+t.type})).attr("cursor",(function(t){return hi[t.type]})),e.each(u).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",f).filter(i).on("touchstart.brush",f).on("touchmove.brush",d).on("touchend.brush touchcancel.brush",p).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function u(){var t=ke(this),e=xi(this).selection;e?(t.selectAll(".selection").style("display",null).attr("x",e[0][0]).attr("y",e[0][1]).attr("width",e[1][0]-e[0][0]).attr("height",e[1][1]-e[0][1]),t.selectAll(".handle").style("display",null).attr("x",(function(t){return"e"===t.type[t.type.length-1]?e[1][0]-s/2:e[0][0]-s/2})).attr("y",(function(t){return"s"===t.type[0]?e[1][1]-s/2:e[0][1]-s/2})).attr("width",(function(t){return"n"===t.type||"s"===t.type?e[1][0]-e[0][0]+s:s})).attr("height",(function(t){return"e"===t.type||"w"===t.type?e[1][1]-e[0][1]+s:s}))):t.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function l(t,e,n){return!n&&t.__brush.emitter||new h(t,e)}function h(t,e){this.that=t,this.args=e,this.state=t.__brush,this.active=0}function f(){if((!e||ce.touches)&&r.apply(this,arguments)){var n,i,o,s,c,h,f,d,p,g,y,v=this,m=ce.target.__data__.type,b="selection"===(a&&ce.metaKey?m="overlay":m)?ei:a&&ce.altKey?ii:ri,x=t===ui?null:pi[m],_=t===ci?null:gi[m],k=xi(v),w=k.extent,E=k.selection,T=w[0][0],C=w[0][1],A=w[1][0],S=w[1][1],M=0,O=0,D=x&&_&&a&&ce.shiftKey,N=ce.touches?si(ce.changedTouches[0].identifier):Nn,B=N(v),L=B,F=l(v,arguments,!0).beforestart();"overlay"===m?(E&&(p=!0),k.selection=E=[[n=t===ui?T:B[0],o=t===ci?C:B[1]],[c=t===ui?A:n,f=t===ci?S:o]]):(n=E[0][0],o=E[0][1],c=E[1][0],f=E[1][1]),i=n,s=o,h=c,d=f;var P=ke(v).attr("pointer-events","none"),I=P.selectAll(".overlay").attr("cursor",hi[m]);if(ce.touches)F.moved=R,F.ended=z;else{var j=ke(ce.view).on("mousemove.brush",R,!0).on("mouseup.brush",z,!0);a&&j.on("keydown.brush",U,!0).on("keyup.brush",$,!0),Te(ce.view)}Qr(),or(v),u.call(v),F.start()}function R(){var t=N(v);!D||g||y||(Math.abs(t[0]-L[0])>Math.abs(t[1]-L[1])?y=!0:g=!0),L=t,p=!0,ti(),Y()}function Y(){var t;switch(M=L[0]-B[0],O=L[1]-B[1],b){case ni:case ei:x&&(M=Math.max(T-n,Math.min(A-c,M)),i=n+M,h=c+M),_&&(O=Math.max(C-o,Math.min(S-f,O)),s=o+O,d=f+O);break;case ri:x<0?(M=Math.max(T-n,Math.min(A-n,M)),i=n+M,h=c):x>0&&(M=Math.max(T-c,Math.min(A-c,M)),i=n,h=c+M),_<0?(O=Math.max(C-o,Math.min(S-o,O)),s=o+O,d=f):_>0&&(O=Math.max(C-f,Math.min(S-f,O)),s=o,d=f+O);break;case ii:x&&(i=Math.max(T,Math.min(A,n-M*x)),h=Math.max(T,Math.min(A,c+M*x))),_&&(s=Math.max(C,Math.min(S,o-O*_)),d=Math.max(C,Math.min(S,f+O*_)))}h0&&(n=i-M),_<0?f=d-O:_>0&&(o=s-O),b=ni,I.attr("cursor",hi.selection),Y());break;default:return}ti()}function $(){switch(ce.keyCode){case 16:D&&(g=y=D=!1,Y());break;case 18:b===ii&&(x<0?c=h:x>0&&(n=i),_<0?f=d:_>0&&(o=s),b=ri,Y());break;case 32:b===ni&&(ce.altKey?(x&&(c=h-M*x,n=i+M*x),_&&(f=d-O*_,o=s+O*_),b=ii):(x<0?c=h:x>0&&(n=i),_<0?f=d:_>0&&(o=s),b=ri),I.attr("cursor",hi[m]),Y());break;default:return}ti()}}function d(){l(this,arguments).moved()}function p(){l(this,arguments).ended()}function g(){var e=this.__brush||{selection:null};return e.extent=oi(n.apply(this,arguments)),e.dim=t,e}return c.move=function(e,n){e.selection?e.on("start.brush",(function(){l(this,arguments).beforestart().start()})).on("interrupt.brush end.brush",(function(){l(this,arguments).end()})).tween("brush",(function(){var e=this,r=e.__brush,i=l(e,arguments),a=r.selection,o=t.input("function"==typeof n?n.apply(this,arguments):n,r.extent),s=Sn(a,o);function c(t){r.selection=1===t&&null===o?null:s(t),u.call(e),i.brush()}return null!==a&&null!==o?c:c(1)})):e.each((function(){var e=this,r=arguments,i=e.__brush,a=t.input("function"==typeof n?n.apply(e,r):n,i.extent),o=l(e,r).beforestart();or(e),i.selection=null===a?null:a,u.call(e),o.start().brush().end()}))},c.clear=function(t){c.move(t,null)},h.prototype={beforestart:function(){return 1==++this.active&&(this.state.emitter=this,this.starting=!0),this},start:function(){return this.starting?(this.starting=!1,this.emit("start")):this.emit("brush"),this},brush:function(){return this.emit("brush"),this},end:function(){return 0==--this.active&&(delete this.state.emitter,this.emit("end")),this},emit:function(e){pe(new Kr(c,e,t.output(this.state.selection)),o.apply,o,[e,this.that,this.args])}},c.extent=function(t){return arguments.length?(n="function"==typeof t?t:Jr(oi(t)),c):n},c.filter=function(t){return arguments.length?(r="function"==typeof t?t:Jr(!!t),c):r},c.touchable=function(t){return arguments.length?(i="function"==typeof t?t:Jr(!!t),c):i},c.handleSize=function(t){return arguments.length?(s=+t,c):s},c.keyModifiers=function(t){return arguments.length?(a=!!t,c):a},c.on=function(){var t=o.on.apply(o,arguments);return t===o?c:t},c}var Ai=Math.cos,Si=Math.sin,Mi=Math.PI,Oi=Mi/2,Di=2*Mi,Ni=Math.max;function Bi(t){return function(e,n){return t(e.source.value+e.target.value,n.source.value+n.target.value)}}var Li=function(){var t=0,e=null,n=null,r=null;function i(i){var a,o,s,c,u,l,h=i.length,f=[],d=k(h),p=[],g=[],y=g.groups=new Array(h),v=new Array(h*h);for(a=0,u=-1;++u1e-6)if(Math.abs(l*s-c*u)>1e-6&&i){var f=n-a,d=r-o,p=s*s+c*c,g=f*f+d*d,y=Math.sqrt(p),v=Math.sqrt(h),m=i*Math.tan((Ii-Math.acos((p+h-g)/(2*y*v)))/2),b=m/v,x=m/y;Math.abs(b-1)>1e-6&&(this._+="L"+(t+b*u)+","+(e+b*l)),this._+="A"+i+","+i+",0,0,"+ +(l*f>u*d)+","+(this._x1=t+x*s)+","+(this._y1=e+x*c)}else this._+="L"+(this._x1=t)+","+(this._y1=e);else;},arc:function(t,e,n,r,i,a){t=+t,e=+e,a=!!a;var o=(n=+n)*Math.cos(r),s=n*Math.sin(r),c=t+o,u=e+s,l=1^a,h=a?r-i:i-r;if(n<0)throw new Error("negative radius: "+n);null===this._x1?this._+="M"+c+","+u:(Math.abs(this._x1-c)>1e-6||Math.abs(this._y1-u)>1e-6)&&(this._+="L"+c+","+u),n&&(h<0&&(h=h%ji+ji),h>Ri?this._+="A"+n+","+n+",0,1,"+l+","+(t-o)+","+(e-s)+"A"+n+","+n+",0,1,"+l+","+(this._x1=c)+","+(this._y1=u):h>1e-6&&(this._+="A"+n+","+n+",0,"+ +(h>=Ii)+","+l+","+(this._x1=t+n*Math.cos(i))+","+(this._y1=e+n*Math.sin(i))))},rect:function(t,e,n,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +n+"v"+ +r+"h"+-n+"Z"},toString:function(){return this._}};var Ui=zi;function $i(t){return t.source}function Wi(t){return t.target}function Hi(t){return t.radius}function Vi(t){return t.startAngle}function Gi(t){return t.endAngle}var qi=function(){var t=$i,e=Wi,n=Hi,r=Vi,i=Gi,a=null;function o(){var o,s=Fi.call(arguments),c=t.apply(this,s),u=e.apply(this,s),l=+n.apply(this,(s[0]=c,s)),h=r.apply(this,s)-Oi,f=i.apply(this,s)-Oi,d=l*Ai(h),p=l*Si(h),g=+n.apply(this,(s[0]=u,s)),y=r.apply(this,s)-Oi,v=i.apply(this,s)-Oi;if(a||(a=o=Ui()),a.moveTo(d,p),a.arc(0,0,l,h,f),h===y&&f===v||(a.quadraticCurveTo(0,0,g*Ai(y),g*Si(y)),a.arc(0,0,g,y,v)),a.quadraticCurveTo(0,0,d,p),a.closePath(),o)return a=null,o+""||null}return o.radius=function(t){return arguments.length?(n="function"==typeof t?t:Pi(+t),o):n},o.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:Pi(+t),o):r},o.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:Pi(+t),o):i},o.source=function(e){return arguments.length?(t=e,o):t},o.target=function(t){return arguments.length?(e=t,o):e},o.context=function(t){return arguments.length?(a=null==t?null:t,o):a},o};function Xi(){}function Zi(t,e){var n=new Xi;if(t instanceof Xi)t.each((function(t,e){n.set(e,t)}));else if(Array.isArray(t)){var r,i=-1,a=t.length;if(null==e)for(;++i=r.length)return null!=t&&n.sort(t),null!=e?e(n):n;for(var c,u,l,h=-1,f=n.length,d=r[i++],p=Ji(),g=o();++hr.length)return n;var o,s=i[a-1];return null!=e&&a>=r.length?o=n.entries():(o=[],n.each((function(e,n){o.push({key:n,values:t(e,a)})}))),null!=s?o.sort((function(t,e){return s(t.key,e.key)})):o}(a(t,0,ea,na),0)},key:function(t){return r.push(t),n},sortKeys:function(t){return i[r.length-1]=t,n},sortValues:function(e){return t=e,n},rollup:function(t){return e=t,n}}};function Qi(){return{}}function ta(t,e,n){t[e]=n}function ea(){return Ji()}function na(t,e,n){t.set(e,n)}function ra(){}var ia=Ji.prototype;function aa(t,e){var n=new ra;if(t instanceof ra)t.each((function(t){n.add(t)}));else if(t){var r=-1,i=t.length;if(null==e)for(;++r6/29*(6/29)*(6/29)?Math.pow(t,1/3):t/(6/29*3*(6/29))+4/29}function va(t){return t>6/29?t*t*t:6/29*3*(6/29)*(t-4/29)}function ma(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function ba(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function xa(t){if(t instanceof wa)return new wa(t.h,t.c,t.l,t.opacity);if(t instanceof ga||(t=fa(t)),0===t.a&&0===t.b)return new wa(NaN,0r!=d>r&&n<(f-u)*(r-l)/(d-l)+u&&(i=-i)}return i}function Ia(t,e,n){var r,i,a,o;return function(t,e,n){return(e[0]-t[0])*(n[1]-t[1])==(n[0]-t[0])*(e[1]-t[1])}(t,e,n)&&(i=t[r=+(t[0]===e[0])],a=n[r],o=e[r],i<=a&&a<=o||o<=a&&a<=i)}var ja=function(){},Ra=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]],Ya=function(){var t=1,e=1,n=M,r=s;function i(t){var e=n(t);if(Array.isArray(e))e=e.slice().sort(Ba);else{var r=y(t),i=r[0],o=r[1];e=S(i,o,e),e=k(Math.floor(i/e)*e,Math.floor(o/e)*e,e)}return e.map((function(e){return a(t,e)}))}function a(n,i){var a=[],s=[];return function(n,r,i){var a,s,c,u,l,h,f=new Array,d=new Array;a=s=-1,u=n[0]>=r,Ra[u<<1].forEach(p);for(;++a=r,Ra[c|u<<1].forEach(p);Ra[u<<0].forEach(p);for(;++s=r,l=n[s*t]>=r,Ra[u<<1|l<<2].forEach(p);++a=r,h=l,l=n[s*t+a+1]>=r,Ra[c|u<<1|l<<2|h<<3].forEach(p);Ra[u|l<<3].forEach(p)}a=-1,l=n[s*t]>=r,Ra[l<<2].forEach(p);for(;++a=r,Ra[l<<2|h<<3].forEach(p);function p(t){var e,n,r=[t[0][0]+a,t[0][1]+s],c=[t[1][0]+a,t[1][1]+s],u=o(r),l=o(c);(e=d[u])?(n=f[l])?(delete d[e.end],delete f[n.start],e===n?(e.ring.push(c),i(e.ring)):f[e.start]=d[n.end]={start:e.start,end:n.end,ring:e.ring.concat(n.ring)}):(delete d[e.end],e.ring.push(c),d[e.end=l]=e):(e=f[l])?(n=d[u])?(delete f[e.start],delete d[n.end],e===n?(e.ring.push(c),i(e.ring)):f[n.start]=d[e.end]={start:n.start,end:e.end,ring:n.ring.concat(e.ring)}):(delete f[e.start],e.ring.unshift(r),f[e.start=u]=e):f[u]=d[l]={start:u,end:l,ring:[r,c]}}Ra[l<<3].forEach(p)}(n,i,(function(t){r(t,n,i),function(t){for(var e=0,n=t.length,r=t[n-1][1]*t[0][0]-t[n-1][0]*t[0][1];++e0?a.push([t]):s.push(t)})),s.forEach((function(t){for(var e,n=0,r=a.length;n0&&o0&&s0&&a>0))throw new Error("invalid size");return t=r,e=a,i},i.thresholds=function(t){return arguments.length?(n="function"==typeof t?t:Array.isArray(t)?La(Na.call(t)):La(t),i):n},i.smooth=function(t){return arguments.length?(r=t?s:ja,i):r===s},i};function za(t,e,n){for(var r=t.width,i=t.height,a=1+(n<<1),o=0;o=n&&(s>=a&&(c-=t.data[s-a+o*r]),e.data[s-n+o*r]=c/Math.min(s+1,r-1+a-s,a))}function Ua(t,e,n){for(var r=t.width,i=t.height,a=1+(n<<1),o=0;o=n&&(s>=a&&(c-=t.data[o+(s-a)*r]),e.data[o+(s-n)*r]=c/Math.min(s+1,i-1+a-s,a))}function $a(t){return t[0]}function Wa(t){return t[1]}function Ha(){return 1}var Va=function(){var t=$a,e=Wa,n=Ha,r=960,i=500,a=20,o=2,s=3*a,c=r+2*s>>o,u=i+2*s>>o,l=La(20);function h(r){var i=new Float32Array(c*u),h=new Float32Array(c*u);r.forEach((function(r,a,l){var h=+t(r,a,l)+s>>o,f=+e(r,a,l)+s>>o,d=+n(r,a,l);h>=0&&h=0&&f>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o),za({width:c,height:u,data:i},{width:c,height:u,data:h},a>>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o),za({width:c,height:u,data:i},{width:c,height:u,data:h},a>>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o);var d=l(i);if(!Array.isArray(d)){var p=L(i);d=S(0,p,d),(d=k(0,Math.floor(p/d)*d,d)).shift()}return Ya().thresholds(d).size([c,u])(i).map(f)}function f(t){return t.value*=Math.pow(2,-2*o),t.coordinates.forEach(d),t}function d(t){t.forEach(p)}function p(t){t.forEach(g)}function g(t){t[0]=t[0]*Math.pow(2,o)-s,t[1]=t[1]*Math.pow(2,o)-s}function y(){return c=r+2*(s=3*a)>>o,u=i+2*s>>o,h}return h.x=function(e){return arguments.length?(t="function"==typeof e?e:La(+e),h):t},h.y=function(t){return arguments.length?(e="function"==typeof t?t:La(+t),h):e},h.weight=function(t){return arguments.length?(n="function"==typeof t?t:La(+t),h):n},h.size=function(t){if(!arguments.length)return[r,i];var e=Math.ceil(t[0]),n=Math.ceil(t[1]);if(!(e>=0||e>=0))throw new Error("invalid size");return r=e,i=n,y()},h.cellSize=function(t){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return o=Math.floor(Math.log(t)/Math.LN2),y()},h.thresholds=function(t){return arguments.length?(l="function"==typeof t?t:Array.isArray(t)?La(Na.call(t)):La(t),h):l},h.bandwidth=function(t){if(!arguments.length)return Math.sqrt(a*(a+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return a=Math.round((Math.sqrt(4*t*t+1)-1)/2),y()},h},Ga=function(t){return function(){return t}};function qa(t,e,n,r,i,a,o,s,c,u){this.target=t,this.type=e,this.subject=n,this.identifier=r,this.active=i,this.x=a,this.y=o,this.dx=s,this.dy=c,this._=u}function Xa(){return!ce.ctrlKey&&!ce.button}function Za(){return this.parentNode}function Ja(t){return null==t?{x:ce.x,y:ce.y}:t}function Ka(){return navigator.maxTouchPoints||"ontouchstart"in this}qa.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};var Qa=function(){var t,e,n,r,i=Xa,a=Za,o=Ja,s=Ka,c={},u=lt("start","drag","end"),l=0,h=0;function f(t){t.on("mousedown.drag",d).filter(s).on("touchstart.drag",y).on("touchmove.drag",v).on("touchend.drag touchcancel.drag",m).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function d(){if(!r&&i.apply(this,arguments)){var o=b("mouse",a.apply(this,arguments),Nn,this,arguments);o&&(ke(ce.view).on("mousemove.drag",p,!0).on("mouseup.drag",g,!0),Te(ce.view),we(),n=!1,t=ce.clientX,e=ce.clientY,o("start"))}}function p(){if(Ee(),!n){var r=ce.clientX-t,i=ce.clientY-e;n=r*r+i*i>h}c.mouse("drag")}function g(){ke(ce.view).on("mousemove.drag mouseup.drag",null),Ce(ce.view,n),Ee(),c.mouse("end")}function y(){if(i.apply(this,arguments)){var t,e,n=ce.changedTouches,r=a.apply(this,arguments),o=n.length;for(t=0;t9999?"+"+io(e,6):io(e,4))+"-"+io(t.getUTCMonth()+1,2)+"-"+io(t.getUTCDate(),2)+(a?"T"+io(n,2)+":"+io(r,2)+":"+io(i,2)+"."+io(a,3)+"Z":i?"T"+io(n,2)+":"+io(r,2)+":"+io(i,2)+"Z":r||n?"T"+io(n,2)+":"+io(r,2)+"Z":"")}var oo=function(t){var e=new RegExp('["'+t+"\n\r]"),n=t.charCodeAt(0);function r(t,e){var r,i=[],a=t.length,o=0,s=0,c=a<=0,u=!1;function l(){if(c)return eo;if(u)return u=!1,to;var e,r,i=o;if(34===t.charCodeAt(i)){for(;o++=a?c=!0:10===(r=t.charCodeAt(o++))?u=!0:13===r&&(u=!0,10===t.charCodeAt(o)&&++o),t.slice(i+1,e-1).replace(/""/g,'"')}for(;o=(a=(g+v)/2))?g=a:v=a,(l=n>=(o=(y+m)/2))?y=o:m=o,i=d,!(d=d[h=l<<1|u]))return i[h]=p,t;if(s=+t._x.call(null,d.data),c=+t._y.call(null,d.data),e===s&&n===c)return p.next=d,i?i[h]=p:t._root=p,t;do{i=i?i[h]=new Array(4):t._root=new Array(4),(u=e>=(a=(g+v)/2))?g=a:v=a,(l=n>=(o=(y+m)/2))?y=o:m=o}while((h=l<<1|u)==(f=(c>=o)<<1|s>=a));return i[f]=d,i[h]=p,t}var _s=function(t,e,n,r,i){this.node=t,this.x0=e,this.y0=n,this.x1=r,this.y1=i};function ks(t){return t[0]}function ws(t){return t[1]}function Es(t,e,n){var r=new Ts(null==e?ks:e,null==n?ws:n,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function Ts(t,e,n,r,i,a){this._x=t,this._y=e,this._x0=n,this._y0=r,this._x1=i,this._y1=a,this._root=void 0}function Cs(t){for(var e={data:t.data},n=e;t=t.next;)n=n.next={data:t.data};return e}var As=Es.prototype=Ts.prototype;function Ss(t){return t.x+t.vx}function Ms(t){return t.y+t.vy}As.copy=function(){var t,e,n=new Ts(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return n;if(!r.length)return n._root=Cs(r),n;for(t=[{source:r,target:n._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(e=r.source[i])&&(e.length?t.push({source:e,target:r.target[i]=new Array(4)}):r.target[i]=Cs(e));return n},As.add=function(t){var e=+this._x.call(null,t),n=+this._y.call(null,t);return xs(this.cover(e,n),e,n,t)},As.addAll=function(t){var e,n,r,i,a=t.length,o=new Array(a),s=new Array(a),c=1/0,u=1/0,l=-1/0,h=-1/0;for(n=0;nl&&(l=r),ih&&(h=i));if(c>l||u>h)return this;for(this.cover(c,u).cover(l,h),n=0;nt||t>=i||r>e||e>=a;)switch(s=(ef||(a=c.y0)>d||(o=c.x1)=v)<<1|t>=y)&&(c=p[p.length-1],p[p.length-1]=p[p.length-1-u],p[p.length-1-u]=c)}else{var m=t-+this._x.call(null,g.data),b=e-+this._y.call(null,g.data),x=m*m+b*b;if(x=(s=(p+y)/2))?p=s:y=s,(l=o>=(c=(g+v)/2))?g=c:v=c,e=d,!(d=d[h=l<<1|u]))return this;if(!d.length)break;(e[h+1&3]||e[h+2&3]||e[h+3&3])&&(n=e,f=h)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):e?(i?e[h]=i:delete e[h],(d=e[0]||e[1]||e[2]||e[3])&&d===(e[3]||e[2]||e[1]||e[0])&&!d.length&&(n?n[f]=d:this._root=d),this):(this._root=i,this)},As.removeAll=function(t){for(var e=0,n=t.length;ec+d||iu+d||as.index){var p=c-o.x-o.vx,g=u-o.y-o.vy,y=p*p+g*g;yt.r&&(t.r=t[e].r)}function s(){if(e){var r,i,a=e.length;for(n=new Array(a),r=0;r1?(null==n?s.remove(t):s.set(t,d(n)),e):s.get(t)},find:function(e,n,r){var i,a,o,s,c,u=0,l=t.length;for(null==r?r=1/0:r*=r,u=0;u1?(u.on(t,n),e):u.on(t)}}},js=function(){var t,e,n,r,i=ms(-30),a=1,o=1/0,s=.81;function c(r){var i,a=t.length,o=Es(t,Ls,Fs).visitAfter(l);for(n=r,i=0;i=o)){(t.data!==e||t.next)&&(0===l&&(d+=(l=bs())*l),0===h&&(d+=(h=bs())*h),d1?r[0]+r.slice(2):r,+t.slice(n+1)]},$s=function(t){return(t=Us(Math.abs(t)))?t[1]:NaN},Ws=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Hs(t){if(!(e=Ws.exec(t)))throw new Error("invalid format: "+t);var e;return new Vs({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}function Vs(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}Hs.prototype=Vs.prototype,Vs.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var Gs,qs,Xs,Zs,Js=function(t,e){var n=Us(t,e);if(!n)return t+"";var r=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")},Ks={"%":function(t,e){return(100*t).toFixed(e)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,e){return t.toExponential(e)},f:function(t,e){return t.toFixed(e)},g:function(t,e){return t.toPrecision(e)},o:function(t){return Math.round(t).toString(8)},p:function(t,e){return Js(100*t,e)},r:Js,s:function(t,e){var n=Us(t,e);if(!n)return t+"";var r=n[0],i=n[1],a=i-(Gs=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,o=r.length;return a===o?r:a>o?r+new Array(a-o+1).join("0"):a>0?r.slice(0,a)+"."+r.slice(a):"0."+new Array(1-a).join("0")+Us(t,Math.max(0,e+a-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}},Qs=function(t){return t},tc=Array.prototype.map,ec=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"],nc=function(t){var e,n,r=void 0===t.grouping||void 0===t.thousands?Qs:(e=tc.call(t.grouping,Number),n=t.thousands+"",function(t,r){for(var i=t.length,a=[],o=0,s=e[0],c=0;i>0&&s>0&&(c+s+1>r&&(s=Math.max(1,r-c)),a.push(t.substring(i-=s,i+s)),!((c+=s+1)>r));)s=e[o=(o+1)%e.length];return a.reverse().join(n)}),i=void 0===t.currency?"":t.currency[0]+"",a=void 0===t.currency?"":t.currency[1]+"",o=void 0===t.decimal?".":t.decimal+"",s=void 0===t.numerals?Qs:function(t){return function(e){return e.replace(/[0-9]/g,(function(e){return t[+e]}))}}(tc.call(t.numerals,String)),c=void 0===t.percent?"%":t.percent+"",u=void 0===t.minus?"-":t.minus+"",l=void 0===t.nan?"NaN":t.nan+"";function h(t){var e=(t=Hs(t)).fill,n=t.align,h=t.sign,f=t.symbol,d=t.zero,p=t.width,g=t.comma,y=t.precision,v=t.trim,m=t.type;"n"===m?(g=!0,m="g"):Ks[m]||(void 0===y&&(y=12),v=!0,m="g"),(d||"0"===e&&"="===n)&&(d=!0,e="0",n="=");var b="$"===f?i:"#"===f&&/[boxX]/.test(m)?"0"+m.toLowerCase():"",x="$"===f?a:/[%p]/.test(m)?c:"",_=Ks[m],k=/[defgprs%]/.test(m);function w(t){var i,a,c,f=b,w=x;if("c"===m)w=_(t)+w,t="";else{var E=(t=+t)<0;if(t=isNaN(t)?l:_(Math.abs(t),y),v&&(t=function(t){t:for(var e,n=t.length,r=1,i=-1;r0&&(i=0)}return i>0?t.slice(0,i)+t.slice(e+1):t}(t)),E&&0==+t&&(E=!1),f=(E?"("===h?h:u:"-"===h||"("===h?"":h)+f,w=("s"===m?ec[8+Gs/3]:"")+w+(E&&"("===h?")":""),k)for(i=-1,a=t.length;++i(c=t.charCodeAt(i))||c>57){w=(46===c?o+t.slice(i+1):t.slice(i))+w,t=t.slice(0,i);break}}g&&!d&&(t=r(t,1/0));var T=f.length+t.length+w.length,C=T>1)+f+t+w+C.slice(T);break;default:t=C+f+t+w}return s(t)}return y=void 0===y?6:/[gprs]/.test(m)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),w.toString=function(){return t+""},w}return{format:h,formatPrefix:function(t,e){var n=h(((t=Hs(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor($s(e)/3))),i=Math.pow(10,-r),a=ec[8+r/3];return function(t){return n(i*t)+a}}}};function rc(t){return qs=nc(t),Xs=qs.format,Zs=qs.formatPrefix,qs}rc({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"});var ic=function(t){return Math.max(0,-$s(Math.abs(t)))},ac=function(t,e){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor($s(e)/3)))-$s(Math.abs(t)))},oc=function(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,$s(e)-$s(t))+1},sc=function(){return new cc};function cc(){this.reset()}cc.prototype={constructor:cc,reset:function(){this.s=this.t=0},add:function(t){lc(uc,t,this.t),lc(this,uc.s,this.s),this.s?this.t+=uc.t:this.s=uc.t},valueOf:function(){return this.s}};var uc=new cc;function lc(t,e,n){var r=t.s=e+n,i=r-e,a=r-i;t.t=e-a+(n-i)}var hc=Math.PI,fc=hc/2,dc=hc/4,pc=2*hc,gc=180/hc,yc=hc/180,vc=Math.abs,mc=Math.atan,bc=Math.atan2,xc=Math.cos,_c=Math.ceil,kc=Math.exp,wc=(Math.floor,Math.log),Ec=Math.pow,Tc=Math.sin,Cc=Math.sign||function(t){return t>0?1:t<0?-1:0},Ac=Math.sqrt,Sc=Math.tan;function Mc(t){return t>1?0:t<-1?hc:Math.acos(t)}function Oc(t){return t>1?fc:t<-1?-fc:Math.asin(t)}function Dc(t){return(t=Tc(t/2))*t}function Nc(){}function Bc(t,e){t&&Fc.hasOwnProperty(t.type)&&Fc[t.type](t,e)}var Lc={Feature:function(t,e){Bc(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r=0?1:-1,i=r*n,a=xc(e=(e*=yc)/2+dc),o=Tc(e),s=Uc*o,c=zc*a+s*xc(i),u=s*r*Tc(i);Wc.add(bc(u,c)),Yc=t,zc=a,Uc=o}var Jc=function(t){return Hc.reset(),$c(t,Vc),2*Hc};function Kc(t){return[bc(t[1],t[0]),Oc(t[2])]}function Qc(t){var e=t[0],n=t[1],r=xc(n);return[r*xc(e),r*Tc(e),Tc(n)]}function tu(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function eu(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function nu(t,e){t[0]+=e[0],t[1]+=e[1],t[2]+=e[2]}function ru(t,e){return[t[0]*e,t[1]*e,t[2]*e]}function iu(t){var e=Ac(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=e,t[1]/=e,t[2]/=e}var au,ou,su,cu,uu,lu,hu,fu,du,pu,gu=sc(),yu={point:vu,lineStart:bu,lineEnd:xu,polygonStart:function(){yu.point=_u,yu.lineStart=ku,yu.lineEnd=wu,gu.reset(),Vc.polygonStart()},polygonEnd:function(){Vc.polygonEnd(),yu.point=vu,yu.lineStart=bu,yu.lineEnd=xu,Wc<0?(au=-(su=180),ou=-(cu=90)):gu>1e-6?cu=90:gu<-1e-6&&(ou=-90),pu[0]=au,pu[1]=su},sphere:function(){au=-(su=180),ou=-(cu=90)}};function vu(t,e){du.push(pu=[au=t,su=t]),ecu&&(cu=e)}function mu(t,e){var n=Qc([t*yc,e*yc]);if(fu){var r=eu(fu,n),i=eu([r[1],-r[0],0],r);iu(i),i=Kc(i);var a,o=t-uu,s=o>0?1:-1,c=i[0]*gc*s,u=vc(o)>180;u^(s*uucu&&(cu=a):u^(s*uu<(c=(c+360)%360-180)&&ccu&&(cu=e)),u?tEu(au,su)&&(su=t):Eu(t,su)>Eu(au,su)&&(au=t):su>=au?(tsu&&(su=t)):t>uu?Eu(au,t)>Eu(au,su)&&(su=t):Eu(t,su)>Eu(au,su)&&(au=t)}else du.push(pu=[au=t,su=t]);ecu&&(cu=e),fu=n,uu=t}function bu(){yu.point=mu}function xu(){pu[0]=au,pu[1]=su,yu.point=vu,fu=null}function _u(t,e){if(fu){var n=t-uu;gu.add(vc(n)>180?n+(n>0?360:-360):n)}else lu=t,hu=e;Vc.point(t,e),mu(t,e)}function ku(){Vc.lineStart()}function wu(){_u(lu,hu),Vc.lineEnd(),vc(gu)>1e-6&&(au=-(su=180)),pu[0]=au,pu[1]=su,fu=null}function Eu(t,e){return(e-=t)<0?e+360:e}function Tu(t,e){return t[0]-e[0]}function Cu(t,e){return t[0]<=t[1]?t[0]<=e&&e<=t[1]:eEu(r[0],r[1])&&(r[1]=i[1]),Eu(i[0],r[1])>Eu(r[0],r[1])&&(r[0]=i[0])):a.push(r=i);for(o=-1/0,e=0,r=a[n=a.length-1];e<=n;r=i,++e)i=a[e],(s=Eu(r[1],i[0]))>o&&(o=s,au=i[0],su=r[1])}return du=pu=null,au===1/0||ou===1/0?[[NaN,NaN],[NaN,NaN]]:[[au,ou],[su,cu]]},Wu={sphere:Nc,point:Hu,lineStart:Gu,lineEnd:Zu,polygonStart:function(){Wu.lineStart=Ju,Wu.lineEnd=Ku},polygonEnd:function(){Wu.lineStart=Gu,Wu.lineEnd=Zu}};function Hu(t,e){t*=yc;var n=xc(e*=yc);Vu(n*xc(t),n*Tc(t),Tc(e))}function Vu(t,e,n){++Au,Mu+=(t-Mu)/Au,Ou+=(e-Ou)/Au,Du+=(n-Du)/Au}function Gu(){Wu.point=qu}function qu(t,e){t*=yc;var n=xc(e*=yc);Yu=n*xc(t),zu=n*Tc(t),Uu=Tc(e),Wu.point=Xu,Vu(Yu,zu,Uu)}function Xu(t,e){t*=yc;var n=xc(e*=yc),r=n*xc(t),i=n*Tc(t),a=Tc(e),o=bc(Ac((o=zu*a-Uu*i)*o+(o=Uu*r-Yu*a)*o+(o=Yu*i-zu*r)*o),Yu*r+zu*i+Uu*a);Su+=o,Nu+=o*(Yu+(Yu=r)),Bu+=o*(zu+(zu=i)),Lu+=o*(Uu+(Uu=a)),Vu(Yu,zu,Uu)}function Zu(){Wu.point=Hu}function Ju(){Wu.point=Qu}function Ku(){tl(ju,Ru),Wu.point=Hu}function Qu(t,e){ju=t,Ru=e,t*=yc,e*=yc,Wu.point=tl;var n=xc(e);Yu=n*xc(t),zu=n*Tc(t),Uu=Tc(e),Vu(Yu,zu,Uu)}function tl(t,e){t*=yc;var n=xc(e*=yc),r=n*xc(t),i=n*Tc(t),a=Tc(e),o=zu*a-Uu*i,s=Uu*r-Yu*a,c=Yu*i-zu*r,u=Ac(o*o+s*s+c*c),l=Oc(u),h=u&&-l/u;Fu+=h*o,Pu+=h*s,Iu+=h*c,Su+=l,Nu+=l*(Yu+(Yu=r)),Bu+=l*(zu+(zu=i)),Lu+=l*(Uu+(Uu=a)),Vu(Yu,zu,Uu)}var el=function(t){Au=Su=Mu=Ou=Du=Nu=Bu=Lu=Fu=Pu=Iu=0,$c(t,Wu);var e=Fu,n=Pu,r=Iu,i=e*e+n*n+r*r;return i<1e-12&&(e=Nu,n=Bu,r=Lu,Su<1e-6&&(e=Mu,n=Ou,r=Du),(i=e*e+n*n+r*r)<1e-12)?[NaN,NaN]:[bc(n,e)*gc,Oc(r/Ac(i))*gc]},nl=function(t){return function(){return t}},rl=function(t,e){function n(n,r){return n=t(n,r),e(n[0],n[1])}return t.invert&&e.invert&&(n.invert=function(n,r){return(n=e.invert(n,r))&&t.invert(n[0],n[1])}),n};function il(t,e){return[vc(t)>hc?t+Math.round(-t/pc)*pc:t,e]}function al(t,e,n){return(t%=pc)?e||n?rl(sl(t),cl(e,n)):sl(t):e||n?cl(e,n):il}function ol(t){return function(e,n){return[(e+=t)>hc?e-pc:e<-hc?e+pc:e,n]}}function sl(t){var e=ol(t);return e.invert=ol(-t),e}function cl(t,e){var n=xc(t),r=Tc(t),i=xc(e),a=Tc(e);function o(t,e){var o=xc(e),s=xc(t)*o,c=Tc(t)*o,u=Tc(e),l=u*n+s*r;return[bc(c*i-l*a,s*n-u*r),Oc(l*i+c*a)]}return o.invert=function(t,e){var o=xc(e),s=xc(t)*o,c=Tc(t)*o,u=Tc(e),l=u*i-c*a;return[bc(c*i+u*a,s*n+l*r),Oc(l*n-s*r)]},o}il.invert=il;var ul=function(t){function e(e){return(e=t(e[0]*yc,e[1]*yc))[0]*=gc,e[1]*=gc,e}return t=al(t[0]*yc,t[1]*yc,t.length>2?t[2]*yc:0),e.invert=function(e){return(e=t.invert(e[0]*yc,e[1]*yc))[0]*=gc,e[1]*=gc,e},e};function ll(t,e,n,r,i,a){if(n){var o=xc(e),s=Tc(e),c=r*n;null==i?(i=e+r*pc,a=e-c/2):(i=hl(o,i),a=hl(o,a),(r>0?ia)&&(i+=r*pc));for(var u,l=i;r>0?l>a:l1&&e.push(e.pop().concat(e.shift()))},result:function(){var n=e;return e=[],t=null,n}}},pl=function(t,e){return vc(t[0]-e[0])<1e-6&&vc(t[1]-e[1])<1e-6};function gl(t,e,n,r){this.x=t,this.z=e,this.o=n,this.e=r,this.v=!1,this.n=this.p=null}var yl=function(t,e,n,r,i){var a,o,s=[],c=[];if(t.forEach((function(t){if(!((e=t.length-1)<=0)){var e,n,r=t[0],o=t[e];if(pl(r,o)){for(i.lineStart(),a=0;a=0;--a)i.point((l=u[a])[0],l[1]);else r(f.x,f.p.x,-1,i);f=f.p}u=(f=f.o).z,d=!d}while(!f.v);i.lineEnd()}}};function vl(t){if(e=t.length){for(var e,n,r=0,i=t[0];++r=0?1:-1,T=E*w,C=T>hc,A=g*_;if(ml.add(bc(A*E*Tc(T),y*k+A*xc(T))),o+=C?w+E*pc:w,C^d>=n^b>=n){var S=eu(Qc(f),Qc(m));iu(S);var M=eu(a,S);iu(M);var O=(C^w>=0?-1:1)*Oc(M[2]);(r>O||r===O&&(S[0]||S[1]))&&(s+=C^w>=0?1:-1)}}return(o<-1e-6||o<1e-6&&ml<-1e-6)^1&s},_l=function(t,e,n,r){return function(i){var a,o,s,c=e(i),u=dl(),l=e(u),h=!1,f={point:d,lineStart:g,lineEnd:y,polygonStart:function(){f.point=v,f.lineStart=m,f.lineEnd=b,o=[],a=[]},polygonEnd:function(){f.point=d,f.lineStart=g,f.lineEnd=y,o=I(o);var t=xl(a,r);o.length?(h||(i.polygonStart(),h=!0),yl(o,wl,t,n,i)):t&&(h||(i.polygonStart(),h=!0),i.lineStart(),n(null,null,1,i),i.lineEnd()),h&&(i.polygonEnd(),h=!1),o=a=null},sphere:function(){i.polygonStart(),i.lineStart(),n(null,null,1,i),i.lineEnd(),i.polygonEnd()}};function d(e,n){t(e,n)&&i.point(e,n)}function p(t,e){c.point(t,e)}function g(){f.point=p,c.lineStart()}function y(){f.point=d,c.lineEnd()}function v(t,e){s.push([t,e]),l.point(t,e)}function m(){l.lineStart(),s=[]}function b(){v(s[0][0],s[0][1]),l.lineEnd();var t,e,n,r,c=l.clean(),f=u.result(),d=f.length;if(s.pop(),a.push(s),s=null,d)if(1&c){if((e=(n=f[0]).length-1)>0){for(h||(i.polygonStart(),h=!0),i.lineStart(),t=0;t1&&2&c&&f.push(f.pop().concat(f.shift())),o.push(f.filter(kl))}return f}};function kl(t){return t.length>1}function wl(t,e){return((t=t.x)[0]<0?t[1]-fc-1e-6:fc-t[1])-((e=e.x)[0]<0?e[1]-fc-1e-6:fc-e[1])}var El=_l((function(){return!0}),(function(t){var e,n=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),e=1},point:function(a,o){var s=a>0?hc:-hc,c=vc(a-n);vc(c-hc)<1e-6?(t.point(n,r=(r+o)/2>0?fc:-fc),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),t.point(a,r),e=0):i!==s&&c>=hc&&(vc(n-i)<1e-6&&(n-=1e-6*i),vc(a-s)<1e-6&&(a-=1e-6*s),r=function(t,e,n,r){var i,a,o=Tc(t-n);return vc(o)>1e-6?mc((Tc(e)*(a=xc(r))*Tc(n)-Tc(r)*(i=xc(e))*Tc(t))/(i*a*o)):(e+r)/2}(n,r,a,o),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),e=0),t.point(n=a,r=o),i=s},lineEnd:function(){t.lineEnd(),n=r=NaN},clean:function(){return 2-e}}}),(function(t,e,n,r){var i;if(null==t)i=n*fc,r.point(-hc,i),r.point(0,i),r.point(hc,i),r.point(hc,0),r.point(hc,-i),r.point(0,-i),r.point(-hc,-i),r.point(-hc,0),r.point(-hc,i);else if(vc(t[0]-e[0])>1e-6){var a=t[0]0,i=vc(e)>1e-6;function a(t,n){return xc(t)*xc(n)>e}function o(t,n,r){var i=[1,0,0],a=eu(Qc(t),Qc(n)),o=tu(a,a),s=a[0],c=o-s*s;if(!c)return!r&&t;var u=e*o/c,l=-e*s/c,h=eu(i,a),f=ru(i,u);nu(f,ru(a,l));var d=h,p=tu(f,d),g=tu(d,d),y=p*p-g*(tu(f,f)-1);if(!(y<0)){var v=Ac(y),m=ru(d,(-p-v)/g);if(nu(m,f),m=Kc(m),!r)return m;var b,x=t[0],_=n[0],k=t[1],w=n[1];_0^m[1]<(vc(m[0]-x)<1e-6?k:w):k<=m[1]&&m[1]<=w:E>hc^(x<=m[0]&&m[0]<=_)){var C=ru(d,(-p+v)/g);return nu(C,f),[m,Kc(C)]}}}function s(e,n){var i=r?t:hc-t,a=0;return e<-i?a|=1:e>i&&(a|=2),n<-i?a|=4:n>i&&(a|=8),a}return _l(a,(function(t){var e,n,c,u,l;return{lineStart:function(){u=c=!1,l=1},point:function(h,f){var d,p=[h,f],g=a(h,f),y=r?g?0:s(h,f):g?s(h+(h<0?hc:-hc),f):0;if(!e&&(u=c=g)&&t.lineStart(),g!==c&&(!(d=o(e,p))||pl(e,d)||pl(p,d))&&(p[0]+=1e-6,p[1]+=1e-6,g=a(p[0],p[1])),g!==c)l=0,g?(t.lineStart(),d=o(p,e),t.point(d[0],d[1])):(d=o(e,p),t.point(d[0],d[1]),t.lineEnd()),e=d;else if(i&&e&&r^g){var v;y&n||!(v=o(p,e,!0))||(l=0,r?(t.lineStart(),t.point(v[0][0],v[0][1]),t.point(v[1][0],v[1][1]),t.lineEnd()):(t.point(v[1][0],v[1][1]),t.lineEnd(),t.lineStart(),t.point(v[0][0],v[0][1])))}!g||e&&pl(e,p)||t.point(p[0],p[1]),e=p,c=g,n=y},lineEnd:function(){c&&t.lineEnd(),e=null},clean:function(){return l|(u&&c)<<1}}}),(function(e,r,i,a){ll(a,t,n,i,e,r)}),r?[0,-t]:[-hc,t-hc])};function Cl(t,e,n,r){function i(i,a){return t<=i&&i<=n&&e<=a&&a<=r}function a(i,a,s,u){var l=0,h=0;if(null==i||(l=o(i,s))!==(h=o(a,s))||c(i,a)<0^s>0)do{u.point(0===l||3===l?t:n,l>1?r:e)}while((l=(l+s+4)%4)!==h);else u.point(a[0],a[1])}function o(r,i){return vc(r[0]-t)<1e-6?i>0?0:3:vc(r[0]-n)<1e-6?i>0?2:1:vc(r[1]-e)<1e-6?i>0?1:0:i>0?3:2}function s(t,e){return c(t.x,e.x)}function c(t,e){var n=o(t,1),r=o(e,1);return n!==r?n-r:0===n?e[1]-t[1]:1===n?t[0]-e[0]:2===n?t[1]-e[1]:e[0]-t[0]}return function(o){var c,u,l,h,f,d,p,g,y,v,m,b=o,x=dl(),_={point:k,lineStart:function(){_.point=w,u&&u.push(l=[]);v=!0,y=!1,p=g=NaN},lineEnd:function(){c&&(w(h,f),d&&y&&x.rejoin(),c.push(x.result()));_.point=k,y&&b.lineEnd()},polygonStart:function(){b=x,c=[],u=[],m=!0},polygonEnd:function(){var e=function(){for(var e=0,n=0,i=u.length;nr&&(f-a)*(r-o)>(d-o)*(t-a)&&++e:d<=r&&(f-a)*(r-o)<(d-o)*(t-a)&&--e;return e}(),n=m&&e,i=(c=I(c)).length;(n||i)&&(o.polygonStart(),n&&(o.lineStart(),a(null,null,1,o),o.lineEnd()),i&&yl(c,s,e,a,o),o.polygonEnd());b=o,c=u=l=null}};function k(t,e){i(t,e)&&b.point(t,e)}function w(a,o){var s=i(a,o);if(u&&l.push([a,o]),v)h=a,f=o,d=s,v=!1,s&&(b.lineStart(),b.point(a,o));else if(s&&y)b.point(a,o);else{var c=[p=Math.max(-1e9,Math.min(1e9,p)),g=Math.max(-1e9,Math.min(1e9,g))],x=[a=Math.max(-1e9,Math.min(1e9,a)),o=Math.max(-1e9,Math.min(1e9,o))];!function(t,e,n,r,i,a){var o,s=t[0],c=t[1],u=0,l=1,h=e[0]-s,f=e[1]-c;if(o=n-s,h||!(o>0)){if(o/=h,h<0){if(o0){if(o>l)return;o>u&&(u=o)}if(o=i-s,h||!(o<0)){if(o/=h,h<0){if(o>l)return;o>u&&(u=o)}else if(h>0){if(o0)){if(o/=f,f<0){if(o0){if(o>l)return;o>u&&(u=o)}if(o=a-c,f||!(o<0)){if(o/=f,f<0){if(o>l)return;o>u&&(u=o)}else if(f>0){if(o0&&(t[0]=s+u*h,t[1]=c+u*f),l<1&&(e[0]=s+l*h,e[1]=c+l*f),!0}}}}}(c,x,t,e,n,r)?s&&(b.lineStart(),b.point(a,o),m=!1):(y||(b.lineStart(),b.point(c[0],c[1])),b.point(x[0],x[1]),s||b.lineEnd(),m=!1)}p=a,g=o,y=s}return _}}var Al,Sl,Ml,Ol=function(){var t,e,n,r=0,i=0,a=960,o=500;return n={stream:function(n){return t&&e===n?t:t=Cl(r,i,a,o)(e=n)},extent:function(s){return arguments.length?(r=+s[0][0],i=+s[0][1],a=+s[1][0],o=+s[1][1],t=e=null,n):[[r,i],[a,o]]}}},Dl=sc(),Nl={sphere:Nc,point:Nc,lineStart:function(){Nl.point=Ll,Nl.lineEnd=Bl},lineEnd:Nc,polygonStart:Nc,polygonEnd:Nc};function Bl(){Nl.point=Nl.lineEnd=Nc}function Ll(t,e){Al=t*=yc,Sl=Tc(e*=yc),Ml=xc(e),Nl.point=Fl}function Fl(t,e){t*=yc;var n=Tc(e*=yc),r=xc(e),i=vc(t-Al),a=xc(i),o=r*Tc(i),s=Ml*n-Sl*r*a,c=Sl*n+Ml*r*a;Dl.add(bc(Ac(o*o+s*s),c)),Al=t,Sl=n,Ml=r}var Pl=function(t){return Dl.reset(),$c(t,Nl),+Dl},Il=[null,null],jl={type:"LineString",coordinates:Il},Rl=function(t,e){return Il[0]=t,Il[1]=e,Pl(jl)},Yl={Feature:function(t,e){return Ul(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r0&&(i=Rl(t[a],t[a-1]))>0&&n<=i&&r<=i&&(n+r-i)*(1-Math.pow((n-r)/i,2))<1e-12*i)return!0;n=r}return!1}function Hl(t,e){return!!xl(t.map(Vl),Gl(e))}function Vl(t){return(t=t.map(Gl)).pop(),t}function Gl(t){return[t[0]*yc,t[1]*yc]}var ql=function(t,e){return(t&&Yl.hasOwnProperty(t.type)?Yl[t.type]:Ul)(t,e)};function Xl(t,e,n){var r=k(t,e-1e-6,n).concat(e);return function(t){return r.map((function(e){return[t,e]}))}}function Zl(t,e,n){var r=k(t,e-1e-6,n).concat(e);return function(t){return r.map((function(e){return[e,t]}))}}function Jl(){var t,e,n,r,i,a,o,s,c,u,l,h,f=10,d=f,p=90,g=360,y=2.5;function v(){return{type:"MultiLineString",coordinates:m()}}function m(){return k(_c(r/p)*p,n,p).map(l).concat(k(_c(s/g)*g,o,g).map(h)).concat(k(_c(e/f)*f,t,f).filter((function(t){return vc(t%p)>1e-6})).map(c)).concat(k(_c(a/d)*d,i,d).filter((function(t){return vc(t%g)>1e-6})).map(u))}return v.lines=function(){return m().map((function(t){return{type:"LineString",coordinates:t}}))},v.outline=function(){return{type:"Polygon",coordinates:[l(r).concat(h(o).slice(1),l(n).reverse().slice(1),h(s).reverse().slice(1))]}},v.extent=function(t){return arguments.length?v.extentMajor(t).extentMinor(t):v.extentMinor()},v.extentMajor=function(t){return arguments.length?(r=+t[0][0],n=+t[1][0],s=+t[0][1],o=+t[1][1],r>n&&(t=r,r=n,n=t),s>o&&(t=s,s=o,o=t),v.precision(y)):[[r,s],[n,o]]},v.extentMinor=function(n){return arguments.length?(e=+n[0][0],t=+n[1][0],a=+n[0][1],i=+n[1][1],e>t&&(n=e,e=t,t=n),a>i&&(n=a,a=i,i=n),v.precision(y)):[[e,a],[t,i]]},v.step=function(t){return arguments.length?v.stepMajor(t).stepMinor(t):v.stepMinor()},v.stepMajor=function(t){return arguments.length?(p=+t[0],g=+t[1],v):[p,g]},v.stepMinor=function(t){return arguments.length?(f=+t[0],d=+t[1],v):[f,d]},v.precision=function(f){return arguments.length?(y=+f,c=Xl(a,i,90),u=Zl(e,t,y),l=Xl(s,o,90),h=Zl(r,n,y),v):y},v.extentMajor([[-180,1e-6-90],[180,90-1e-6]]).extentMinor([[-180,-80-1e-6],[180,80+1e-6]])}function Kl(){return Jl()()}var Ql,th,eh,nh,rh=function(t,e){var n=t[0]*yc,r=t[1]*yc,i=e[0]*yc,a=e[1]*yc,o=xc(r),s=Tc(r),c=xc(a),u=Tc(a),l=o*xc(n),h=o*Tc(n),f=c*xc(i),d=c*Tc(i),p=2*Oc(Ac(Dc(a-r)+o*c*Dc(i-n))),g=Tc(p),y=p?function(t){var e=Tc(t*=p)/g,n=Tc(p-t)/g,r=n*l+e*f,i=n*h+e*d,a=n*s+e*u;return[bc(i,r)*gc,bc(a,Ac(r*r+i*i))*gc]}:function(){return[n*gc,r*gc]};return y.distance=p,y},ih=function(t){return t},ah=sc(),oh=sc(),sh={point:Nc,lineStart:Nc,lineEnd:Nc,polygonStart:function(){sh.lineStart=ch,sh.lineEnd=hh},polygonEnd:function(){sh.lineStart=sh.lineEnd=sh.point=Nc,ah.add(vc(oh)),oh.reset()},result:function(){var t=ah/2;return ah.reset(),t}};function ch(){sh.point=uh}function uh(t,e){sh.point=lh,Ql=eh=t,th=nh=e}function lh(t,e){oh.add(nh*t-eh*e),eh=t,nh=e}function hh(){lh(Ql,th)}var fh=sh,dh=1/0,ph=dh,gh=-dh,yh=gh;var vh,mh,bh,xh,_h={point:function(t,e){tgh&&(gh=t);eyh&&(yh=e)},lineStart:Nc,lineEnd:Nc,polygonStart:Nc,polygonEnd:Nc,result:function(){var t=[[dh,ph],[gh,yh]];return gh=yh=-(ph=dh=1/0),t}},kh=0,wh=0,Eh=0,Th=0,Ch=0,Ah=0,Sh=0,Mh=0,Oh=0,Dh={point:Nh,lineStart:Bh,lineEnd:Ph,polygonStart:function(){Dh.lineStart=Ih,Dh.lineEnd=jh},polygonEnd:function(){Dh.point=Nh,Dh.lineStart=Bh,Dh.lineEnd=Ph},result:function(){var t=Oh?[Sh/Oh,Mh/Oh]:Ah?[Th/Ah,Ch/Ah]:Eh?[kh/Eh,wh/Eh]:[NaN,NaN];return kh=wh=Eh=Th=Ch=Ah=Sh=Mh=Oh=0,t}};function Nh(t,e){kh+=t,wh+=e,++Eh}function Bh(){Dh.point=Lh}function Lh(t,e){Dh.point=Fh,Nh(bh=t,xh=e)}function Fh(t,e){var n=t-bh,r=e-xh,i=Ac(n*n+r*r);Th+=i*(bh+t)/2,Ch+=i*(xh+e)/2,Ah+=i,Nh(bh=t,xh=e)}function Ph(){Dh.point=Nh}function Ih(){Dh.point=Rh}function jh(){Yh(vh,mh)}function Rh(t,e){Dh.point=Yh,Nh(vh=bh=t,mh=xh=e)}function Yh(t,e){var n=t-bh,r=e-xh,i=Ac(n*n+r*r);Th+=i*(bh+t)/2,Ch+=i*(xh+e)/2,Ah+=i,Sh+=(i=xh*t-bh*e)*(bh+t),Mh+=i*(xh+e),Oh+=3*i,Nh(bh=t,xh=e)}var zh=Dh;function Uh(t){this._context=t}Uh.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._context.moveTo(t,e),this._point=1;break;case 1:this._context.lineTo(t,e);break;default:this._context.moveTo(t+this._radius,e),this._context.arc(t,e,this._radius,0,pc)}},result:Nc};var $h,Wh,Hh,Vh,Gh,qh=sc(),Xh={point:Nc,lineStart:function(){Xh.point=Zh},lineEnd:function(){$h&&Jh(Wh,Hh),Xh.point=Nc},polygonStart:function(){$h=!0},polygonEnd:function(){$h=null},result:function(){var t=+qh;return qh.reset(),t}};function Zh(t,e){Xh.point=Jh,Wh=Vh=t,Hh=Gh=e}function Jh(t,e){Vh-=t,Gh-=e,qh.add(Ac(Vh*Vh+Gh*Gh)),Vh=t,Gh=e}var Kh=Xh;function Qh(){this._string=[]}function tf(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}Qh.prototype={_radius:4.5,_circle:tf(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._string.push("M",t,",",e),this._point=1;break;case 1:this._string.push("L",t,",",e);break;default:null==this._circle&&(this._circle=tf(this._radius)),this._string.push("M",t,",",e,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}};var ef=function(t,e){var n,r,i=4.5;function a(t){return t&&("function"==typeof i&&r.pointRadius(+i.apply(this,arguments)),$c(t,n(r))),r.result()}return a.area=function(t){return $c(t,n(fh)),fh.result()},a.measure=function(t){return $c(t,n(Kh)),Kh.result()},a.bounds=function(t){return $c(t,n(_h)),_h.result()},a.centroid=function(t){return $c(t,n(zh)),zh.result()},a.projection=function(e){return arguments.length?(n=null==e?(t=null,ih):(t=e).stream,a):t},a.context=function(t){return arguments.length?(r=null==t?(e=null,new Qh):new Uh(e=t),"function"!=typeof i&&r.pointRadius(i),a):e},a.pointRadius=function(t){return arguments.length?(i="function"==typeof t?t:(r.pointRadius(+t),+t),a):i},a.projection(t).context(e)},nf=function(t){return{stream:rf(t)}};function rf(t){return function(e){var n=new af;for(var r in t)n[r]=t[r];return n.stream=e,n}}function af(){}function of(t,e,n){var r=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),null!=r&&t.clipExtent(null),$c(n,t.stream(_h)),e(_h.result()),null!=r&&t.clipExtent(r),t}function sf(t,e,n){return of(t,(function(n){var r=e[1][0]-e[0][0],i=e[1][1]-e[0][1],a=Math.min(r/(n[1][0]-n[0][0]),i/(n[1][1]-n[0][1])),o=+e[0][0]+(r-a*(n[1][0]+n[0][0]))/2,s=+e[0][1]+(i-a*(n[1][1]+n[0][1]))/2;t.scale(150*a).translate([o,s])}),n)}function cf(t,e,n){return sf(t,[[0,0],e],n)}function uf(t,e,n){return of(t,(function(n){var r=+e,i=r/(n[1][0]-n[0][0]),a=(r-i*(n[1][0]+n[0][0]))/2,o=-i*n[0][1];t.scale(150*i).translate([a,o])}),n)}function lf(t,e,n){return of(t,(function(n){var r=+e,i=r/(n[1][1]-n[0][1]),a=-i*n[0][0],o=(r-i*(n[1][1]+n[0][1]))/2;t.scale(150*i).translate([a,o])}),n)}af.prototype={constructor:af,point:function(t,e){this.stream.point(t,e)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var hf=xc(30*yc),ff=function(t,e){return+e?function(t,e){function n(r,i,a,o,s,c,u,l,h,f,d,p,g,y){var v=u-r,m=l-i,b=v*v+m*m;if(b>4*e&&g--){var x=o+f,_=s+d,k=c+p,w=Ac(x*x+_*_+k*k),E=Oc(k/=w),T=vc(vc(k)-1)<1e-6||vc(a-h)<1e-6?(a+h)/2:bc(_,x),C=t(T,E),A=C[0],S=C[1],M=A-r,O=S-i,D=m*M-v*O;(D*D/b>e||vc((v*M+m*O)/b-.5)>.3||o*f+s*d+c*p2?t[2]%360*yc:0,A()):[y*gc,v*gc,m*gc]},T.angle=function(t){return arguments.length?(b=t%360*yc,A()):b*gc},T.precision=function(t){return arguments.length?(o=ff(s,E=t*t),S()):Ac(E)},T.fitExtent=function(t,e){return sf(T,t,e)},T.fitSize=function(t,e){return cf(T,t,e)},T.fitWidth=function(t,e){return uf(T,t,e)},T.fitHeight=function(t,e){return lf(T,t,e)},function(){return e=t.apply(this,arguments),T.invert=e.invert&&C,A()}}function mf(t){var e=0,n=hc/3,r=vf(t),i=r(e,n);return i.parallels=function(t){return arguments.length?r(e=t[0]*yc,n=t[1]*yc):[e*gc,n*gc]},i}function bf(t,e){var n=Tc(t),r=(n+Tc(e))/2;if(vc(r)<1e-6)return function(t){var e=xc(t);function n(t,n){return[t*e,Tc(n)/e]}return n.invert=function(t,n){return[t/e,Oc(n*e)]},n}(t);var i=1+n*(2*r-n),a=Ac(i)/r;function o(t,e){var n=Ac(i-2*r*Tc(e))/r;return[n*Tc(t*=r),a-n*xc(t)]}return o.invert=function(t,e){var n=a-e;return[bc(t,vc(n))/r*Cc(n),Oc((i-(t*t+n*n)*r*r)/(2*r))]},o}var xf=function(){return mf(bf).scale(155.424).center([0,33.6442])},_f=function(){return xf().parallels([29.5,45.5]).scale(1070).translate([480,250]).rotate([96,0]).center([-.6,38.7])};var kf=function(){var t,e,n,r,i,a,o=_f(),s=xf().rotate([154,0]).center([-2,58.5]).parallels([55,65]),c=xf().rotate([157,0]).center([-3,19.9]).parallels([8,18]),u={point:function(t,e){a=[t,e]}};function l(t){var e=t[0],o=t[1];return a=null,n.point(e,o),a||(r.point(e,o),a)||(i.point(e,o),a)}function h(){return t=e=null,l}return l.invert=function(t){var e=o.scale(),n=o.translate(),r=(t[0]-n[0])/e,i=(t[1]-n[1])/e;return(i>=.12&&i<.234&&r>=-.425&&r<-.214?s:i>=.166&&i<.234&&r>=-.214&&r<-.115?c:o).invert(t)},l.stream=function(n){return t&&e===n?t:(r=[o.stream(e=n),s.stream(n),c.stream(n)],i=r.length,t={point:function(t,e){for(var n=-1;++n0?e<1e-6-fc&&(e=1e-6-fc):e>fc-1e-6&&(e=fc-1e-6);var n=i/Ec(Nf(e),r);return[n*Tc(r*t),i-n*xc(r*t)]}return a.invert=function(t,e){var n=i-e,a=Cc(r)*Ac(t*t+n*n);return[bc(t,vc(n))/r*Cc(n),2*mc(Ec(i/a,1/r))-fc]},a}var Lf=function(){return mf(Bf).scale(109.5).parallels([30,30])};function Ff(t,e){return[t,e]}Ff.invert=Ff;var Pf=function(){return yf(Ff).scale(152.63)};function If(t,e){var n=xc(t),r=t===e?Tc(t):(n-xc(e))/(e-t),i=n/r+t;if(vc(r)<1e-6)return Ff;function a(t,e){var n=i-e,a=r*t;return[n*Tc(a),i-n*xc(a)]}return a.invert=function(t,e){var n=i-e;return[bc(t,vc(n))/r*Cc(n),i-Cc(r)*Ac(t*t+n*n)]},a}var jf=function(){return mf(If).scale(131.154).center([0,13.9389])},Rf=1.340264,Yf=-.081106,zf=893e-6,Uf=.003796,$f=Ac(3)/2;function Wf(t,e){var n=Oc($f*Tc(e)),r=n*n,i=r*r*r;return[t*xc(n)/($f*(Rf+3*Yf*r+i*(7*zf+9*Uf*r))),n*(Rf+Yf*r+i*(zf+Uf*r))]}Wf.invert=function(t,e){for(var n,r=e,i=r*r,a=i*i*i,o=0;o<12&&(a=(i=(r-=n=(r*(Rf+Yf*i+a*(zf+Uf*i))-e)/(Rf+3*Yf*i+a*(7*zf+9*Uf*i)))*r)*i*i,!(vc(n)<1e-12));++o);return[$f*t*(Rf+3*Yf*i+a*(7*zf+9*Uf*i))/xc(r),Oc(Tc(r)/$f)]};var Hf=function(){return yf(Wf).scale(177.158)};function Vf(t,e){var n=xc(e),r=xc(t)*n;return[n*Tc(t)/r,Tc(e)/r]}Vf.invert=Ef(mc);var Gf=function(){return yf(Vf).scale(144.049).clipAngle(60)};function qf(t,e,n,r){return 1===t&&1===e&&0===n&&0===r?ih:rf({point:function(i,a){this.stream.point(i*t+n,a*e+r)}})}var Xf=function(){var t,e,n,r,i,a,o=1,s=0,c=0,u=1,l=1,h=ih,f=null,d=ih;function p(){return r=i=null,a}return a={stream:function(t){return r&&i===t?r:r=h(d(i=t))},postclip:function(r){return arguments.length?(d=r,f=t=e=n=null,p()):d},clipExtent:function(r){return arguments.length?(d=null==r?(f=t=e=n=null,ih):Cl(f=+r[0][0],t=+r[0][1],e=+r[1][0],n=+r[1][1]),p()):null==f?null:[[f,t],[e,n]]},scale:function(t){return arguments.length?(h=qf((o=+t)*u,o*l,s,c),p()):o},translate:function(t){return arguments.length?(h=qf(o*u,o*l,s=+t[0],c=+t[1]),p()):[s,c]},reflectX:function(t){return arguments.length?(h=qf(o*(u=t?-1:1),o*l,s,c),p()):u<0},reflectY:function(t){return arguments.length?(h=qf(o*u,o*(l=t?-1:1),s,c),p()):l<0},fitExtent:function(t,e){return sf(a,t,e)},fitSize:function(t,e){return cf(a,t,e)},fitWidth:function(t,e){return uf(a,t,e)},fitHeight:function(t,e){return lf(a,t,e)}}};function Zf(t,e){var n=e*e,r=n*n;return[t*(.8707-.131979*n+r*(r*(.003971*n-.001529*r)-.013791)),e*(1.007226+n*(.015085+r*(.028874*n-.044475-.005916*r)))]}Zf.invert=function(t,e){var n,r=e,i=25;do{var a=r*r,o=a*a;r-=n=(r*(1.007226+a*(.015085+o*(.028874*a-.044475-.005916*o)))-e)/(1.007226+a*(.045255+o*(.259866*a-.311325-.005916*11*o)))}while(vc(n)>1e-6&&--i>0);return[t/(.8707+(a=r*r)*(a*(a*a*a*(.003971-.001529*a)-.013791)-.131979)),r]};var Jf=function(){return yf(Zf).scale(175.295)};function Kf(t,e){return[xc(e)*Tc(t),Tc(e)]}Kf.invert=Ef(Oc);var Qf=function(){return yf(Kf).scale(249.5).clipAngle(90+1e-6)};function td(t,e){var n=xc(e),r=1+xc(t)*n;return[n*Tc(t)/r,Tc(e)/r]}td.invert=Ef((function(t){return 2*mc(t)}));var ed=function(){return yf(td).scale(250).clipAngle(142)};function nd(t,e){return[wc(Sc((fc+e)/2)),-t]}nd.invert=function(t,e){return[-e,2*mc(kc(t))-fc]};var rd=function(){var t=Df(nd),e=t.center,n=t.rotate;return t.center=function(t){return arguments.length?e([-t[1],t[0]]):[(t=e())[1],-t[0]]},t.rotate=function(t){return arguments.length?n([t[0],t[1],t.length>2?t[2]+90:90]):[(t=n())[0],t[1],t[2]-90]},n([0,0,90]).scale(159.155)};function id(t,e){return t.parent===e.parent?1:2}function ad(t,e){return t+e.x}function od(t,e){return Math.max(t,e.y)}var sd=function(){var t=id,e=1,n=1,r=!1;function i(i){var a,o=0;i.eachAfter((function(e){var n=e.children;n?(e.x=function(t){return t.reduce(ad,0)/t.length}(n),e.y=function(t){return 1+t.reduce(od,0)}(n)):(e.x=a?o+=t(e,a):0,e.y=0,a=e)}));var s=function(t){for(var e;e=t.children;)t=e[0];return t}(i),c=function(t){for(var e;e=t.children;)t=e[e.length-1];return t}(i),u=s.x-t(s,c)/2,l=c.x+t(c,s)/2;return i.eachAfter(r?function(t){t.x=(t.x-i.x)*e,t.y=(i.y-t.y)*n}:function(t){t.x=(t.x-u)/(l-u)*e,t.y=(1-(i.y?t.y/i.y:1))*n})}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i};function cd(t){var e=0,n=t.children,r=n&&n.length;if(r)for(;--r>=0;)e+=n[r].value;else e=1;t.value=e}function ud(t,e){var n,r,i,a,o,s=new dd(t),c=+t.value&&(s.value=t.value),u=[s];for(null==e&&(e=ld);n=u.pop();)if(c&&(n.value=+n.data.value),(i=e(n.data))&&(o=i.length))for(n.children=new Array(o),a=o-1;a>=0;--a)u.push(r=n.children[a]=new dd(i[a])),r.parent=n,r.depth=n.depth+1;return s.eachBefore(fd)}function ld(t){return t.children}function hd(t){t.data=t.data.data}function fd(t){var e=0;do{t.height=e}while((t=t.parent)&&t.height<++e)}function dd(t){this.data=t,this.depth=this.height=0,this.parent=null}dd.prototype=ud.prototype={constructor:dd,count:function(){return this.eachAfter(cd)},each:function(t){var e,n,r,i,a=this,o=[a];do{for(e=o.reverse(),o=[];a=e.pop();)if(t(a),n=a.children)for(r=0,i=n.length;r=0;--n)i.push(e[n]);return this},sum:function(t){return this.eachAfter((function(e){for(var n=+t(e.data)||0,r=e.children,i=r&&r.length;--i>=0;)n+=r[i].value;e.value=n}))},sort:function(t){return this.eachBefore((function(e){e.children&&e.children.sort(t)}))},path:function(t){for(var e=this,n=function(t,e){if(t===e)return t;var n=t.ancestors(),r=e.ancestors(),i=null;t=n.pop(),e=r.pop();for(;t===e;)i=t,t=n.pop(),e=r.pop();return i}(e,t),r=[e];e!==n;)e=e.parent,r.push(e);for(var i=r.length;t!==n;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e},descendants:function(){var t=[];return this.each((function(e){t.push(e)})),t},leaves:function(){var t=[];return this.eachBefore((function(e){e.children||t.push(e)})),t},links:function(){var t=this,e=[];return t.each((function(n){n!==t&&e.push({source:n.parent,target:n})})),e},copy:function(){return ud(this).eachBefore(hd)}};var pd=Array.prototype.slice;var gd=function(t){for(var e,n,r=0,i=(t=function(t){for(var e,n,r=t.length;r;)n=Math.random()*r--|0,e=t[r],t[r]=t[n],t[n]=e;return t}(pd.call(t))).length,a=[];r0&&n*n>r*r+i*i}function bd(t,e){for(var n=0;n(o*=o)?(r=(u+o-i)/(2*u),a=Math.sqrt(Math.max(0,o/u-r*r)),n.x=t.x-r*s-a*c,n.y=t.y-r*c+a*s):(r=(u+i-o)/(2*u),a=Math.sqrt(Math.max(0,i/u-r*r)),n.x=e.x+r*s-a*c,n.y=e.y+r*c+a*s)):(n.x=e.x+n.r,n.y=e.y)}function Ed(t,e){var n=t.r+e.r-1e-6,r=e.x-t.x,i=e.y-t.y;return n>0&&n*n>r*r+i*i}function Td(t){var e=t._,n=t.next._,r=e.r+n.r,i=(e.x*n.r+n.x*e.r)/r,a=(e.y*n.r+n.y*e.r)/r;return i*i+a*a}function Cd(t){this._=t,this.next=null,this.previous=null}function Ad(t){if(!(i=t.length))return 0;var e,n,r,i,a,o,s,c,u,l,h;if((e=t[0]).x=0,e.y=0,!(i>1))return e.r;if(n=t[1],e.x=-n.r,n.x=e.r,n.y=0,!(i>2))return e.r+n.r;wd(n,e,r=t[2]),e=new Cd(e),n=new Cd(n),r=new Cd(r),e.next=r.previous=n,n.next=e.previous=r,r.next=n.previous=e;t:for(s=3;s0)throw new Error("cycle");return a}return n.id=function(e){return arguments.length?(t=Od(e),n):t},n.parentId=function(t){return arguments.length?(e=Od(t),n):e},n};function Vd(t,e){return t.parent===e.parent?1:2}function Gd(t){var e=t.children;return e?e[0]:t.t}function qd(t){var e=t.children;return e?e[e.length-1]:t.t}function Xd(t,e,n){var r=n/(e.i-t.i);e.c-=r,e.s+=n,t.c+=r,e.z+=n,e.m+=n}function Zd(t,e,n){return t.a.parent===e.parent?t.a:n}function Jd(t,e){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=e}Jd.prototype=Object.create(dd.prototype);var Kd=function(){var t=Vd,e=1,n=1,r=null;function i(i){var c=function(t){for(var e,n,r,i,a,o=new Jd(t,0),s=[o];e=s.pop();)if(r=e._.children)for(e.children=new Array(a=r.length),i=a-1;i>=0;--i)s.push(n=e.children[i]=new Jd(r[i],i)),n.parent=e;return(o.parent=new Jd(null,0)).children=[o],o}(i);if(c.eachAfter(a),c.parent.m=-c.z,c.eachBefore(o),r)i.eachBefore(s);else{var u=i,l=i,h=i;i.eachBefore((function(t){t.xl.x&&(l=t),t.depth>h.depth&&(h=t)}));var f=u===l?1:t(u,l)/2,d=f-u.x,p=e/(l.x+f+d),g=n/(h.depth||1);i.eachBefore((function(t){t.x=(t.x+d)*p,t.y=t.depth*g}))}return i}function a(e){var n=e.children,r=e.parent.children,i=e.i?r[e.i-1]:null;if(n){!function(t){for(var e,n=0,r=0,i=t.children,a=i.length;--a>=0;)(e=i[a]).z+=n,e.m+=n,n+=e.s+(r+=e.c)}(e);var a=(n[0].z+n[n.length-1].z)/2;i?(e.z=i.z+t(e._,i._),e.m=e.z-a):e.z=a}else i&&(e.z=i.z+t(e._,i._));e.parent.A=function(e,n,r){if(n){for(var i,a=e,o=e,s=n,c=a.parent.children[0],u=a.m,l=o.m,h=s.m,f=c.m;s=qd(s),a=Gd(a),s&&a;)c=Gd(c),(o=qd(o)).a=e,(i=s.z+h-a.z-u+t(s._,a._))>0&&(Xd(Zd(s,e,r),e,i),u+=i,l+=i),h+=s.m,u+=a.m,f+=c.m,l+=o.m;s&&!qd(o)&&(o.t=s,o.m+=h-l),a&&!Gd(c)&&(c.t=a,c.m+=u-f,r=e)}return r}(e,i,e.parent.A||r[0])}function o(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function s(t){t.x*=e,t.y=t.depth*n}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i},Qd=function(t,e,n,r,i){for(var a,o=t.children,s=-1,c=o.length,u=t.value&&(i-n)/t.value;++sf&&(f=s),y=l*l*g,(d=Math.max(f/y,y/h))>p){l-=s;break}p=d}v.push(o={value:l,dice:c1?e:1)},n}(tp),rp=function(){var t=np,e=!1,n=1,r=1,i=[0],a=Dd,o=Dd,s=Dd,c=Dd,u=Dd;function l(t){return t.x0=t.y0=0,t.x1=n,t.y1=r,t.eachBefore(h),i=[0],e&&t.eachBefore(jd),t}function h(e){var n=i[e.depth],r=e.x0+n,l=e.y0+n,h=e.x1-n,f=e.y1-n;h=n-1){var l=s[e];return l.x0=i,l.y0=a,l.x1=o,void(l.y1=c)}var h=u[e],f=r/2+h,d=e+1,p=n-1;for(;d>>1;u[g]c-a){var m=(i*v+o*y)/r;t(e,d,y,i,a,m,c),t(d,n,v,m,a,o,c)}else{var b=(a*v+c*y)/r;t(e,d,y,i,a,o,b),t(d,n,v,i,b,o,c)}}(0,c,t.value,e,n,r,i)},ap=function(t,e,n,r,i){(1&t.depth?Qd:Rd)(t,e,n,r,i)},op=function t(e){function n(t,n,r,i,a){if((o=t._squarify)&&o.ratio===e)for(var o,s,c,u,l,h=-1,f=o.length,d=t.value;++h1?e:1)},n}(tp),sp=function(t){var e=t.length;return function(n){return t[Math.max(0,Math.min(e-1,Math.floor(n*e)))]}},cp=function(t,e){var n=un(+t,+e);return function(t){var e=n(t);return e-360*Math.floor(e/360)}},up=function(t,e){return t=+t,e=+e,function(n){return Math.round(t*(1-n)+e*n)}},lp=Math.SQRT2;function hp(t){return((t=Math.exp(t))+1/t)/2}var fp=function(t,e){var n,r,i=t[0],a=t[1],o=t[2],s=e[0],c=e[1],u=e[2],l=s-i,h=c-a,f=l*l+h*h;if(f<1e-12)r=Math.log(u/o)/lp,n=function(t){return[i+t*l,a+t*h,o*Math.exp(lp*t*r)]};else{var d=Math.sqrt(f),p=(u*u-o*o+4*f)/(2*o*2*d),g=(u*u-o*o-4*f)/(2*u*2*d),y=Math.log(Math.sqrt(p*p+1)-p),v=Math.log(Math.sqrt(g*g+1)-g);r=(v-y)/lp,n=function(t){var e,n=t*r,s=hp(y),c=o/(2*d)*(s*(e=lp*n+y,((e=Math.exp(2*e))-1)/(e+1))-function(t){return((t=Math.exp(t))-1/t)/2}(y));return[i+c*l,a+c*h,o*s/hp(lp*n+y)]}}return n.duration=1e3*r,n};function dp(t){return function(e,n){var r=t((e=tn(e)).h,(n=tn(n)).h),i=hn(e.s,n.s),a=hn(e.l,n.l),o=hn(e.opacity,n.opacity);return function(t){return e.h=r(t),e.s=i(t),e.l=a(t),e.opacity=o(t),e+""}}}var pp=dp(un),gp=dp(hn);function yp(t,e){var n=hn((t=pa(t)).l,(e=pa(e)).l),r=hn(t.a,e.a),i=hn(t.b,e.b),a=hn(t.opacity,e.opacity);return function(e){return t.l=n(e),t.a=r(e),t.b=i(e),t.opacity=a(e),t+""}}function vp(t){return function(e,n){var r=t((e=ka(e)).h,(n=ka(n)).h),i=hn(e.c,n.c),a=hn(e.l,n.l),o=hn(e.opacity,n.opacity);return function(t){return e.h=r(t),e.c=i(t),e.l=a(t),e.opacity=o(t),e+""}}}var mp=vp(un),bp=vp(hn);function xp(t){return function e(n){function r(e,r){var i=t((e=Oa(e)).h,(r=Oa(r)).h),a=hn(e.s,r.s),o=hn(e.l,r.l),s=hn(e.opacity,r.opacity);return function(t){return e.h=i(t),e.s=a(t),e.l=o(Math.pow(t,n)),e.opacity=s(t),e+""}}return n=+n,r.gamma=e,r}(1)}var _p=xp(un),kp=xp(hn);function wp(t,e){for(var n=0,r=e.length-1,i=e[0],a=new Array(r<0?0:r);n1&&(e=t[a[o-2]],n=t[a[o-1]],r=t[s],(n[0]-e[0])*(r[1]-e[1])-(n[1]-e[1])*(r[0]-e[0])<=0);)--o;a[o++]=s}return a.slice(0,o)}var Mp=function(t){if((n=t.length)<3)return null;var e,n,r=new Array(n),i=new Array(n);for(e=0;e=0;--e)u.push(t[r[a[e]][2]]);for(e=+s;es!=u>s&&o<(c-n)*(s-r)/(u-r)+n&&(l=!l),c=n,u=r;return l},Dp=function(t){for(var e,n,r=-1,i=t.length,a=t[i-1],o=a[0],s=a[1],c=0;++r1);return t+n*a*Math.sqrt(-2*Math.log(i)/i)}}return n.source=t,n}(Np),Fp=function t(e){function n(){var t=Lp.source(e).apply(this,arguments);return function(){return Math.exp(t())}}return n.source=t,n}(Np),Pp=function t(e){function n(t){return function(){for(var n=0,r=0;rr&&(e=n,n=r,r=e),function(t){return Math.max(n,Math.min(r,t))}}function tg(t,e,n){var r=t[0],i=t[1],a=e[0],o=e[1];return i2?eg:tg,i=a=null,h}function h(e){return isNaN(e=+e)?n:(i||(i=r(o.map(t),s,c)))(t(u(e)))}return h.invert=function(n){return u(e((a||(a=r(s,o.map(t),_n)))(n)))},h.domain=function(t){return arguments.length?(o=Up.call(t,Xp),u===Jp||(u=Qp(o)),l()):o.slice()},h.range=function(t){return arguments.length?(s=$p.call(t),l()):s.slice()},h.rangeRound=function(t){return s=$p.call(t),c=up,l()},h.clamp=function(t){return arguments.length?(u=t?Qp(o):Jp,h):u!==Jp},h.interpolate=function(t){return arguments.length?(c=t,l()):c},h.unknown=function(t){return arguments.length?(n=t,h):n},function(n,r){return t=n,e=r,l()}}function ig(t,e){return rg()(t,e)}var ag=function(t,e,n,r){var i,a=S(t,e,n);switch((r=Hs(null==r?",f":r)).type){case"s":var o=Math.max(Math.abs(t),Math.abs(e));return null!=r.precision||isNaN(i=ac(a,o))||(r.precision=i),Zs(r,o);case"":case"e":case"g":case"p":case"r":null!=r.precision||isNaN(i=oc(a,Math.max(Math.abs(t),Math.abs(e))))||(r.precision=i-("e"===r.type));break;case"f":case"%":null!=r.precision||isNaN(i=ic(a))||(r.precision=i-2*("%"===r.type))}return Xs(r)};function og(t){var e=t.domain;return t.ticks=function(t){var n=e();return C(n[0],n[n.length-1],null==t?10:t)},t.tickFormat=function(t,n){var r=e();return ag(r[0],r[r.length-1],null==t?10:t,n)},t.nice=function(n){null==n&&(n=10);var r,i=e(),a=0,o=i.length-1,s=i[a],c=i[o];return c0?r=A(s=Math.floor(s/r)*r,c=Math.ceil(c/r)*r,n):r<0&&(r=A(s=Math.ceil(s*r)/r,c=Math.floor(c*r)/r,n)),r>0?(i[a]=Math.floor(s/r)*r,i[o]=Math.ceil(c/r)*r,e(i)):r<0&&(i[a]=Math.ceil(s*r)/r,i[o]=Math.floor(c*r)/r,e(i)),t},t}function sg(){var t=ig(Jp,Jp);return t.copy=function(){return ng(t,sg())},Rp.apply(t,arguments),og(t)}function cg(t){var e;function n(t){return isNaN(t=+t)?e:t}return n.invert=n,n.domain=n.range=function(e){return arguments.length?(t=Up.call(e,Xp),n):t.slice()},n.unknown=function(t){return arguments.length?(e=t,n):e},n.copy=function(){return cg(t).unknown(e)},t=arguments.length?Up.call(t,Xp):[0,1],og(n)}var ug=function(t,e){var n,r=0,i=(t=t.slice()).length-1,a=t[r],o=t[i];return o0){for(;fc)break;g.push(h)}}else for(;f=1;--l)if(!((h=u*l)c)break;g.push(h)}}else g=C(f,d,Math.min(d-f,p)).map(n);return r?g.reverse():g},r.tickFormat=function(t,i){if(null==i&&(i=10===a?".0e":","),"function"!=typeof i&&(i=Xs(i)),t===1/0)return i;null==t&&(t=10);var o=Math.max(1,a*t/r.ticks().length);return function(t){var r=t/n(Math.round(e(t)));return r*a0?i[r-1]:e[0],r=r?[i[r-1],n]:[i[o-1],i[o]]},o.unknown=function(e){return arguments.length?(t=e,o):o},o.thresholds=function(){return i.slice()},o.copy=function(){return Mg().domain([e,n]).range(a).unknown(t)},Rp.apply(og(o),arguments)}function Og(){var t,e=[.5],n=[0,1],r=1;function i(i){return i<=i?n[c(e,i,0,r)]:t}return i.domain=function(t){return arguments.length?(e=$p.call(t),r=Math.min(e.length,n.length-1),i):e.slice()},i.range=function(t){return arguments.length?(n=$p.call(t),r=Math.min(e.length,n.length-1),i):n.slice()},i.invertExtent=function(t){var r=n.indexOf(t);return[e[r-1],e[r]]},i.unknown=function(e){return arguments.length?(t=e,i):t},i.copy=function(){return Og().domain(e).range(n).unknown(t)},Rp.apply(i,arguments)}var Dg=new Date,Ng=new Date;function Bg(t,e,n,r){function i(e){return t(e=0===arguments.length?new Date:new Date(+e)),e}return i.floor=function(e){return t(e=new Date(+e)),e},i.ceil=function(n){return t(n=new Date(n-1)),e(n,1),t(n),n},i.round=function(t){var e=i(t),n=i.ceil(t);return t-e0))return s;do{s.push(o=new Date(+n)),e(n,a),t(n)}while(o=e)for(;t(e),!n(e);)e.setTime(e-1)}),(function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;e(t,-1),!n(t););else for(;--r>=0;)for(;e(t,1),!n(t););}))},n&&(i.count=function(e,r){return Dg.setTime(+e),Ng.setTime(+r),t(Dg),t(Ng),Math.floor(n(Dg,Ng))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(e){return r(e)%t==0}:function(e){return i.count(0,e)%t==0}):i:null}),i}var Lg=Bg((function(t){t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,e){t.setFullYear(t.getFullYear()+e)}),(function(t,e){return e.getFullYear()-t.getFullYear()}),(function(t){return t.getFullYear()}));Lg.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Bg((function(e){e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)}),(function(e,n){e.setFullYear(e.getFullYear()+n*t)})):null};var Fg=Lg,Pg=Lg.range,Ig=Bg((function(t){t.setDate(1),t.setHours(0,0,0,0)}),(function(t,e){t.setMonth(t.getMonth()+e)}),(function(t,e){return e.getMonth()-t.getMonth()+12*(e.getFullYear()-t.getFullYear())}),(function(t){return t.getMonth()})),jg=Ig,Rg=Ig.range;function Yg(t){return Bg((function(e){e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+7*e)}),(function(t,e){return(e-t-6e4*(e.getTimezoneOffset()-t.getTimezoneOffset()))/6048e5}))}var zg=Yg(0),Ug=Yg(1),$g=Yg(2),Wg=Yg(3),Hg=Yg(4),Vg=Yg(5),Gg=Yg(6),qg=zg.range,Xg=Ug.range,Zg=$g.range,Jg=Wg.range,Kg=Hg.range,Qg=Vg.range,ty=Gg.range,ey=Bg((function(t){t.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+e)}),(function(t,e){return(e-t-6e4*(e.getTimezoneOffset()-t.getTimezoneOffset()))/864e5}),(function(t){return t.getDate()-1})),ny=ey,ry=ey.range,iy=Bg((function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds()-6e4*t.getMinutes())}),(function(t,e){t.setTime(+t+36e5*e)}),(function(t,e){return(e-t)/36e5}),(function(t){return t.getHours()})),ay=iy,oy=iy.range,sy=Bg((function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds())}),(function(t,e){t.setTime(+t+6e4*e)}),(function(t,e){return(e-t)/6e4}),(function(t){return t.getMinutes()})),cy=sy,uy=sy.range,ly=Bg((function(t){t.setTime(t-t.getMilliseconds())}),(function(t,e){t.setTime(+t+1e3*e)}),(function(t,e){return(e-t)/1e3}),(function(t){return t.getUTCSeconds()})),hy=ly,fy=ly.range,dy=Bg((function(){}),(function(t,e){t.setTime(+t+e)}),(function(t,e){return e-t}));dy.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?Bg((function(e){e.setTime(Math.floor(e/t)*t)}),(function(e,n){e.setTime(+e+n*t)}),(function(e,n){return(n-e)/t})):dy:null};var py=dy,gy=dy.range;function yy(t){return Bg((function(e){e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+7*e)}),(function(t,e){return(e-t)/6048e5}))}var vy=yy(0),my=yy(1),by=yy(2),xy=yy(3),_y=yy(4),ky=yy(5),wy=yy(6),Ey=vy.range,Ty=my.range,Cy=by.range,Ay=xy.range,Sy=_y.range,My=ky.range,Oy=wy.range,Dy=Bg((function(t){t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+e)}),(function(t,e){return(e-t)/864e5}),(function(t){return t.getUTCDate()-1})),Ny=Dy,By=Dy.range,Ly=Bg((function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCFullYear(t.getUTCFullYear()+e)}),(function(t,e){return e.getUTCFullYear()-t.getUTCFullYear()}),(function(t){return t.getUTCFullYear()}));Ly.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Bg((function(e){e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)}),(function(e,n){e.setUTCFullYear(e.getUTCFullYear()+n*t)})):null};var Fy=Ly,Py=Ly.range;function Iy(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function jy(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Ry(t,e,n){return{y:t,m:e,d:n,H:0,M:0,S:0,L:0}}function Yy(t){var e=t.dateTime,n=t.date,r=t.time,i=t.periods,a=t.days,o=t.shortDays,s=t.months,c=t.shortMonths,u=Ky(i),l=Qy(i),h=Ky(a),f=Qy(a),d=Ky(o),p=Qy(o),g=Ky(s),y=Qy(s),v=Ky(c),m=Qy(c),b={a:function(t){return o[t.getDay()]},A:function(t){return a[t.getDay()]},b:function(t){return c[t.getMonth()]},B:function(t){return s[t.getMonth()]},c:null,d:xv,e:xv,f:Tv,H:_v,I:kv,j:wv,L:Ev,m:Cv,M:Av,p:function(t){return i[+(t.getHours()>=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:em,s:nm,S:Sv,u:Mv,U:Ov,V:Dv,w:Nv,W:Bv,x:null,X:null,y:Lv,Y:Fv,Z:Pv,"%":tm},x={a:function(t){return o[t.getUTCDay()]},A:function(t){return a[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return s[t.getUTCMonth()]},c:null,d:Iv,e:Iv,f:Uv,H:jv,I:Rv,j:Yv,L:zv,m:$v,M:Wv,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:em,s:nm,S:Hv,u:Vv,U:Gv,V:qv,w:Xv,W:Zv,x:null,X:null,y:Jv,Y:Kv,Z:Qv,"%":tm},_={a:function(t,e,n){var r=d.exec(e.slice(n));return r?(t.w=p[r[0].toLowerCase()],n+r[0].length):-1},A:function(t,e,n){var r=h.exec(e.slice(n));return r?(t.w=f[r[0].toLowerCase()],n+r[0].length):-1},b:function(t,e,n){var r=v.exec(e.slice(n));return r?(t.m=m[r[0].toLowerCase()],n+r[0].length):-1},B:function(t,e,n){var r=g.exec(e.slice(n));return r?(t.m=y[r[0].toLowerCase()],n+r[0].length):-1},c:function(t,n,r){return E(t,e,n,r)},d:lv,e:lv,f:yv,H:fv,I:fv,j:hv,L:gv,m:uv,M:dv,p:function(t,e,n){var r=u.exec(e.slice(n));return r?(t.p=l[r[0].toLowerCase()],n+r[0].length):-1},q:cv,Q:mv,s:bv,S:pv,u:ev,U:nv,V:rv,w:tv,W:iv,x:function(t,e,r){return E(t,n,e,r)},X:function(t,e,n){return E(t,r,e,n)},y:ov,Y:av,Z:sv,"%":vv};function k(t,e){return function(n){var r,i,a,o=[],s=-1,c=0,u=t.length;for(n instanceof Date||(n=new Date(+n));++s53)return null;"w"in a||(a.w=1),"Z"in a?(i=(r=jy(Ry(a.y,0,1))).getUTCDay(),r=i>4||0===i?my.ceil(r):my(r),r=Ny.offset(r,7*(a.V-1)),a.y=r.getUTCFullYear(),a.m=r.getUTCMonth(),a.d=r.getUTCDate()+(a.w+6)%7):(i=(r=Iy(Ry(a.y,0,1))).getDay(),r=i>4||0===i?Ug.ceil(r):Ug(r),r=ny.offset(r,7*(a.V-1)),a.y=r.getFullYear(),a.m=r.getMonth(),a.d=r.getDate()+(a.w+6)%7)}else("W"in a||"U"in a)&&("w"in a||(a.w="u"in a?a.u%7:"W"in a?1:0),i="Z"in a?jy(Ry(a.y,0,1)).getUTCDay():Iy(Ry(a.y,0,1)).getDay(),a.m=0,a.d="W"in a?(a.w+6)%7+7*a.W-(i+5)%7:a.w+7*a.U-(i+6)%7);return"Z"in a?(a.H+=a.Z/100|0,a.M+=a.Z%100,jy(a)):Iy(a)}}function E(t,e,n,r){for(var i,a,o=0,s=e.length,c=n.length;o=c)return-1;if(37===(i=e.charCodeAt(o++))){if(i=e.charAt(o++),!(a=_[i in Vy?e.charAt(o++):i])||(r=a(t,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}return(b.x=k(n,b),b.X=k(r,b),b.c=k(e,b),x.x=k(n,x),x.X=k(r,x),x.c=k(e,x),{format:function(t){var e=k(t+="",b);return e.toString=function(){return t},e},parse:function(t){var e=w(t+="",!1);return e.toString=function(){return t},e},utcFormat:function(t){var e=k(t+="",x);return e.toString=function(){return t},e},utcParse:function(t){var e=w(t+="",!0);return e.toString=function(){return t},e}})}var zy,Uy,$y,Wy,Hy,Vy={"-":"",_:" ",0:"0"},Gy=/^\s*\d+/,qy=/^%/,Xy=/[\\^$*+?|[\]().{}]/g;function Zy(t,e,n){var r=t<0?"-":"",i=(r?-t:t)+"",a=i.length;return r+(a68?1900:2e3),n+r[0].length):-1}function sv(t,e,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(n,n+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function cv(t,e,n){var r=Gy.exec(e.slice(n,n+1));return r?(t.q=3*r[0]-3,n+r[0].length):-1}function uv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.m=r[0]-1,n+r[0].length):-1}function lv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.d=+r[0],n+r[0].length):-1}function hv(t,e,n){var r=Gy.exec(e.slice(n,n+3));return r?(t.m=0,t.d=+r[0],n+r[0].length):-1}function fv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.H=+r[0],n+r[0].length):-1}function dv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.M=+r[0],n+r[0].length):-1}function pv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.S=+r[0],n+r[0].length):-1}function gv(t,e,n){var r=Gy.exec(e.slice(n,n+3));return r?(t.L=+r[0],n+r[0].length):-1}function yv(t,e,n){var r=Gy.exec(e.slice(n,n+6));return r?(t.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function vv(t,e,n){var r=qy.exec(e.slice(n,n+1));return r?n+r[0].length:-1}function mv(t,e,n){var r=Gy.exec(e.slice(n));return r?(t.Q=+r[0],n+r[0].length):-1}function bv(t,e,n){var r=Gy.exec(e.slice(n));return r?(t.s=+r[0],n+r[0].length):-1}function xv(t,e){return Zy(t.getDate(),e,2)}function _v(t,e){return Zy(t.getHours(),e,2)}function kv(t,e){return Zy(t.getHours()%12||12,e,2)}function wv(t,e){return Zy(1+ny.count(Fg(t),t),e,3)}function Ev(t,e){return Zy(t.getMilliseconds(),e,3)}function Tv(t,e){return Ev(t,e)+"000"}function Cv(t,e){return Zy(t.getMonth()+1,e,2)}function Av(t,e){return Zy(t.getMinutes(),e,2)}function Sv(t,e){return Zy(t.getSeconds(),e,2)}function Mv(t){var e=t.getDay();return 0===e?7:e}function Ov(t,e){return Zy(zg.count(Fg(t)-1,t),e,2)}function Dv(t,e){var n=t.getDay();return t=n>=4||0===n?Hg(t):Hg.ceil(t),Zy(Hg.count(Fg(t),t)+(4===Fg(t).getDay()),e,2)}function Nv(t){return t.getDay()}function Bv(t,e){return Zy(Ug.count(Fg(t)-1,t),e,2)}function Lv(t,e){return Zy(t.getFullYear()%100,e,2)}function Fv(t,e){return Zy(t.getFullYear()%1e4,e,4)}function Pv(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+Zy(e/60|0,"0",2)+Zy(e%60,"0",2)}function Iv(t,e){return Zy(t.getUTCDate(),e,2)}function jv(t,e){return Zy(t.getUTCHours(),e,2)}function Rv(t,e){return Zy(t.getUTCHours()%12||12,e,2)}function Yv(t,e){return Zy(1+Ny.count(Fy(t),t),e,3)}function zv(t,e){return Zy(t.getUTCMilliseconds(),e,3)}function Uv(t,e){return zv(t,e)+"000"}function $v(t,e){return Zy(t.getUTCMonth()+1,e,2)}function Wv(t,e){return Zy(t.getUTCMinutes(),e,2)}function Hv(t,e){return Zy(t.getUTCSeconds(),e,2)}function Vv(t){var e=t.getUTCDay();return 0===e?7:e}function Gv(t,e){return Zy(vy.count(Fy(t)-1,t),e,2)}function qv(t,e){var n=t.getUTCDay();return t=n>=4||0===n?_y(t):_y.ceil(t),Zy(_y.count(Fy(t),t)+(4===Fy(t).getUTCDay()),e,2)}function Xv(t){return t.getUTCDay()}function Zv(t,e){return Zy(my.count(Fy(t)-1,t),e,2)}function Jv(t,e){return Zy(t.getUTCFullYear()%100,e,2)}function Kv(t,e){return Zy(t.getUTCFullYear()%1e4,e,4)}function Qv(){return"+0000"}function tm(){return"%"}function em(t){return+t}function nm(t){return Math.floor(+t/1e3)}function rm(t){return zy=Yy(t),Uy=zy.format,$y=zy.parse,Wy=zy.utcFormat,Hy=zy.utcParse,zy}rm({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function im(t){return new Date(t)}function am(t){return t instanceof Date?+t:+new Date(+t)}function om(t,e,n,r,a,o,s,c,u){var l=ig(Jp,Jp),h=l.invert,f=l.domain,d=u(".%L"),p=u(":%S"),g=u("%I:%M"),y=u("%I %p"),v=u("%a %d"),m=u("%b %d"),b=u("%B"),x=u("%Y"),_=[[s,1,1e3],[s,5,5e3],[s,15,15e3],[s,30,3e4],[o,1,6e4],[o,5,3e5],[o,15,9e5],[o,30,18e5],[a,1,36e5],[a,3,108e5],[a,6,216e5],[a,12,432e5],[r,1,864e5],[r,2,1728e5],[n,1,6048e5],[e,1,2592e6],[e,3,7776e6],[t,1,31536e6]];function k(i){return(s(i)1)&&(t-=Math.floor(t));var e=Math.abs(t-.5);return qb.h=360*t-100,qb.s=1.5-1.5*e,qb.l=.8-.9*e,qb+""},Zb=Ge(),Jb=Math.PI/3,Kb=2*Math.PI/3,Qb=function(t){var e;return t=(.5-t)*Math.PI,Zb.r=255*(e=Math.sin(t))*e,Zb.g=255*(e=Math.sin(t+Jb))*e,Zb.b=255*(e=Math.sin(t+Kb))*e,Zb+""},tx=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-14825.05*t)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+707.56*t)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-6838.66*t)))))))+")"};function ex(t){var e=t.length;return function(n){return t[Math.max(0,Math.min(e-1,Math.floor(n*e)))]}}var nx=ex(Nm("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),rx=ex(Nm("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),ix=ex(Nm("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),ax=ex(Nm("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921")),ox=function(t){return ke(ne(t).call(document.documentElement))},sx=0;function cx(){return new ux}function ux(){this._="@"+(++sx).toString(36)}ux.prototype=cx.prototype={constructor:ux,get:function(t){for(var e=this._;!(e in t);)if(!(t=t.parentNode))return;return t[e]},set:function(t,e){return t[this._]=e},remove:function(t){return this._ in t&&delete t[this._]},toString:function(){return this._}};var lx=function(t){return"string"==typeof t?new be([document.querySelectorAll(t)],[document.documentElement]):new be([null==t?[]:t],me)},hx=function(t,e){null==e&&(e=Mn().touches);for(var n=0,r=e?e.length:0,i=new Array(r);n1?0:t<-1?xx:Math.acos(t)}function Ex(t){return t>=1?_x:t<=-1?-_x:Math.asin(t)}function Tx(t){return t.innerRadius}function Cx(t){return t.outerRadius}function Ax(t){return t.startAngle}function Sx(t){return t.endAngle}function Mx(t){return t&&t.padAngle}function Ox(t,e,n,r,i,a,o,s){var c=n-t,u=r-e,l=o-i,h=s-a,f=h*c-l*u;if(!(f*f<1e-12))return[t+(f=(l*(e-a)-h*(t-i))/f)*c,e+f*u]}function Dx(t,e,n,r,i,a,o){var s=t-n,c=e-r,u=(o?a:-a)/bx(s*s+c*c),l=u*c,h=-u*s,f=t+l,d=e+h,p=n+l,g=r+h,y=(f+p)/2,v=(d+g)/2,m=p-f,b=g-d,x=m*m+b*b,_=i-a,k=f*g-p*d,w=(b<0?-1:1)*bx(yx(0,_*_*x-k*k)),E=(k*b-m*w)/x,T=(-k*m-b*w)/x,C=(k*b+m*w)/x,A=(-k*m+b*w)/x,S=E-y,M=T-v,O=C-y,D=A-v;return S*S+M*M>O*O+D*D&&(E=C,T=A),{cx:E,cy:T,x01:-l,y01:-h,x11:E*(i/_-1),y11:T*(i/_-1)}}var Nx=function(){var t=Tx,e=Cx,n=fx(0),r=null,i=Ax,a=Sx,o=Mx,s=null;function c(){var c,u,l=+t.apply(this,arguments),h=+e.apply(this,arguments),f=i.apply(this,arguments)-_x,d=a.apply(this,arguments)-_x,p=dx(d-f),g=d>f;if(s||(s=c=Ui()),h1e-12)if(p>kx-1e-12)s.moveTo(h*gx(f),h*mx(f)),s.arc(0,0,h,f,d,!g),l>1e-12&&(s.moveTo(l*gx(d),l*mx(d)),s.arc(0,0,l,d,f,g));else{var y,v,m=f,b=d,x=f,_=d,k=p,w=p,E=o.apply(this,arguments)/2,T=E>1e-12&&(r?+r.apply(this,arguments):bx(l*l+h*h)),C=vx(dx(h-l)/2,+n.apply(this,arguments)),A=C,S=C;if(T>1e-12){var M=Ex(T/l*mx(E)),O=Ex(T/h*mx(E));(k-=2*M)>1e-12?(x+=M*=g?1:-1,_-=M):(k=0,x=_=(f+d)/2),(w-=2*O)>1e-12?(m+=O*=g?1:-1,b-=O):(w=0,m=b=(f+d)/2)}var D=h*gx(m),N=h*mx(m),B=l*gx(_),L=l*mx(_);if(C>1e-12){var F,P=h*gx(b),I=h*mx(b),j=l*gx(x),R=l*mx(x);if(p1e-12?S>1e-12?(y=Dx(j,R,D,N,h,S,g),v=Dx(P,I,B,L,h,S,g),s.moveTo(y.cx+y.x01,y.cy+y.y01),S1e-12&&k>1e-12?A>1e-12?(y=Dx(B,L,P,I,l,-A,g),v=Dx(D,N,j,R,l,-A,g),s.lineTo(y.cx+y.x01,y.cy+y.y01),A=l;--h)s.point(y[h],v[h]);s.lineEnd(),s.areaEnd()}g&&(y[u]=+t(f,u,c),v[u]=+n(f,u,c),s.point(e?+e(f,u,c):y[u],r?+r(f,u,c):v[u]))}if(d)return s=null,d+""||null}function u(){return Ix().defined(i).curve(o).context(a)}return c.x=function(n){return arguments.length?(t="function"==typeof n?n:fx(+n),e=null,c):t},c.x0=function(e){return arguments.length?(t="function"==typeof e?e:fx(+e),c):t},c.x1=function(t){return arguments.length?(e=null==t?null:"function"==typeof t?t:fx(+t),c):e},c.y=function(t){return arguments.length?(n="function"==typeof t?t:fx(+t),r=null,c):n},c.y0=function(t){return arguments.length?(n="function"==typeof t?t:fx(+t),c):n},c.y1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:fx(+t),c):r},c.lineX0=c.lineY0=function(){return u().x(t).y(n)},c.lineY1=function(){return u().x(t).y(r)},c.lineX1=function(){return u().x(e).y(n)},c.defined=function(t){return arguments.length?(i="function"==typeof t?t:fx(!!t),c):i},c.curve=function(t){return arguments.length?(o=t,null!=a&&(s=o(a)),c):o},c.context=function(t){return arguments.length?(null==t?a=s=null:s=o(a=t),c):a},c},Rx=function(t,e){return et?1:e>=t?0:NaN},Yx=function(t){return t},zx=function(){var t=Yx,e=Rx,n=null,r=fx(0),i=fx(kx),a=fx(0);function o(o){var s,c,u,l,h,f=o.length,d=0,p=new Array(f),g=new Array(f),y=+r.apply(this,arguments),v=Math.min(kx,Math.max(-kx,i.apply(this,arguments)-y)),m=Math.min(Math.abs(v)/f,a.apply(this,arguments)),b=m*(v<0?-1:1);for(s=0;s0&&(d+=h);for(null!=e?p.sort((function(t,n){return e(g[t],g[n])})):null!=n&&p.sort((function(t,e){return n(o[t],o[e])})),s=0,u=d?(v-f*b)/d:0;s0?h*u:0)+b,g[c]={data:o[c],index:s,value:h,startAngle:y,endAngle:l,padAngle:m};return g}return o.value=function(e){return arguments.length?(t="function"==typeof e?e:fx(+e),o):t},o.sortValues=function(t){return arguments.length?(e=t,n=null,o):e},o.sort=function(t){return arguments.length?(n=t,e=null,o):n},o.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:fx(+t),o):r},o.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:fx(+t),o):i},o.padAngle=function(t){return arguments.length?(a="function"==typeof t?t:fx(+t),o):a},o},Ux=Wx(Lx);function $x(t){this._curve=t}function Wx(t){function e(e){return new $x(t(e))}return e._curve=t,e}function Hx(t){var e=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?e(Wx(t)):e()._curve},t}$x.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,e){this._curve.point(e*Math.sin(t),e*-Math.cos(t))}};var Vx=function(){return Hx(Ix().curve(Ux))},Gx=function(){var t=jx().curve(Ux),e=t.curve,n=t.lineX0,r=t.lineX1,i=t.lineY0,a=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return Hx(n())},delete t.lineX0,t.lineEndAngle=function(){return Hx(r())},delete t.lineX1,t.lineInnerRadius=function(){return Hx(i())},delete t.lineY0,t.lineOuterRadius=function(){return Hx(a())},delete t.lineY1,t.curve=function(t){return arguments.length?e(Wx(t)):e()._curve},t},qx=function(t,e){return[(e=+e)*Math.cos(t-=Math.PI/2),e*Math.sin(t)]},Xx=Array.prototype.slice;function Zx(t){return t.source}function Jx(t){return t.target}function Kx(t){var e=Zx,n=Jx,r=Fx,i=Px,a=null;function o(){var o,s=Xx.call(arguments),c=e.apply(this,s),u=n.apply(this,s);if(a||(a=o=Ui()),t(a,+r.apply(this,(s[0]=c,s)),+i.apply(this,s),+r.apply(this,(s[0]=u,s)),+i.apply(this,s)),o)return a=null,o+""||null}return o.source=function(t){return arguments.length?(e=t,o):e},o.target=function(t){return arguments.length?(n=t,o):n},o.x=function(t){return arguments.length?(r="function"==typeof t?t:fx(+t),o):r},o.y=function(t){return arguments.length?(i="function"==typeof t?t:fx(+t),o):i},o.context=function(t){return arguments.length?(a=null==t?null:t,o):a},o}function Qx(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e=(e+r)/2,n,e,i,r,i)}function t_(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e,n=(n+i)/2,r,n,r,i)}function e_(t,e,n,r,i){var a=qx(e,n),o=qx(e,n=(n+i)/2),s=qx(r,n),c=qx(r,i);t.moveTo(a[0],a[1]),t.bezierCurveTo(o[0],o[1],s[0],s[1],c[0],c[1])}function n_(){return Kx(Qx)}function r_(){return Kx(t_)}function i_(){var t=Kx(e_);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t}var a_={draw:function(t,e){var n=Math.sqrt(e/xx);t.moveTo(n,0),t.arc(0,0,n,0,kx)}},o_={draw:function(t,e){var n=Math.sqrt(e/5)/2;t.moveTo(-3*n,-n),t.lineTo(-n,-n),t.lineTo(-n,-3*n),t.lineTo(n,-3*n),t.lineTo(n,-n),t.lineTo(3*n,-n),t.lineTo(3*n,n),t.lineTo(n,n),t.lineTo(n,3*n),t.lineTo(-n,3*n),t.lineTo(-n,n),t.lineTo(-3*n,n),t.closePath()}},s_=Math.sqrt(1/3),c_=2*s_,u_={draw:function(t,e){var n=Math.sqrt(e/c_),r=n*s_;t.moveTo(0,-n),t.lineTo(r,0),t.lineTo(0,n),t.lineTo(-r,0),t.closePath()}},l_=Math.sin(xx/10)/Math.sin(7*xx/10),h_=Math.sin(kx/10)*l_,f_=-Math.cos(kx/10)*l_,d_={draw:function(t,e){var n=Math.sqrt(.8908130915292852*e),r=h_*n,i=f_*n;t.moveTo(0,-n),t.lineTo(r,i);for(var a=1;a<5;++a){var o=kx*a/5,s=Math.cos(o),c=Math.sin(o);t.lineTo(c*n,-s*n),t.lineTo(s*r-c*i,c*r+s*i)}t.closePath()}},p_={draw:function(t,e){var n=Math.sqrt(e),r=-n/2;t.rect(r,r,n,n)}},g_=Math.sqrt(3),y_={draw:function(t,e){var n=-Math.sqrt(e/(3*g_));t.moveTo(0,2*n),t.lineTo(-g_*n,-n),t.lineTo(g_*n,-n),t.closePath()}},v_=Math.sqrt(3)/2,m_=1/Math.sqrt(12),b_=3*(m_/2+1),x_={draw:function(t,e){var n=Math.sqrt(e/b_),r=n/2,i=n*m_,a=r,o=n*m_+n,s=-a,c=o;t.moveTo(r,i),t.lineTo(a,o),t.lineTo(s,c),t.lineTo(-.5*r-v_*i,v_*r+-.5*i),t.lineTo(-.5*a-v_*o,v_*a+-.5*o),t.lineTo(-.5*s-v_*c,v_*s+-.5*c),t.lineTo(-.5*r+v_*i,-.5*i-v_*r),t.lineTo(-.5*a+v_*o,-.5*o-v_*a),t.lineTo(-.5*s+v_*c,-.5*c-v_*s),t.closePath()}},__=[a_,o_,u_,p_,d_,y_,x_],k_=function(){var t=fx(a_),e=fx(64),n=null;function r(){var r;if(n||(n=r=Ui()),t.apply(this,arguments).draw(n,+e.apply(this,arguments)),r)return n=null,r+""||null}return r.type=function(e){return arguments.length?(t="function"==typeof e?e:fx(e),r):t},r.size=function(t){return arguments.length?(e="function"==typeof t?t:fx(+t),r):e},r.context=function(t){return arguments.length?(n=null==t?null:t,r):n},r},w_=function(){};function E_(t,e,n){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+n)/6)}function T_(t){this._context=t}T_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:E_(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:E_(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var C_=function(t){return new T_(t)};function A_(t){this._context=t}A_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:E_(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var S_=function(t){return new A_(t)};function M_(t){this._context=t}M_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:E_(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var O_=function(t){return new M_(t)};function D_(t,e){this._basis=new T_(t),this._beta=e}D_.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,e=this._y,n=t.length-1;if(n>0)for(var r,i=t[0],a=e[0],o=t[n]-i,s=e[n]-a,c=-1;++c<=n;)r=c/n,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*o),this._beta*e[c]+(1-this._beta)*(a+r*s));this._x=this._y=null,this._basis.lineEnd()},point:function(t,e){this._x.push(+t),this._y.push(+e)}};var N_=function t(e){function n(t){return 1===e?new T_(t):new D_(t,e)}return n.beta=function(e){return t(+e)},n}(.85);function B_(t,e,n){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-e),t._y2+t._k*(t._y1-n),t._x2,t._y2)}function L_(t,e){this._context=t,this._k=(1-e)/6}L_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:B_(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2,this._x1=t,this._y1=e;break;case 2:this._point=3;default:B_(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var F_=function t(e){function n(t){return new L_(t,e)}return n.tension=function(e){return t(+e)},n}(0);function P_(t,e){this._context=t,this._k=(1-e)/6}P_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:B_(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var I_=function t(e){function n(t){return new P_(t,e)}return n.tension=function(e){return t(+e)},n}(0);function j_(t,e){this._context=t,this._k=(1-e)/6}j_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:B_(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var R_=function t(e){function n(t){return new j_(t,e)}return n.tension=function(e){return t(+e)},n}(0);function Y_(t,e,n){var r=t._x1,i=t._y1,a=t._x2,o=t._y2;if(t._l01_a>1e-12){var s=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*s-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*s-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>1e-12){var u=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,l=3*t._l23_a*(t._l23_a+t._l12_a);a=(a*u+t._x1*t._l23_2a-e*t._l12_2a)/l,o=(o*u+t._y1*t._l23_2a-n*t._l12_2a)/l}t._context.bezierCurveTo(r,i,a,o,t._x2,t._y2)}function z_(t,e){this._context=t,this._alpha=e}z_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3;default:Y_(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var U_=function t(e){function n(t){return e?new z_(t,e):new L_(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function $_(t,e){this._context=t,this._alpha=e}$_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:Y_(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var W_=function t(e){function n(t){return e?new $_(t,e):new P_(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function H_(t,e){this._context=t,this._alpha=e}H_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Y_(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var V_=function t(e){function n(t){return e?new H_(t,e):new j_(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function G_(t){this._context=t}G_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(t,e){t=+t,e=+e,this._point?this._context.lineTo(t,e):(this._point=1,this._context.moveTo(t,e))}};var q_=function(t){return new G_(t)};function X_(t){return t<0?-1:1}function Z_(t,e,n){var r=t._x1-t._x0,i=e-t._x1,a=(t._y1-t._y0)/(r||i<0&&-0),o=(n-t._y1)/(i||r<0&&-0),s=(a*i+o*r)/(r+i);return(X_(a)+X_(o))*Math.min(Math.abs(a),Math.abs(o),.5*Math.abs(s))||0}function J_(t,e){var n=t._x1-t._x0;return n?(3*(t._y1-t._y0)/n-e)/2:e}function K_(t,e,n){var r=t._x0,i=t._y0,a=t._x1,o=t._y1,s=(a-r)/3;t._context.bezierCurveTo(r+s,i+s*e,a-s,o-s*n,a,o)}function Q_(t){this._context=t}function tk(t){this._context=new ek(t)}function ek(t){this._context=t}function nk(t){return new Q_(t)}function rk(t){return new tk(t)}function ik(t){this._context=t}function ak(t){var e,n,r=t.length-1,i=new Array(r),a=new Array(r),o=new Array(r);for(i[0]=0,a[0]=2,o[0]=t[0]+2*t[1],e=1;e=0;--e)i[e]=(o[e]-i[e+1])/a[e];for(a[r-1]=(t[r]+i[r-1])/2,e=0;e=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var n=this._x*(1-this._t)+t*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,e)}}this._x=t,this._y=e}};var ck=function(t){return new sk(t,.5)};function uk(t){return new sk(t,0)}function lk(t){return new sk(t,1)}var hk=function(t,e){if((i=t.length)>1)for(var n,r,i,a=1,o=t[e[0]],s=o.length;a=0;)n[e]=e;return n};function dk(t,e){return t[e]}var pk=function(){var t=fx([]),e=fk,n=hk,r=dk;function i(i){var a,o,s=t.apply(this,arguments),c=i.length,u=s.length,l=new Array(u);for(a=0;a0){for(var n,r,i,a=0,o=t[0].length;a0)for(var n,r,i,a,o,s,c=0,u=t[e[0]].length;c0?(r[0]=a,r[1]=a+=i):i<0?(r[1]=o,r[0]=o+=i):(r[0]=0,r[1]=i)},vk=function(t,e){if((n=t.length)>0){for(var n,r=0,i=t[e[0]],a=i.length;r0&&(r=(n=t[e[0]]).length)>0){for(var n,r,i,a=0,o=1;oa&&(a=e,r=n);return r}var _k=function(t){var e=t.map(kk);return fk(t).sort((function(t,n){return e[t]-e[n]}))};function kk(t){for(var e,n=0,r=-1,i=t.length;++r0)){if(a/=f,f<0){if(a0){if(a>h)return;a>l&&(l=a)}if(a=r-c,f||!(a<0)){if(a/=f,f<0){if(a>h)return;a>l&&(l=a)}else if(f>0){if(a0)){if(a/=d,d<0){if(a0){if(a>h)return;a>l&&(l=a)}if(a=i-u,d||!(a<0)){if(a/=d,d<0){if(a>h)return;a>l&&(l=a)}else if(d>0){if(a0||h<1)||(l>0&&(t[0]=[c+l*f,u+l*d]),h<1&&(t[1]=[c+h*f,u+h*d]),!0)}}}}}function Uk(t,e,n,r,i){var a=t[1];if(a)return!0;var o,s,c=t[0],u=t.left,l=t.right,h=u[0],f=u[1],d=l[0],p=l[1],g=(h+d)/2,y=(f+p)/2;if(p===f){if(g=r)return;if(h>d){if(c){if(c[1]>=i)return}else c=[g,n];a=[g,i]}else{if(c){if(c[1]1)if(h>d){if(c){if(c[1]>=i)return}else c=[(n-s)/o,n];a=[(i-s)/o,i]}else{if(c){if(c[1]=r)return}else c=[e,o*e+s];a=[r,o*r+s]}else{if(c){if(c[0]=-lw)){var d=c*c+u*u,p=l*l+h*h,g=(h*d-u*p)/f,y=(c*p-l*d)/f,v=Gk.pop()||new qk;v.arc=t,v.site=i,v.x=g+o,v.y=(v.cy=y+s)+Math.sqrt(g*g+y*y),t.circle=v;for(var m=null,b=sw._;b;)if(v.yuw)s=s.L;else{if(!((i=a-iw(s,o))>uw)){r>-uw?(e=s.P,n=s):i>-uw?(e=s,n=s.N):e=n=s;break}if(!s.R){e=s;break}s=s.R}!function(t){ow[t.index]={site:t,halfedges:[]}}(t);var c=Qk(t);if(aw.insert(e,c),e||n){if(e===n)return Zk(e),n=Qk(e.site),aw.insert(c,n),c.edge=n.edge=jk(e.site,c.site),Xk(e),void Xk(n);if(n){Zk(e),Zk(n);var u=e.site,l=u[0],h=u[1],f=t[0]-l,d=t[1]-h,p=n.site,g=p[0]-l,y=p[1]-h,v=2*(f*y-d*g),m=f*f+d*d,b=g*g+y*y,x=[(y*m-d*b)/v+l,(f*b-g*m)/v+h];Yk(n.edge,u,p,x),c.edge=jk(u,t,null,x),n.edge=jk(t,p,null,x),Xk(e),Xk(n)}else c.edge=jk(e.site,c.site)}}function rw(t,e){var n=t.site,r=n[0],i=n[1],a=i-e;if(!a)return r;var o=t.P;if(!o)return-1/0;var s=(n=o.site)[0],c=n[1],u=c-e;if(!u)return s;var l=s-r,h=1/a-1/u,f=l/u;return h?(-f+Math.sqrt(f*f-2*h*(l*l/(-2*u)-c+u/2+i-a/2)))/h+r:(r+s)/2}function iw(t,e){var n=t.N;if(n)return rw(n,e);var r=t.site;return r[1]===e?r[0]:1/0}var aw,ow,sw,cw,uw=1e-6,lw=1e-12;function hw(t,e){return e[1]-t[1]||e[0]-t[0]}function fw(t,e){var n,r,i,a=t.sort(hw).pop();for(cw=[],ow=new Array(t.length),aw=new Ik,sw=new Ik;;)if(i=Vk,a&&(!i||a[1]uw||Math.abs(i[0][1]-i[1][1])>uw)||delete cw[a]}(o,s,c,u),function(t,e,n,r){var i,a,o,s,c,u,l,h,f,d,p,g,y=ow.length,v=!0;for(i=0;iuw||Math.abs(g-f)>uw)&&(c.splice(s,0,cw.push(Rk(o,d,Math.abs(p-t)uw?[t,Math.abs(h-t)uw?[Math.abs(f-r)uw?[n,Math.abs(h-n)uw?[Math.abs(f-e)=s)return null;var c=t-i.site[0],u=e-i.site[1],l=c*c+u*u;do{i=a.cells[r=o],o=null,i.halfedges.forEach((function(n){var r=a.edges[n],s=r.left;if(s!==i.site&&s||(s=r.right)){var c=t-s[0],u=e-s[1],h=c*c+u*u;hr?(r+i)/2:Math.min(0,r)||Math.max(0,i),o>a?(a+o)/2:Math.min(0,a)||Math.max(0,o))}var Aw=function(){var t,e,n=_w,r=kw,i=Cw,a=Ew,o=Tw,s=[0,1/0],c=[[-1/0,-1/0],[1/0,1/0]],u=250,l=fp,h=lt("start","zoom","end"),f=0;function d(t){t.property("__zoom",ww).on("wheel.zoom",x).on("mousedown.zoom",_).on("dblclick.zoom",k).filter(o).on("touchstart.zoom",w).on("touchmove.zoom",E).on("touchend.zoom touchcancel.zoom",T).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function p(t,e){return(e=Math.max(s[0],Math.min(s[1],e)))===t.k?t:new yw(e,t.x,t.y)}function g(t,e,n){var r=e[0]-n[0]*t.k,i=e[1]-n[1]*t.k;return r===t.x&&i===t.y?t:new yw(t.k,r,i)}function y(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function v(t,e,n){t.on("start.zoom",(function(){m(this,arguments).start()})).on("interrupt.zoom end.zoom",(function(){m(this,arguments).end()})).tween("zoom",(function(){var t=this,i=arguments,a=m(t,i),o=r.apply(t,i),s=null==n?y(o):"function"==typeof n?n.apply(t,i):n,c=Math.max(o[1][0]-o[0][0],o[1][1]-o[0][1]),u=t.__zoom,h="function"==typeof e?e.apply(t,i):e,f=l(u.invert(s).concat(c/u.k),h.invert(s).concat(c/h.k));return function(t){if(1===t)t=h;else{var e=f(t),n=c/e[2];t=new yw(n,s[0]-e[0]*n,s[1]-e[1]*n)}a.zoom(null,t)}}))}function m(t,e,n){return!n&&t.__zooming||new b(t,e)}function b(t,e){this.that=t,this.args=e,this.active=0,this.extent=r.apply(t,e),this.taps=0}function x(){if(n.apply(this,arguments)){var t=m(this,arguments),e=this.__zoom,r=Math.max(s[0],Math.min(s[1],e.k*Math.pow(2,a.apply(this,arguments)))),o=Nn(this);if(t.wheel)t.mouse[0][0]===o[0]&&t.mouse[0][1]===o[1]||(t.mouse[1]=e.invert(t.mouse[0]=o)),clearTimeout(t.wheel);else{if(e.k===r)return;t.mouse=[o,e.invert(o)],or(this),t.start()}xw(),t.wheel=setTimeout(u,150),t.zoom("mouse",i(g(p(e,r),t.mouse[0],t.mouse[1]),t.extent,c))}function u(){t.wheel=null,t.end()}}function _(){if(!e&&n.apply(this,arguments)){var t=m(this,arguments,!0),r=ke(ce.view).on("mousemove.zoom",u,!0).on("mouseup.zoom",l,!0),a=Nn(this),o=ce.clientX,s=ce.clientY;Te(ce.view),bw(),t.mouse=[a,this.__zoom.invert(a)],or(this),t.start()}function u(){if(xw(),!t.moved){var e=ce.clientX-o,n=ce.clientY-s;t.moved=e*e+n*n>f}t.zoom("mouse",i(g(t.that.__zoom,t.mouse[0]=Nn(t.that),t.mouse[1]),t.extent,c))}function l(){r.on("mousemove.zoom mouseup.zoom",null),Ce(ce.view,t.moved),xw(),t.end()}}function k(){if(n.apply(this,arguments)){var t=this.__zoom,e=Nn(this),a=t.invert(e),o=t.k*(ce.shiftKey?.5:2),s=i(g(p(t,o),e,a),r.apply(this,arguments),c);xw(),u>0?ke(this).transition().duration(u).call(v,s,e):ke(this).call(d.transform,s)}}function w(){if(n.apply(this,arguments)){var e,r,i,a,o=ce.touches,s=o.length,c=m(this,arguments,ce.changedTouches.length===s);for(bw(),r=0;rh&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},M={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),56;case 1:return this.begin("type_directive"),57;case 2:return this.popState(),this.begin("arg_directive"),14;case 3:return this.popState(),this.popState(),59;case 4:return 58;case 5:return 5;case 6:case 7:case 8:case 9:case 10:break;case 11:return this.begin("ID"),16;case 12:return e.yytext=e.yytext.trim(),this.begin("ALIAS"),48;case 13:return this.popState(),this.popState(),this.begin("LINE"),18;case 14:return this.popState(),this.popState(),5;case 15:return this.begin("LINE"),27;case 16:return this.begin("LINE"),29;case 17:return this.begin("LINE"),30;case 18:return this.begin("LINE"),31;case 19:return this.begin("LINE"),36;case 20:return this.begin("LINE"),33;case 21:return this.begin("LINE"),35;case 22:return this.popState(),19;case 23:return 28;case 24:return 43;case 25:return 44;case 26:return 39;case 27:return 37;case 28:return this.begin("ID"),22;case 29:return this.begin("ID"),23;case 30:return 25;case 31:return 7;case 32:return 21;case 33:return 42;case 34:return 5;case 35:return e.yytext=e.yytext.trim(),48;case 36:return 51;case 37:return 52;case 38:return 49;case 39:return 50;case 40:return 53;case 41:return 54;case 42:return 55;case 43:return 46;case 44:return 47;case 45:return 5;case 46:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:participant\b)/i,/^(?:[^\->:\n,;]+?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:as\b)/i,/^(?:(?:))/i,/^(?:loop\b)/i,/^(?:rect\b)/i,/^(?:opt\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:par\b)/i,/^(?:and\b)/i,/^(?:(?:[:]?(?:no)?wrap)?[^#\n;]*)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:activate\b)/i,/^(?:deactivate\b)/i,/^(?:title\b)/i,/^(?:sequenceDiagram\b)/i,/^(?:autonumber\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^\+\->:\n,;]+((?!(-x|--x))[\-]*[^\+\->:\n,;]+)*)/i,/^(?:->>)/i,/^(?:-->>)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:-[x])/i,/^(?:--[x])/i,/^(?::(?:(?:no)?wrap)?[^#\n;]+)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1,8],inclusive:!1},type_directive:{rules:[2,3,8],inclusive:!1},arg_directive:{rules:[3,4,8],inclusive:!1},ID:{rules:[7,8,12],inclusive:!1},ALIAS:{rules:[7,8,13,14],inclusive:!1},LINE:{rules:[7,8,22],inclusive:!1},INITIAL:{rules:[0,5,6,8,9,10,11,15,16,17,18,19,20,21,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],inclusive:!0}}};function O(){this.yy={}}return S.lexer=M,O.prototype=S,S.Parser=O,new O}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){var r=n(198);t.exports={Graph:r.Graph,json:n(301),alg:n(302),version:r.version}},function(t,e,n){var r;try{r={cloneDeep:n(313),constant:n(86),defaults:n(154),each:n(87),filter:n(128),find:n(314),flatten:n(156),forEach:n(126),forIn:n(319),has:n(93),isUndefined:n(139),last:n(320),map:n(140),mapValues:n(321),max:n(322),merge:n(324),min:n(329),minBy:n(330),now:n(331),pick:n(161),range:n(162),reduce:n(142),sortBy:n(338),uniqueId:n(163),values:n(147),zipObject:n(343)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){var n=Array.isArray;t.exports=n},function(t,e,n){ +/** + * @license + * Copyright (c) 2012-2013 Chris Pettitt + * + * 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. + */ +t.exports={graphlib:n(311),dagre:n(153),intersect:n(368),render:n(370),util:n(12),version:n(382)}},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e,n){"use strict";var r=n(4),i=n(17).Graph;function a(t,e,n,i){var a;do{a=r.uniqueId(i)}while(t.hasNode(a));return n.dummy=e,t.setNode(a,n),a}function o(t){return r.max(r.map(t.nodes(),(function(e){var n=t.node(e).rank;if(!r.isUndefined(n))return n})))}t.exports={addDummyNode:a,simplify:function(t){var e=(new i).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){var r=e.edge(n.v,n.w)||{weight:0,minlen:1},i=t.edge(n);e.setEdge(n.v,n.w,{weight:r.weight+i.weight,minlen:Math.max(r.minlen,i.minlen)})})),e},asNonCompoundGraph:function(t){var e=new i({multigraph:t.isMultigraph()}).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){t.children(n).length||e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){e.setEdge(n,t.edge(n))})),e},successorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.outEdges(e),(function(e){n[e.w]=(n[e.w]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},predecessorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.inEdges(e),(function(e){n[e.v]=(n[e.v]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},intersectRect:function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,u=t.height/2;if(!o&&!s)throw new Error("Not possible to find intersection inside of the rectangle");Math.abs(s)*c>Math.abs(o)*u?(s<0&&(u=-u),n=u*o/s,r=u):(o<0&&(c=-c),n=c,r=c*s/o);return{x:i+n,y:a+r}},buildLayerMatrix:function(t){var e=r.map(r.range(o(t)+1),(function(){return[]}));return r.forEach(t.nodes(),(function(n){var i=t.node(n),a=i.rank;r.isUndefined(a)||(e[a][i.order]=n)})),e},normalizeRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank})));r.forEach(t.nodes(),(function(n){var i=t.node(n);r.has(i,"rank")&&(i.rank-=e)}))},removeEmptyRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank}))),n=[];r.forEach(t.nodes(),(function(r){var i=t.node(r).rank-e;n[i]||(n[i]=[]),n[i].push(r)}));var i=0,a=t.graph().nodeRankFactor;r.forEach(n,(function(e,n){r.isUndefined(e)&&n%a!=0?--i:i&&r.forEach(e,(function(e){t.node(e).rank+=i}))}))},addBorderNode:function(t,e,n,r){var i={width:0,height:0};arguments.length>=4&&(i.rank=n,i.order=r);return a(t,"border",i,e)},maxRank:o,partition:function(t,e){var n={lhs:[],rhs:[]};return r.forEach(t,(function(t){e(t)?n.lhs.push(t):n.rhs.push(t)})),n},time:function(t,e){var n=r.now();try{return e()}finally{console.log(t+" time: "+(r.now()-n)+"ms")}},notime:function(t,e){return e()}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(173),i=n(174),a=n(175),o={channel:r.default,lang:i.default,unit:a.default};e.default=o},function(t,e,n){var r;try{r={clone:n(199),constant:n(86),each:n(87),filter:n(128),has:n(93),isArray:n(5),isEmpty:n(276),isFunction:n(37),isUndefined:n(139),keys:n(30),map:n(140),reduce:n(142),size:n(279),transform:n(285),union:n(286),values:n(147)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},function(t,e,n){var r=n(43);t.exports={isSubgraph:function(t,e){return!!t.children(e).length},edgeToId:function(t){return a(t.v)+":"+a(t.w)+":"+a(t.name)},applyStyle:function(t,e){e&&t.attr("style",e)},applyClass:function(t,e,n){e&&t.attr("class",e).attr("class",n+" "+t.attr("class"))},applyTransition:function(t,e){var n=e.graph();if(r.isPlainObject(n)){var i=n.transition;if(r.isFunction(i))return i(t)}return t}};var i=/:/g;function a(t){return t?String(t).replace(i,"\\:"):""}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,7],n=[1,6],r=[1,14],i=[1,25],a=[1,28],o=[1,26],s=[1,27],c=[1,29],u=[1,30],l=[1,31],h=[1,32],f=[1,34],d=[1,35],p=[1,36],g=[10,19],y=[1,48],v=[1,49],m=[1,50],b=[1,51],x=[1,52],_=[1,53],k=[10,19,25,32,33,41,44,45,46,47,48,49,54,56],w=[10,19,23,25,32,33,37,41,44,45,46,47,48,49,54,56,71,72,73],E=[10,13,17,19],T=[41,71,72,73],C=[41,48,49,71,72,73],A=[41,44,45,46,47,71,72,73],S=[10,19,25],M=[1,85],O={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,directive:5,graphConfig:6,openDirective:7,typeDirective:8,closeDirective:9,NEWLINE:10,":":11,argDirective:12,open_directive:13,type_directive:14,arg_directive:15,close_directive:16,CLASS_DIAGRAM:17,statements:18,EOF:19,statement:20,className:21,alphaNumToken:22,GENERICTYPE:23,relationStatement:24,LABEL:25,classStatement:26,methodStatement:27,annotationStatement:28,clickStatement:29,cssClassStatement:30,CLASS:31,STYLE_SEPARATOR:32,STRUCT_START:33,members:34,STRUCT_STOP:35,ANNOTATION_START:36,ANNOTATION_END:37,MEMBER:38,SEPARATOR:39,relation:40,STR:41,relationType:42,lineType:43,AGGREGATION:44,EXTENSION:45,COMPOSITION:46,DEPENDENCY:47,LINE:48,DOTTED_LINE:49,CALLBACK:50,LINK:51,LINK_TARGET:52,CLICK:53,CALLBACK_NAME:54,CALLBACK_ARGS:55,HREF:56,CSSCLASS:57,commentToken:58,textToken:59,graphCodeTokens:60,textNoTagsToken:61,TAGSTART:62,TAGEND:63,"==":64,"--":65,PCT:66,DEFAULT:67,SPACE:68,MINUS:69,keywords:70,UNICODE_TEXT:71,NUM:72,ALPHA:73,$accept:0,$end:1},terminals_:{2:"error",10:"NEWLINE",11:":",13:"open_directive",14:"type_directive",15:"arg_directive",16:"close_directive",17:"CLASS_DIAGRAM",19:"EOF",23:"GENERICTYPE",25:"LABEL",31:"CLASS",32:"STYLE_SEPARATOR",33:"STRUCT_START",35:"STRUCT_STOP",36:"ANNOTATION_START",37:"ANNOTATION_END",38:"MEMBER",39:"SEPARATOR",41:"STR",44:"AGGREGATION",45:"EXTENSION",46:"COMPOSITION",47:"DEPENDENCY",48:"LINE",49:"DOTTED_LINE",50:"CALLBACK",51:"LINK",52:"LINK_TARGET",53:"CLICK",54:"CALLBACK_NAME",55:"CALLBACK_ARGS",56:"HREF",57:"CSSCLASS",60:"graphCodeTokens",62:"TAGSTART",63:"TAGEND",64:"==",65:"--",66:"PCT",67:"DEFAULT",68:"SPACE",69:"MINUS",70:"keywords",71:"UNICODE_TEXT",72:"NUM",73:"ALPHA"},productions_:[0,[3,1],[3,2],[4,1],[5,4],[5,6],[7,1],[8,1],[12,1],[9,1],[6,4],[18,1],[18,2],[18,3],[21,1],[21,2],[21,3],[21,2],[20,1],[20,2],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[26,2],[26,4],[26,5],[26,7],[28,4],[34,1],[34,2],[27,1],[27,2],[27,1],[27,1],[24,3],[24,4],[24,4],[24,5],[40,3],[40,2],[40,2],[40,1],[42,1],[42,1],[42,1],[42,1],[43,1],[43,1],[29,3],[29,4],[29,3],[29,4],[29,4],[29,5],[29,3],[29,4],[29,4],[29,5],[29,3],[29,4],[29,4],[29,5],[30,3],[58,1],[58,1],[59,1],[59,1],[59,1],[59,1],[59,1],[59,1],[59,1],[61,1],[61,1],[61,1],[61,1],[22,1],[22,1],[22,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 6:r.parseDirective("%%{","open_directive");break;case 7:r.parseDirective(a[s],"type_directive");break;case 8:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 9:r.parseDirective("}%%","close_directive","class");break;case 14:this.$=a[s];break;case 15:this.$=a[s-1]+a[s];break;case 16:this.$=a[s-2]+"~"+a[s-1]+a[s];break;case 17:this.$=a[s-1]+"~"+a[s];break;case 18:r.addRelation(a[s]);break;case 19:a[s-1].title=r.cleanupLabel(a[s]),r.addRelation(a[s-1]);break;case 26:r.addClass(a[s]);break;case 27:r.addClass(a[s-2]),r.setCssClass(a[s-2],a[s]);break;case 28:r.addClass(a[s-3]),r.addMembers(a[s-3],a[s-1]);break;case 29:r.addClass(a[s-5]),r.setCssClass(a[s-5],a[s-3]),r.addMembers(a[s-5],a[s-1]);break;case 30:r.addAnnotation(a[s],a[s-2]);break;case 31:this.$=[a[s]];break;case 32:a[s].push(a[s-1]),this.$=a[s];break;case 33:break;case 34:r.addMember(a[s-1],r.cleanupLabel(a[s]));break;case 35:case 36:break;case 37:this.$={id1:a[s-2],id2:a[s],relation:a[s-1],relationTitle1:"none",relationTitle2:"none"};break;case 38:this.$={id1:a[s-3],id2:a[s],relation:a[s-1],relationTitle1:a[s-2],relationTitle2:"none"};break;case 39:this.$={id1:a[s-3],id2:a[s],relation:a[s-2],relationTitle1:"none",relationTitle2:a[s-1]};break;case 40:this.$={id1:a[s-4],id2:a[s],relation:a[s-2],relationTitle1:a[s-3],relationTitle2:a[s-1]};break;case 41:this.$={type1:a[s-2],type2:a[s],lineType:a[s-1]};break;case 42:this.$={type1:"none",type2:a[s],lineType:a[s-1]};break;case 43:this.$={type1:a[s-1],type2:"none",lineType:a[s]};break;case 44:this.$={type1:"none",type2:"none",lineType:a[s]};break;case 45:this.$=r.relationType.AGGREGATION;break;case 46:this.$=r.relationType.EXTENSION;break;case 47:this.$=r.relationType.COMPOSITION;break;case 48:this.$=r.relationType.DEPENDENCY;break;case 49:this.$=r.lineType.LINE;break;case 50:this.$=r.lineType.DOTTED_LINE;break;case 51:case 57:this.$=a[s-2],r.setClickEvent(a[s-1],a[s]);break;case 52:case 58:this.$=a[s-3],r.setClickEvent(a[s-2],a[s-1]),r.setTooltip(a[s-2],a[s]);break;case 53:case 61:this.$=a[s-2],r.setLink(a[s-1],a[s]);break;case 54:this.$=a[s-3],r.setLink(a[s-2],a[s-1],a[s]);break;case 55:case 63:this.$=a[s-3],r.setLink(a[s-2],a[s-1]),r.setTooltip(a[s-2],a[s]);break;case 56:case 64:this.$=a[s-4],r.setLink(a[s-3],a[s-2],a[s]),r.setTooltip(a[s-3],a[s-1]);break;case 59:this.$=a[s-3],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 60:this.$=a[s-4],r.setClickEvent(a[s-3],a[s-2],a[s-1]),r.setTooltip(a[s-3],a[s]);break;case 62:this.$=a[s-3],r.setLink(a[s-2],a[s-1],a[s]);break;case 65:r.setCssClass(a[s-1],a[s])}},table:[{3:1,4:2,5:3,6:4,7:5,13:e,17:n},{1:[3]},{1:[2,1]},{3:8,4:2,5:3,6:4,7:5,13:e,17:n},{1:[2,3]},{8:9,14:[1,10]},{10:[1,11]},{14:[2,6]},{1:[2,2]},{9:12,11:[1,13],16:r},t([11,16],[2,7]),{5:23,7:5,13:e,18:15,20:16,21:24,22:33,24:17,26:18,27:19,28:20,29:21,30:22,31:i,36:a,38:o,39:s,50:c,51:u,53:l,57:h,71:f,72:d,73:p},{10:[1,37]},{12:38,15:[1,39]},{10:[2,9]},{19:[1,40]},{10:[1,41],19:[2,11]},t(g,[2,18],{25:[1,42]}),t(g,[2,20]),t(g,[2,21]),t(g,[2,22]),t(g,[2,23]),t(g,[2,24]),t(g,[2,25]),t(g,[2,33],{40:43,42:46,43:47,25:[1,45],41:[1,44],44:y,45:v,46:m,47:b,48:x,49:_}),{21:54,22:33,71:f,72:d,73:p},t(g,[2,35]),t(g,[2,36]),{22:55,71:f,72:d,73:p},{21:56,22:33,71:f,72:d,73:p},{21:57,22:33,71:f,72:d,73:p},{21:58,22:33,71:f,72:d,73:p},{41:[1,59]},t(k,[2,14],{22:33,21:60,23:[1,61],71:f,72:d,73:p}),t(w,[2,79]),t(w,[2,80]),t(w,[2,81]),t(E,[2,4]),{9:62,16:r},{16:[2,8]},{1:[2,10]},{5:23,7:5,13:e,18:63,19:[2,12],20:16,21:24,22:33,24:17,26:18,27:19,28:20,29:21,30:22,31:i,36:a,38:o,39:s,50:c,51:u,53:l,57:h,71:f,72:d,73:p},t(g,[2,19]),{21:64,22:33,41:[1,65],71:f,72:d,73:p},{40:66,42:46,43:47,44:y,45:v,46:m,47:b,48:x,49:_},t(g,[2,34]),{43:67,48:x,49:_},t(T,[2,44],{42:68,44:y,45:v,46:m,47:b}),t(C,[2,45]),t(C,[2,46]),t(C,[2,47]),t(C,[2,48]),t(A,[2,49]),t(A,[2,50]),t(g,[2,26],{32:[1,69],33:[1,70]}),{37:[1,71]},{41:[1,72]},{41:[1,73]},{54:[1,74],56:[1,75]},{22:76,71:f,72:d,73:p},t(k,[2,15]),t(k,[2,17],{22:33,21:77,71:f,72:d,73:p}),{10:[1,78]},{19:[2,13]},t(S,[2,37]),{21:79,22:33,71:f,72:d,73:p},{21:80,22:33,41:[1,81],71:f,72:d,73:p},t(T,[2,43],{42:82,44:y,45:v,46:m,47:b}),t(T,[2,42]),{22:83,71:f,72:d,73:p},{34:84,38:M},{21:86,22:33,71:f,72:d,73:p},t(g,[2,51],{41:[1,87]}),t(g,[2,53],{41:[1,89],52:[1,88]}),t(g,[2,57],{41:[1,90],55:[1,91]}),t(g,[2,61],{41:[1,93],52:[1,92]}),t(g,[2,65]),t(k,[2,16]),t(E,[2,5]),t(S,[2,39]),t(S,[2,38]),{21:94,22:33,71:f,72:d,73:p},t(T,[2,41]),t(g,[2,27],{33:[1,95]}),{35:[1,96]},{34:97,35:[2,31],38:M},t(g,[2,30]),t(g,[2,52]),t(g,[2,54]),t(g,[2,55],{52:[1,98]}),t(g,[2,58]),t(g,[2,59],{41:[1,99]}),t(g,[2,62]),t(g,[2,63],{52:[1,100]}),t(S,[2,40]),{34:101,38:M},t(g,[2,28]),{35:[2,32]},t(g,[2,56]),t(g,[2,60]),t(g,[2,64]),{35:[1,102]},t(g,[2,29])],defaultActions:{2:[2,1],4:[2,3],7:[2,6],8:[2,2],14:[2,9],39:[2,8],40:[2,10],63:[2,13],97:[2,32]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},D={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),13;case 1:return this.begin("type_directive"),14;case 2:return this.popState(),this.begin("arg_directive"),11;case 3:return this.popState(),this.popState(),16;case 4:return 15;case 5:case 6:break;case 7:return 10;case 8:break;case 9:case 10:return 17;case 11:return this.begin("struct"),33;case 12:return"EOF_IN_STRUCT";case 13:return"OPEN_IN_STRUCT";case 14:return this.popState(),35;case 15:break;case 16:return"MEMBER";case 17:return 31;case 18:return 57;case 19:return 50;case 20:return 51;case 21:return 53;case 22:return 36;case 23:return 37;case 24:this.begin("generic");break;case 25:this.popState();break;case 26:return"GENERICTYPE";case 27:this.begin("string");break;case 28:this.popState();break;case 29:return"STR";case 30:this.begin("href");break;case 31:this.popState();break;case 32:return 56;case 33:this.begin("callback_name");break;case 34:this.popState();break;case 35:this.popState(),this.begin("callback_args");break;case 36:return 54;case 37:this.popState();break;case 38:return 55;case 39:case 40:case 41:case 42:return 52;case 43:case 44:return 45;case 45:case 46:return 47;case 47:return 46;case 48:return 44;case 49:return 48;case 50:return 49;case 51:return 25;case 52:return 32;case 53:return 69;case 54:return"DOT";case 55:return"PLUS";case 56:return 66;case 57:case 58:return"EQUALS";case 59:return 73;case 60:return"PUNCTUATION";case 61:return 72;case 62:return 71;case 63:return 68;case 64:return 19}},rules:[/^(?:%%\{)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:(\r?\n)+)/,/^(?:\s+)/,/^(?:classDiagram-v2\b)/,/^(?:classDiagram\b)/,/^(?:[{])/,/^(?:$)/,/^(?:[{])/,/^(?:[}])/,/^(?:[\n])/,/^(?:[^{}\n]*)/,/^(?:class\b)/,/^(?:cssClass\b)/,/^(?:callback\b)/,/^(?:link\b)/,/^(?:click\b)/,/^(?:<<)/,/^(?:>>)/,/^(?:[~])/,/^(?:[~])/,/^(?:[^~]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:\s*<\|)/,/^(?:\s*\|>)/,/^(?:\s*>)/,/^(?:\s*<)/,/^(?:\s*\*)/,/^(?:\s*o\b)/,/^(?:--)/,/^(?:\.\.)/,/^(?::{1}[^:\n;]+)/,/^(?::{3})/,/^(?:-)/,/^(?:\.)/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:\w+)/,/^(?:[!"#$%&'*+,-.`?\\/])/,/^(?:[0-9]+)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\s)/,/^(?:$)/],conditions:{arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callback_args:{rules:[37,38],inclusive:!1},callback_name:{rules:[34,35,36],inclusive:!1},href:{rules:[31,32],inclusive:!1},struct:{rules:[12,13,14,15,16],inclusive:!1},generic:{rules:[25,26],inclusive:!1},string:{rules:[28,29],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,17,18,19,20,21,22,23,24,27,30,33,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64],inclusive:!0}}};function N(){this.yy={}}return O.lexer=D,N.prototype=O,O.Parser=N,new N}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e){var n,r,i=t.exports={};function a(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function s(t){if(n===setTimeout)return setTimeout(t,0);if((n===a||!n)&&setTimeout)return n=setTimeout,setTimeout(t,0);try{return n(t,0)}catch(e){try{return n.call(null,t,0)}catch(e){return n.call(this,t,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:a}catch(t){n=a}try{r="function"==typeof clearTimeout?clearTimeout:o}catch(t){r=o}}();var c,u=[],l=!1,h=-1;function f(){l&&c&&(l=!1,c.length?u=c.concat(u):h=-1,u.length&&d())}function d(){if(!l){var t=s(f);l=!0;for(var e=u.length;e;){for(c=u,u=[];++h1)for(var n=1;n=0;r--){var i=t[r];"."===i?t.splice(r,1):".."===i?(t.splice(r,1),n++):n&&(t.splice(r,1),n--)}if(e)for(;n--;n)t.unshift("..");return t}function r(t,e){if(t.filter)return t.filter(e);for(var n=[],r=0;r=-1&&!i;a--){var o=a>=0?arguments[a]:t.cwd();if("string"!=typeof o)throw new TypeError("Arguments to path.resolve must be strings");o&&(e=o+"/"+e,i="/"===o.charAt(0))}return(i?"/":"")+(e=n(r(e.split("/"),(function(t){return!!t})),!i).join("/"))||"."},e.normalize=function(t){var a=e.isAbsolute(t),o="/"===i(t,-1);return(t=n(r(t.split("/"),(function(t){return!!t})),!a).join("/"))||a||(t="."),t&&o&&(t+="/"),(a?"/":"")+t},e.isAbsolute=function(t){return"/"===t.charAt(0)},e.join=function(){var t=Array.prototype.slice.call(arguments,0);return e.normalize(r(t,(function(t,e){if("string"!=typeof t)throw new TypeError("Arguments to path.join must be strings");return t})).join("/"))},e.relative=function(t,n){function r(t){for(var e=0;e=0&&""===t[n];n--);return e>n?[]:t.slice(e,n-e+1)}t=e.resolve(t).substr(1),n=e.resolve(n).substr(1);for(var i=r(t.split("/")),a=r(n.split("/")),o=Math.min(i.length,a.length),s=o,c=0;c=1;--a)if(47===(e=t.charCodeAt(a))){if(!i){r=a;break}}else i=!1;return-1===r?n?"/":".":n&&1===r?"/":t.slice(0,r)},e.basename=function(t,e){var n=function(t){"string"!=typeof t&&(t+="");var e,n=0,r=-1,i=!0;for(e=t.length-1;e>=0;--e)if(47===t.charCodeAt(e)){if(!i){n=e+1;break}}else-1===r&&(i=!1,r=e+1);return-1===r?"":t.slice(n,r)}(t);return e&&n.substr(-1*e.length)===e&&(n=n.substr(0,n.length-e.length)),n},e.extname=function(t){"string"!=typeof t&&(t+="");for(var e=-1,n=0,r=-1,i=!0,a=0,o=t.length-1;o>=0;--o){var s=t.charCodeAt(o);if(47!==s)-1===r&&(i=!1,r=o+1),46===s?-1===e?e=o:1!==a&&(a=1):-1!==e&&(a=-1);else if(!i){n=o+1;break}}return-1===e||-1===r||0===a||1===a&&e===r-1&&e===n+1?"":t.slice(e,r)};var i="b"==="ab".substr(-1)?function(t,e,n){return t.substr(e,n)}:function(t,e,n){return e<0&&(e=t.length+e),t.substr(e,n)}}).call(this,n(14))},function(t,e){t.exports=function(t){return null!=t&&"object"==typeof t}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,5],i=[1,7],a=[2,5],o=[1,15],s=[1,17],c=[1,19],u=[1,20],l=[1,21],h=[1,22],f=[1,28],d=[1,23],p=[1,24],g=[1,25],y=[1,26],v=[1,29],m=[1,32],b=[1,4,5,14,15,17,19,20,22,23,24,25,26,36,39],x=[1,4,5,12,13,14,15,17,19,20,22,23,24,25,26,36,39],_=[1,4,5,7,14,15,17,19,20,22,23,24,25,26,36,39],k=[4,5,14,15,17,19,20,22,23,24,25,26,36,39],w={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NL:5,directive:6,SD:7,document:8,line:9,statement:10,idStatement:11,DESCR:12,"--\x3e":13,HIDE_EMPTY:14,scale:15,WIDTH:16,COMPOSIT_STATE:17,STRUCT_START:18,STRUCT_STOP:19,STATE_DESCR:20,AS:21,ID:22,FORK:23,JOIN:24,CONCURRENT:25,note:26,notePosition:27,NOTE_TEXT:28,openDirective:29,typeDirective:30,closeDirective:31,":":32,argDirective:33,eol:34,";":35,EDGE_STATE:36,left_of:37,right_of:38,open_directive:39,type_directive:40,arg_directive:41,close_directive:42,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NL",7:"SD",12:"DESCR",13:"--\x3e",14:"HIDE_EMPTY",15:"scale",16:"WIDTH",17:"COMPOSIT_STATE",18:"STRUCT_START",19:"STRUCT_STOP",20:"STATE_DESCR",21:"AS",22:"ID",23:"FORK",24:"JOIN",25:"CONCURRENT",26:"note",28:"NOTE_TEXT",32:":",35:";",36:"EDGE_STATE",37:"left_of",38:"right_of",39:"open_directive",40:"type_directive",41:"arg_directive",42:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,2],[8,0],[8,2],[9,2],[9,1],[9,1],[10,1],[10,2],[10,3],[10,4],[10,1],[10,2],[10,1],[10,4],[10,3],[10,6],[10,1],[10,1],[10,1],[10,4],[10,4],[10,1],[6,3],[6,5],[34,1],[34,1],[11,1],[11,1],[27,1],[27,1],[29,1],[30,1],[33,1],[31,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:return r.setRootDoc(a[s]),a[s];case 5:this.$=[];break;case 6:"nl"!=a[s]&&(a[s-1].push(a[s]),this.$=a[s-1]);break;case 7:case 8:this.$=a[s];break;case 9:this.$="nl";break;case 10:this.$={stmt:"state",id:a[s],type:"default",description:""};break;case 11:this.$={stmt:"state",id:a[s-1],type:"default",description:r.trimColon(a[s])};break;case 12:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-2],type:"default",description:""},state2:{stmt:"state",id:a[s],type:"default",description:""}};break;case 13:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-3],type:"default",description:""},state2:{stmt:"state",id:a[s-1],type:"default",description:""},description:a[s].substr(1).trim()};break;case 17:this.$={stmt:"state",id:a[s-3],type:"default",description:"",doc:a[s-1]};break;case 18:var c=a[s],u=a[s-2].trim();if(a[s].match(":")){var l=a[s].split(":");c=l[0],u=[u,l[1]]}this.$={stmt:"state",id:c,type:"default",description:u};break;case 19:this.$={stmt:"state",id:a[s-3],type:"default",description:a[s-5],doc:a[s-1]};break;case 20:this.$={stmt:"state",id:a[s],type:"fork"};break;case 21:this.$={stmt:"state",id:a[s],type:"join"};break;case 22:this.$={stmt:"state",id:r.getDividerId(),type:"divider"};break;case 23:this.$={stmt:"state",id:a[s-1].trim(),note:{position:a[s-2].trim(),text:a[s].trim()}};break;case 30:case 31:this.$=a[s];break;case 34:r.parseDirective("%%{","open_directive");break;case 35:r.parseDirective(a[s],"type_directive");break;case 36:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 37:r.parseDirective("}%%","close_directive","state")}},table:[{3:1,4:e,5:n,6:4,7:r,29:6,39:i},{1:[3]},{3:8,4:e,5:n,6:4,7:r,29:6,39:i},{3:9,4:e,5:n,6:4,7:r,29:6,39:i},{3:10,4:e,5:n,6:4,7:r,29:6,39:i},t([1,4,5,14,15,17,20,22,23,24,25,26,36,39],a,{8:11}),{30:12,40:[1,13]},{40:[2,34]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{1:[2,4],4:o,5:s,6:27,9:14,10:16,11:18,14:c,15:u,17:l,20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},{31:30,32:[1,31],42:m},t([32,42],[2,35]),t(b,[2,6]),{6:27,10:33,11:18,14:c,15:u,17:l,20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},t(b,[2,8]),t(b,[2,9]),t(b,[2,10],{12:[1,34],13:[1,35]}),t(b,[2,14]),{16:[1,36]},t(b,[2,16],{18:[1,37]}),{21:[1,38]},t(b,[2,20]),t(b,[2,21]),t(b,[2,22]),{27:39,28:[1,40],37:[1,41],38:[1,42]},t(b,[2,25]),t(x,[2,30]),t(x,[2,31]),t(_,[2,26]),{33:43,41:[1,44]},t(_,[2,37]),t(b,[2,7]),t(b,[2,11]),{11:45,22:f,36:v},t(b,[2,15]),t(k,a,{8:46}),{22:[1,47]},{22:[1,48]},{21:[1,49]},{22:[2,32]},{22:[2,33]},{31:50,42:m},{42:[2,36]},t(b,[2,12],{12:[1,51]}),{4:o,5:s,6:27,9:14,10:16,11:18,14:c,15:u,17:l,19:[1,52],20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},t(b,[2,18],{18:[1,53]}),{28:[1,54]},{22:[1,55]},t(_,[2,27]),t(b,[2,13]),t(b,[2,17]),t(k,a,{8:56}),t(b,[2,23]),t(b,[2,24]),{4:o,5:s,6:27,9:14,10:16,11:18,14:c,15:u,17:l,19:[1,57],20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},t(b,[2,19])],defaultActions:{7:[2,34],8:[2,1],9:[2,2],10:[2,3],41:[2,32],42:[2,33],44:[2,36]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},E={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),39;case 1:return this.begin("type_directive"),40;case 2:return this.popState(),this.begin("arg_directive"),32;case 3:return this.popState(),this.popState(),42;case 4:return 41;case 5:break;case 6:console.log("Crap after close");break;case 7:return 5;case 8:case 9:case 10:case 11:break;case 12:return this.pushState("SCALE"),15;case 13:return 16;case 14:this.popState();break;case 15:this.pushState("STATE");break;case 16:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 17:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),24;case 18:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 19:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),24;case 20:this.begin("STATE_STRING");break;case 21:return this.popState(),this.pushState("STATE_ID"),"AS";case 22:return this.popState(),"ID";case 23:this.popState();break;case 24:return"STATE_DESCR";case 25:return 17;case 26:this.popState();break;case 27:return this.popState(),this.pushState("struct"),18;case 28:return this.popState(),19;case 29:break;case 30:return this.begin("NOTE"),26;case 31:return this.popState(),this.pushState("NOTE_ID"),37;case 32:return this.popState(),this.pushState("NOTE_ID"),38;case 33:this.popState(),this.pushState("FLOATING_NOTE");break;case 34:return this.popState(),this.pushState("FLOATING_NOTE_ID"),"AS";case 35:break;case 36:return"NOTE_TEXT";case 37:return this.popState(),"ID";case 38:return this.popState(),this.pushState("NOTE_TEXT"),22;case 39:return this.popState(),e.yytext=e.yytext.substr(2).trim(),28;case 40:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),28;case 41:case 42:return 7;case 43:return 14;case 44:return 36;case 45:return 22;case 46:return e.yytext=e.yytext.trim(),12;case 47:return 13;case 48:return 25;case 49:return 5;case 50:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:[\s]+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:state\s+)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*\[\[fork\]\])/i,/^(?:.*\[\[join\]\])/i,/^(?:["])/i,/^(?:\s*as\s+)/i,/^(?:[^\n\{]*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n\s\{]+)/i,/^(?:\n)/i,/^(?:\{)/i,/^(?:\})/i,/^(?:[\n])/i,/^(?:note\s+)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:")/i,/^(?:\s*as\s*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n]*)/i,/^(?:\s*[^:\n\s\-]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:[\s\S]*?end note\b)/i,/^(?:stateDiagram\s+)/i,/^(?:stateDiagram-v2\s+)/i,/^(?:hide empty description\b)/i,/^(?:\[\*\])/i,/^(?:[^:\n\s\-\{]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:-->)/i,/^(?:--)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[9,10],inclusive:!1},close_directive:{rules:[9,10],inclusive:!1},arg_directive:{rules:[3,4,9,10],inclusive:!1},type_directive:{rules:[2,3,9,10],inclusive:!1},open_directive:{rules:[1,9,10],inclusive:!1},struct:{rules:[9,10,15,28,29,30,44,45,46,47,48],inclusive:!1},FLOATING_NOTE_ID:{rules:[37],inclusive:!1},FLOATING_NOTE:{rules:[34,35,36],inclusive:!1},NOTE_TEXT:{rules:[39,40],inclusive:!1},NOTE_ID:{rules:[38],inclusive:!1},NOTE:{rules:[31,32,33],inclusive:!1},SCALE:{rules:[13,14],inclusive:!1},ALIAS:{rules:[],inclusive:!1},STATE_ID:{rules:[22],inclusive:!1},STATE_STRING:{rules:[23,24],inclusive:!1},FORK_STATE:{rules:[],inclusive:!1},STATE:{rules:[9,10,16,17,18,19,20,21,25,26,27],inclusive:!1},ID:{rules:[9,10],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,10,11,12,15,27,30,41,42,43,44,45,46,47,49,50],inclusive:!0}}};function T(){this.yy={}}return w.lexer=E,T.prototype=w,w.Parser=T,new T}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t){t.exports=function(){"use strict";var e,r;function i(){return e.apply(null,arguments)}function a(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function o(t){return null!=t&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return void 0===t}function c(t){return"number"==typeof t||"[object Number]"===Object.prototype.toString.call(t)}function u(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function l(t,e){var n,r=[];for(n=0;n>>0,r=0;ryt(t)?(a=t+1,s-yt(t)):(a=t,s),{year:a,dayOfYear:o}}function Ft(t,e,n){var r,i,a=Bt(t.year(),e,n),o=Math.floor((t.dayOfYear()-a-1)/7)+1;return o<1?r=o+Pt(i=t.year()-1,e,n):o>Pt(t.year(),e,n)?(r=o-Pt(t.year(),e,n),i=t.year()+1):(i=t.year(),r=o),{week:r,year:i}}function Pt(t,e,n){var r=Bt(t,e,n),i=Bt(t+1,e,n);return(yt(t)-r+i)/7}function It(t,e){return t.slice(e,7).concat(t.slice(0,e))}W("w",["ww",2],"wo","week"),W("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),j("week",5),j("isoWeek",5),lt("w",K),lt("ww",K,q),lt("W",K),lt("WW",K,q),gt(["w","ww","W","WW"],(function(t,e,n,r){e[r.substr(0,1)]=w(t)})),W("d",0,"do","day"),W("dd",0,0,(function(t){return this.localeData().weekdaysMin(this,t)})),W("ddd",0,0,(function(t){return this.localeData().weekdaysShort(this,t)})),W("dddd",0,0,(function(t){return this.localeData().weekdays(this,t)})),W("e",0,0,"weekday"),W("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),j("day",11),j("weekday",11),j("isoWeekday",11),lt("d",K),lt("e",K),lt("E",K),lt("dd",(function(t,e){return e.weekdaysMinRegex(t)})),lt("ddd",(function(t,e){return e.weekdaysShortRegex(t)})),lt("dddd",(function(t,e){return e.weekdaysRegex(t)})),gt(["dd","ddd","dddd"],(function(t,e,n,r){var i=n._locale.weekdaysParse(t,r,n._strict);null!=i?e.d=i:p(n).invalidWeekday=t})),gt(["d","e","E"],(function(t,e,n,r){e[r]=w(t)}));var jt="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Rt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Yt="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),zt=ct,Ut=ct,$t=ct;function Wt(){function t(t,e){return e.length-t.length}var e,n,r,i,a,o=[],s=[],c=[],u=[];for(e=0;e<7;e++)n=d([2e3,1]).day(e),r=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),a=this.weekdays(n,""),o.push(r),s.push(i),c.push(a),u.push(r),u.push(i),u.push(a);for(o.sort(t),s.sort(t),c.sort(t),u.sort(t),e=0;e<7;e++)s[e]=ft(s[e]),c[e]=ft(c[e]),u[e]=ft(u[e]);this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+c.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+o.join("|")+")","i")}function Ht(){return this.hours()%12||12}function Vt(t,e){W(t,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)}))}function Gt(t,e){return e._meridiemParse}W("H",["HH",2],0,"hour"),W("h",["hh",2],0,Ht),W("k",["kk",2],0,(function(){return this.hours()||24})),W("hmm",0,0,(function(){return""+Ht.apply(this)+R(this.minutes(),2)})),W("hmmss",0,0,(function(){return""+Ht.apply(this)+R(this.minutes(),2)+R(this.seconds(),2)})),W("Hmm",0,0,(function(){return""+this.hours()+R(this.minutes(),2)})),W("Hmmss",0,0,(function(){return""+this.hours()+R(this.minutes(),2)+R(this.seconds(),2)})),Vt("a",!0),Vt("A",!1),L("hour","h"),j("hour",13),lt("a",Gt),lt("A",Gt),lt("H",K),lt("h",K),lt("k",K),lt("HH",K,q),lt("hh",K,q),lt("kk",K,q),lt("hmm",Q),lt("hmmss",tt),lt("Hmm",Q),lt("Hmmss",tt),pt(["H","HH"],3),pt(["k","kk"],(function(t,e,n){var r=w(t);e[3]=24===r?0:r})),pt(["a","A"],(function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t})),pt(["h","hh"],(function(t,e,n){e[3]=w(t),p(n).bigHour=!0})),pt("hmm",(function(t,e,n){var r=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r)),p(n).bigHour=!0})),pt("hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r,2)),e[5]=w(t.substr(i)),p(n).bigHour=!0})),pt("Hmm",(function(t,e,n){var r=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r))})),pt("Hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r,2)),e[5]=w(t.substr(i))}));var qt,Xt=xt("Hours",!0),Zt={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Tt,monthsShort:Ct,week:{dow:0,doy:6},weekdays:jt,weekdaysMin:Yt,weekdaysShort:Rt,meridiemParse:/[ap]\.?m?\.?/i},Jt={},Kt={};function Qt(t){return t?t.toLowerCase().replace("_","-"):t}function te(e){var r=null;if(!Jt[e]&&void 0!==t&&t&&t.exports)try{r=qt._abbr,n(171)("./"+e),ee(r)}catch(e){}return Jt[e]}function ee(t,e){var n;return t&&((n=s(e)?re(t):ne(t,e))?qt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+t+" not found. Did you forget to load it?")),qt._abbr}function ne(t,e){if(null===e)return delete Jt[t],null;var n,r=Zt;if(e.abbr=t,null!=Jt[t])M("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),r=Jt[t]._config;else if(null!=e.parentLocale)if(null!=Jt[e.parentLocale])r=Jt[e.parentLocale]._config;else{if(null==(n=te(e.parentLocale)))return Kt[e.parentLocale]||(Kt[e.parentLocale]=[]),Kt[e.parentLocale].push({name:t,config:e}),null;r=n._config}return Jt[t]=new N(D(r,e)),Kt[t]&&Kt[t].forEach((function(t){ne(t.name,t.config)})),ee(t),Jt[t]}function re(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return qt;if(!a(t)){if(e=te(t))return e;t=[t]}return function(t){for(var e,n,r,i,a=0;a=e&&E(i,n,!0)>=e-1)break;e--}a++}return qt}(t)}function ie(t){var e,n=t._a;return n&&-2===p(t).overflow&&(e=n[1]<0||11wt(n[0],n[1])?2:n[3]<0||24Pt(n,a,o)?p(t)._overflowWeeks=!0:null!=c?p(t)._overflowWeekday=!0:(s=Lt(n,r,i,a,o),t._a[0]=s.year,t._dayOfYear=s.dayOfYear)}(t),null!=t._dayOfYear&&(o=ae(t._a[0],r[0]),(t._dayOfYear>yt(o)||0===t._dayOfYear)&&(p(t)._overflowDayOfYear=!0),n=Nt(o,0,t._dayOfYear),t._a[1]=n.getUTCMonth(),t._a[2]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=s[e]=r[e];for(;e<7;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[3]&&0===t._a[4]&&0===t._a[5]&&0===t._a[6]&&(t._nextDay=!0,t._a[3]=0),t._d=(t._useUTC?Nt:function(t,e,n,r,i,a,o){var s;return t<100&&0<=t?(s=new Date(t+400,e,n,r,i,a,o),isFinite(s.getFullYear())&&s.setFullYear(t)):s=new Date(t,e,n,r,i,a,o),s}).apply(null,s),a=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[3]=24),t._w&&void 0!==t._w.d&&t._w.d!==a&&(p(t).weekdayMismatch=!0)}}var se=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ce=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ue=/Z|[+-]\d\d(?::?\d\d)?/,le=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],he=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],fe=/^\/?Date\((\-?\d+)/i;function de(t){var e,n,r,i,a,o,s=t._i,c=se.exec(s)||ce.exec(s);if(c){for(p(t).iso=!0,e=0,n=le.length;en.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},on.isLocal=function(){return!!this.isValid()&&!this._isUTC},on.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},on.isUtc=Be,on.isUTC=Be,on.zoneAbbr=function(){return this._isUTC?"UTC":""},on.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},on.dates=C("dates accessor is deprecated. Use date instead.",Qe),on.months=C("months accessor is deprecated. Use month instead",St),on.years=C("years accessor is deprecated. Use year instead",bt),on.zone=C("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()})),on.isDSTShifted=C("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var t={};if(m(t,this),(t=me(t))._a){var e=t._isUTC?d(t._a):xe(t._a);this._isDSTShifted=this.isValid()&&0h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},qt={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),12;case 1:return this.begin("type_directive"),13;case 2:return this.popState(),this.begin("arg_directive"),10;case 3:return this.popState(),this.popState(),15;case 4:return 14;case 5:case 6:break;case 7:this.begin("string");break;case 8:this.popState();break;case 9:return"STR";case 10:return 75;case 11:return 84;case 12:return 76;case 13:return 93;case 14:return 77;case 15:return 78;case 16:this.begin("href");break;case 17:this.popState();break;case 18:return 89;case 19:this.begin("callbackname");break;case 20:this.popState();break;case 21:this.popState(),this.begin("callbackargs");break;case 22:return 87;case 23:this.popState();break;case 24:return 88;case 25:this.begin("click");break;case 26:this.popState();break;case 27:return 79;case 28:case 29:return t.lex.firstGraph()&&this.begin("dir"),24;case 30:return 38;case 31:return 42;case 32:case 33:case 34:case 35:return 90;case 36:return this.popState(),25;case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:return this.popState(),26;case 47:return 94;case 48:return 102;case 49:return 47;case 50:return 99;case 51:return 46;case 52:return 20;case 53:return 95;case 54:return 113;case 55:case 56:case 57:return 70;case 58:case 59:case 60:return 69;case 61:return 51;case 62:return 52;case 63:return 53;case 64:return 54;case 65:return 55;case 66:return 56;case 67:return 57;case 68:return 58;case 69:return 100;case 70:return 103;case 71:return 114;case 72:return 111;case 73:return 104;case 74:case 75:return 112;case 76:return 105;case 77:return 61;case 78:return 81;case 79:return"SEP";case 80:return 80;case 81:return 98;case 82:return 63;case 83:return 62;case 84:return 65;case 85:return 64;case 86:return 109;case 87:return 110;case 88:return 71;case 89:return 49;case 90:return 50;case 91:return 40;case 92:return 41;case 93:return 59;case 94:return 60;case 95:return 120;case 96:return 21;case 97:return 22;case 98:return 23}},rules:[/^(?:%%\{)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)[^\n]*)/,/^(?:[^\}]%%[^\n]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:click[\s]+)/,/^(?:[\s\n])/,/^(?:[^\s\n]*)/,/^(?:graph\b)/,/^(?:flowchart\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:(\r?\n)*\s*\n)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:&)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:\(-)/,/^(?:-\))/,/^(?:\(\[)/,/^(?:\]\))/,/^(?:\[\[)/,/^(?:\]\])/,/^(?:\[\()/,/^(?:\)\])/,/^(?:-)/,/^(?:\.)/,/^(?:[\_])/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:\\\|)/,/^(?:v\b)/,/^(?:[A-Za-z]+)/,/^(?:\\\])/,/^(?:\[\/)/,/^(?:\/\])/,/^(?:\[\\)/,/^(?:[!"#$%&'*+,-.`?\\_/])/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\()/,/^(?:\))/,/^(?:\[)/,/^(?:\])/,/^(?:\{)/,/^(?:\})/,/^(?:")/,/^(?:(\r?\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[23,24],inclusive:!1},callbackname:{rules:[20,21,22],inclusive:!1},href:{rules:[17,18],inclusive:!1},click:{rules:[26,27],inclusive:!1},vertex:{rules:[],inclusive:!1},dir:{rules:[36,37,38,39,40,41,42,43,44,45,46],inclusive:!1},string:{rules:[8,9],inclusive:!1},INITIAL:{rules:[0,5,6,7,10,11,12,13,14,15,16,19,25,28,29,30,31,32,33,34,35,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98],inclusive:!0}}};function Xt(){this.yy={}}return Gt.lexer=qt,Xt.prototype=Gt,Gt.Parser=Xt,new Xt}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,3],n=[1,5],r=[7,9,11,12,13,14,15,16,17,18,20,27,32],i=[1,15],a=[1,16],o=[1,17],s=[1,18],c=[1,19],u=[1,20],l=[1,21],h=[1,23],f=[1,25],d=[1,28],p=[5,7,9,11,12,13,14,15,16,17,18,20,27,32],g={trace:function(){},yy:{},symbols_:{error:2,start:3,directive:4,gantt:5,document:6,EOF:7,line:8,SPACE:9,statement:10,NL:11,dateFormat:12,inclusiveEndDates:13,axisFormat:14,excludes:15,todayMarker:16,title:17,section:18,clickStatement:19,taskTxt:20,taskData:21,openDirective:22,typeDirective:23,closeDirective:24,":":25,argDirective:26,click:27,callbackname:28,callbackargs:29,href:30,clickStatementDebug:31,open_directive:32,type_directive:33,arg_directive:34,close_directive:35,$accept:0,$end:1},terminals_:{2:"error",5:"gantt",7:"EOF",9:"SPACE",11:"NL",12:"dateFormat",13:"inclusiveEndDates",14:"axisFormat",15:"excludes",16:"todayMarker",17:"title",18:"section",20:"taskTxt",21:"taskData",25:":",27:"click",28:"callbackname",29:"callbackargs",30:"href",32:"open_directive",33:"type_directive",34:"arg_directive",35:"close_directive"},productions_:[0,[3,2],[3,3],[6,0],[6,2],[8,2],[8,1],[8,1],[8,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,2],[10,1],[4,4],[4,6],[19,2],[19,3],[19,3],[19,4],[19,3],[19,4],[19,2],[31,2],[31,3],[31,3],[31,4],[31,3],[31,4],[31,2],[22,1],[23,1],[26,1],[24,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 2:return a[s-1];case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 9:r.setDateFormat(a[s].substr(11)),this.$=a[s].substr(11);break;case 10:r.enableInclusiveEndDates(),this.$=a[s].substr(18);break;case 11:r.setAxisFormat(a[s].substr(11)),this.$=a[s].substr(11);break;case 12:r.setExcludes(a[s].substr(9)),this.$=a[s].substr(9);break;case 13:r.setTodayMarker(a[s].substr(12)),this.$=a[s].substr(12);break;case 14:r.setTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 15:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 17:r.addTask(a[s-1],a[s]),this.$="task";break;case 21:this.$=a[s-1],r.setClickEvent(a[s-1],a[s],null);break;case 22:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 23:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],null),r.setLink(a[s-2],a[s]);break;case 24:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-2],a[s-1]),r.setLink(a[s-3],a[s]);break;case 25:this.$=a[s-2],r.setClickEvent(a[s-2],a[s],null),r.setLink(a[s-2],a[s-1]);break;case 26:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-1],a[s]),r.setLink(a[s-3],a[s-2]);break;case 27:this.$=a[s-1],r.setLink(a[s-1],a[s]);break;case 28:case 34:this.$=a[s-1]+" "+a[s];break;case 29:case 30:case 32:this.$=a[s-2]+" "+a[s-1]+" "+a[s];break;case 31:case 33:this.$=a[s-3]+" "+a[s-2]+" "+a[s-1]+" "+a[s];break;case 35:r.parseDirective("%%{","open_directive");break;case 36:r.parseDirective(a[s],"type_directive");break;case 37:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 38:r.parseDirective("}%%","close_directive","gantt")}},table:[{3:1,4:2,5:e,22:4,32:n},{1:[3]},{3:6,4:2,5:e,22:4,32:n},t(r,[2,3],{6:7}),{23:8,33:[1,9]},{33:[2,35]},{1:[2,1]},{4:24,7:[1,10],8:11,9:[1,12],10:13,11:[1,14],12:i,13:a,14:o,15:s,16:c,17:u,18:l,19:22,20:h,22:4,27:f,32:n},{24:26,25:[1,27],35:d},t([25,35],[2,36]),t(r,[2,8],{1:[2,2]}),t(r,[2,4]),{4:24,10:29,12:i,13:a,14:o,15:s,16:c,17:u,18:l,19:22,20:h,22:4,27:f,32:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,9]),t(r,[2,10]),t(r,[2,11]),t(r,[2,12]),t(r,[2,13]),t(r,[2,14]),t(r,[2,15]),t(r,[2,16]),{21:[1,30]},t(r,[2,18]),{28:[1,31],30:[1,32]},{11:[1,33]},{26:34,34:[1,35]},{11:[2,38]},t(r,[2,5]),t(r,[2,17]),t(r,[2,21],{29:[1,36],30:[1,37]}),t(r,[2,27],{28:[1,38]}),t(p,[2,19]),{24:39,35:d},{35:[2,37]},t(r,[2,22],{30:[1,40]}),t(r,[2,23]),t(r,[2,25],{29:[1,41]}),{11:[1,42]},t(r,[2,24]),t(r,[2,26]),t(p,[2,20])],defaultActions:{5:[2,35],6:[2,1],28:[2,38],35:[2,37]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},y={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),32;case 1:return this.begin("type_directive"),33;case 2:return this.popState(),this.begin("arg_directive"),25;case 3:return this.popState(),this.popState(),35;case 4:return 34;case 5:case 6:case 7:break;case 8:return 11;case 9:case 10:case 11:break;case 12:this.begin("href");break;case 13:this.popState();break;case 14:return 30;case 15:this.begin("callbackname");break;case 16:this.popState();break;case 17:this.popState(),this.begin("callbackargs");break;case 18:return 28;case 19:this.popState();break;case 20:return 29;case 21:this.begin("click");break;case 22:this.popState();break;case 23:return 27;case 24:return 5;case 25:return 12;case 26:return 13;case 27:return 14;case 28:return 15;case 29:return 16;case 30:return"date";case 31:return 17;case 32:return 18;case 33:return 20;case 34:return 21;case 35:return 25;case 36:return 7;case 37:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)*[^\n]*)/i,/^(?:[^\}]%%*[^\n]*)/i,/^(?:%%*[^\n]*[\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:inclusiveEndDates\b)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:todayMarker\s[^\n;]+)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[19,20],inclusive:!1},callbackname:{rules:[16,17,18],inclusive:!1},href:{rules:[13,14],inclusive:!1},click:{rules:[22,23],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,15,21,24,25,26,27,28,29,30,31,32,33,34,35,36,37],inclusive:!0}}};function v(){this.yy={}}return g.lexer=y,v.prototype=g,g.Parser=v,new v}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,17,18,19,21],i=[1,15],a=[1,16],o=[1,17],s=[1,21],c=[4,6,9,11,17,18,19,21],u={trace:function(){},yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,title:17,section:18,taskName:19,taskData:20,open_directive:21,type_directive:22,arg_directive:23,close_directive:24,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",17:"title",18:"section",19:"taskName",20:"taskData",21:"open_directive",22:"type_directive",23:"arg_directive",24:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,1],[10,2],[10,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 11:r.setTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 12:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 13:r.addTask(a[s-1],a[s]),this.$="task";break;case 15:r.parseDirective("%%{","open_directive");break;case 16:r.parseDirective(a[s],"type_directive");break;case 17:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 18:r.parseDirective("}%%","close_directive","journey")}},table:[{3:1,4:e,7:3,12:4,21:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,21:n},{13:8,22:[1,9]},{22:[2,15]},{6:[1,10],7:18,8:11,9:[1,12],10:13,11:[1,14],12:4,17:i,18:a,19:o,21:n},{1:[2,2]},{14:19,15:[1,20],24:s},t([15,24],[2,16]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:18,10:22,12:4,17:i,18:a,19:o,21:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,12]),{20:[1,23]},t(r,[2,14]),{11:[1,24]},{16:25,23:[1,26]},{11:[2,18]},t(r,[2,5]),t(r,[2,13]),t(c,[2,9]),{14:27,24:s},{24:[2,17]},{11:[1,28]},t(c,[2,10])],defaultActions:{5:[2,15],7:[2,2],21:[2,18],26:[2,17]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},l={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),21;case 1:return this.begin("type_directive"),22;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),24;case 4:return 23;case 5:case 6:break;case 7:return 11;case 8:case 9:break;case 10:return 4;case 11:return 17;case 12:return 18;case 13:return 19;case 14:return 20;case 15:return 15;case 16:return 6;case 17:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:journey\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,13,14,15,16,17],inclusive:!0}}};function h(){this.yy={}}return u.lexer=l,h.prototype=u,u.Parser=h,new h}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15);e.default=function(t,e){return r.default.lang.round(i.default.parse(t)[e])}},function(t,e,n){var r=n(112),i=n(82),a=n(24);t.exports=function(t){return a(t)?r(t):i(t)}},function(t,e,n){var r;if(!r)try{r=n(0)}catch(t){}r||(r=window.d3),t.exports=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15);e.default=function(t,e,n){var a=i.default.parse(t),o=a[e],s=r.default.channel.clamp[e](o+n);return o!==s&&(a[e]=s),i.default.stringify(a)}},function(t,e,n){var r=n(210),i=n(216);t.exports=function(t,e){var n=i(t,e);return r(n)?n:void 0}},function(t,e,n){var r=n(38),i=n(212),a=n(213),o=r?r.toStringTag:void 0;t.exports=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":o&&o in Object(t)?i(t):a(t)}},function(t,e){t.exports=function(t){return t}},function(t,e){t.exports=function(t,e){return t===e||t!=t&&e!=e}},function(t,e,n){var r=n(34),i=n(11);t.exports=function(t){if(!i(t))return!1;var e=r(t);return"[object Function]"==e||"[object GeneratorFunction]"==e||"[object AsyncFunction]"==e||"[object Proxy]"==e}},function(t,e,n){var r=n(16).Symbol;t.exports=r},function(t,e,n){(function(t){var r=n(16),i=n(232),a=e&&!e.nodeType&&e,o=a&&"object"==typeof t&&t&&!t.nodeType&&t,s=o&&o.exports===a?r.Buffer:void 0,c=(s?s.isBuffer:void 0)||i;t.exports=c}).call(this,n(7)(t))},function(t,e,n){var r=n(112),i=n(236),a=n(24);t.exports=function(t){return a(t)?r(t,!0):i(t)}},function(t,e,n){var r=n(241),i=n(77),a=n(242),o=n(121),s=n(243),c=n(34),u=n(110),l=u(r),h=u(i),f=u(a),d=u(o),p=u(s),g=c;(r&&"[object DataView]"!=g(new r(new ArrayBuffer(1)))||i&&"[object Map]"!=g(new i)||a&&"[object Promise]"!=g(a.resolve())||o&&"[object Set]"!=g(new o)||s&&"[object WeakMap]"!=g(new s))&&(g=function(t){var e=c(t),n="[object Object]"==e?t.constructor:void 0,r=n?u(n):"";if(r)switch(r){case l:return"[object DataView]";case h:return"[object Map]";case f:return"[object Promise]";case d:return"[object Set]";case p:return"[object WeakMap]"}return e}),t.exports=g},function(t,e,n){var r=n(34),i=n(21);t.exports=function(t){return"symbol"==typeof t||i(t)&&"[object Symbol]"==r(t)}},function(t,e,n){var r;try{r={defaults:n(154),each:n(87),isFunction:n(37),isPlainObject:n(158),pick:n(161),has:n(93),range:n(162),uniqueId:n(163)}}catch(t){}r||(r=window._),t.exports=r},function(t){t.exports=JSON.parse('{"name":"mermaid","version":"8.8.4","description":"Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.","main":"dist/mermaid.core.js","keywords":["diagram","markdown","flowchart","sequence diagram","gantt","class diagram","git graph"],"scripts":{"build:development":"webpack --progress --colors","build:production":"yarn build:development -p --config webpack.config.prod.babel.js","build":"yarn build:development && yarn build:production","postbuild":"documentation build src/mermaidAPI.js src/config.js --shallow -f md --markdown-toc false > docs/Setup.md","build:watch":"yarn build --watch","minify":"minify ./dist/mermaid.js > ./dist/mermaid.min.js","release":"yarn build","lint":"eslint src","e2e:depr":"yarn lint && jest e2e --config e2e/jest.config.js","cypress":"percy exec -- cypress run","e2e":"start-server-and-test dev http://localhost:9000/ cypress","e2e-upd":"yarn lint && jest e2e -u --config e2e/jest.config.js","dev":"webpack-dev-server --config webpack.config.e2e.js","test":"yarn lint && jest src/.*","test:watch":"jest --watch src","prepublishOnly":"yarn build && yarn test","prepare":"yarn build"},"repository":{"type":"git","url":"https://github.com/knsv/mermaid"},"author":"Knut Sveidqvist","license":"MIT","standard":{"ignore":["**/parser/*.js","dist/**/*.js","cypress/**/*.js"],"globals":["page"]},"dependencies":{"@braintree/sanitize-url":"^3.1.0","d3":"^5.7.0","dagre":"^0.8.4","dagre-d3":"^0.6.4","entity-decode":"^2.0.2","graphlib":"^2.1.7","he":"^1.2.0","khroma":"^1.1.0","minify":"^4.1.1","moment-mini":"^2.22.1","stylis":"^3.5.2"},"devDependencies":{"@babel/core":"^7.2.2","@babel/preset-env":"^7.8.4","@babel/register":"^7.0.0","@percy/cypress":"*","babel-core":"7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^24.9.0","babel-loader":"^8.0.4","coveralls":"^3.0.2","css-loader":"^2.0.1","css-to-string-loader":"^0.1.3","cypress":"4.0.1","documentation":"^12.0.1","eslint":"^6.3.0","eslint-config-prettier":"^6.3.0","eslint-plugin-prettier":"^3.1.0","husky":"^1.2.1","identity-obj-proxy":"^3.0.0","jest":"^24.9.0","jison":"^0.4.18","moment":"^2.23.0","node-sass":"^4.12.0","prettier":"^1.18.2","puppeteer":"^1.17.0","sass-loader":"^7.1.0","start-server-and-test":"^1.10.6","terser-webpack-plugin":"^2.2.2","webpack":"^4.41.2","webpack-bundle-analyzer":"^3.7.0","webpack-cli":"^3.1.2","webpack-dev-server":"^3.4.1","webpack-node-externals":"^1.7.2","yarn-upgrade-all":"^0.5.0"},"files":["dist"],"yarn-upgrade-all":{"ignore":["babel-core"]},"sideEffects":["**/*.css","**/*.scss"],"husky":{"hooks":{"pre-push":"yarn test"}}}')},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=new(n(176).default)({r:0,g:0,b:0,a:0},"transparent");e.default=r},function(t,e,n){var r=n(58),i=n(59);t.exports=function(t,e,n,a){var o=!n;n||(n={});for(var s=-1,c=e.length;++s-1&&t%1==0&&t-1}(s)?s:(n=s.match(a))?(e=n[0],r.test(e)?"about:blank":s):"about:blank"}}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[2,3],n=[1,7],r=[7,12,15,17,19,20,21],i=[7,11,12,15,17,19,20,21],a=[2,20],o=[1,32],s={trace:function(){},yy:{},symbols_:{error:2,start:3,GG:4,":":5,document:6,EOF:7,DIR:8,options:9,body:10,OPT:11,NL:12,line:13,statement:14,COMMIT:15,commit_arg:16,BRANCH:17,ID:18,CHECKOUT:19,MERGE:20,RESET:21,reset_arg:22,STR:23,HEAD:24,reset_parents:25,CARET:26,$accept:0,$end:1},terminals_:{2:"error",4:"GG",5:":",7:"EOF",8:"DIR",11:"OPT",12:"NL",15:"COMMIT",17:"BRANCH",18:"ID",19:"CHECKOUT",20:"MERGE",21:"RESET",23:"STR",24:"HEAD",26:"CARET"},productions_:[0,[3,4],[3,5],[6,0],[6,2],[9,2],[9,1],[10,0],[10,2],[13,2],[13,1],[14,2],[14,2],[14,2],[14,2],[14,2],[16,0],[16,1],[22,2],[22,2],[25,0],[25,2]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 2:return r.setDirection(a[s-3]),a[s-1];case 4:r.setOptions(a[s-1]),this.$=a[s];break;case 5:a[s-1]+=a[s],this.$=a[s-1];break;case 7:this.$=[];break;case 8:a[s-1].push(a[s]),this.$=a[s-1];break;case 9:this.$=a[s-1];break;case 11:r.commit(a[s]);break;case 12:r.branch(a[s]);break;case 13:r.checkout(a[s]);break;case 14:r.merge(a[s]);break;case 15:r.reset(a[s]);break;case 16:this.$="";break;case 17:this.$=a[s];break;case 18:this.$=a[s-1]+":"+a[s];break;case 19:this.$=a[s-1]+":"+r.count,r.count=0;break;case 20:r.count=0;break;case 21:r.count+=1}},table:[{3:1,4:[1,2]},{1:[3]},{5:[1,3],8:[1,4]},{6:5,7:e,9:6,12:n},{5:[1,8]},{7:[1,9]},t(r,[2,7],{10:10,11:[1,11]}),t(i,[2,6]),{6:12,7:e,9:6,12:n},{1:[2,1]},{7:[2,4],12:[1,15],13:13,14:14,15:[1,16],17:[1,17],19:[1,18],20:[1,19],21:[1,20]},t(i,[2,5]),{7:[1,21]},t(r,[2,8]),{12:[1,22]},t(r,[2,10]),{12:[2,16],16:23,23:[1,24]},{18:[1,25]},{18:[1,26]},{18:[1,27]},{18:[1,30],22:28,24:[1,29]},{1:[2,2]},t(r,[2,9]),{12:[2,11]},{12:[2,17]},{12:[2,12]},{12:[2,13]},{12:[2,14]},{12:[2,15]},{12:a,25:31,26:o},{12:a,25:33,26:o},{12:[2,18]},{12:a,25:34,26:o},{12:[2,19]},{12:[2,21]}],defaultActions:{9:[2,1],21:[2,2],23:[2,11],24:[2,17],25:[2,12],26:[2,13],27:[2,14],28:[2,15],31:[2,18],33:[2,19],34:[2,21]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},c={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 12;case 1:case 2:case 3:break;case 4:return 4;case 5:return 15;case 6:return 17;case 7:return 20;case 8:return 21;case 9:return 19;case 10:case 11:return 8;case 12:return 5;case 13:return 26;case 14:this.begin("options");break;case 15:this.popState();break;case 16:return 11;case 17:this.begin("string");break;case 18:this.popState();break;case 19:return 23;case 20:return 18;case 21:return 7}},rules:[/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:gitGraph\b)/i,/^(?:commit\b)/i,/^(?:branch\b)/i,/^(?:merge\b)/i,/^(?:reset\b)/i,/^(?:checkout\b)/i,/^(?:LR\b)/i,/^(?:BT\b)/i,/^(?::)/i,/^(?:\^)/i,/^(?:options\r?\n)/i,/^(?:end\r?\n)/i,/^(?:[^\n]+\r?\n)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[a-zA-Z][-_\.a-zA-Z0-9]*[-_a-zA-Z0-9])/i,/^(?:$)/i],conditions:{options:{rules:[15,16],inclusive:!1},string:{rules:[18,19],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,17,20,21],inclusive:!0}}};function u(){this.yy={}}return s.lexer=c,u.prototype=s,s.Parser=u,new u}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[6,9,10],n={trace:function(){},yy:{},symbols_:{error:2,start:3,info:4,document:5,EOF:6,line:7,statement:8,NL:9,showInfo:10,$accept:0,$end:1},terminals_:{2:"error",4:"info",6:"EOF",9:"NL",10:"showInfo"},productions_:[0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,1]],performAction:function(t,e,n,r,i,a,o){a.length;switch(i){case 1:return r;case 4:break;case 6:r.setInfo(!0)}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8]},{1:[2,1]},t(e,[2,3]),t(e,[2,4]),t(e,[2,5]),t(e,[2,6])],defaultActions:{4:[2,1]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},r={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 4;case 1:return 9;case 2:return"space";case 3:return 10;case 4:return 6;case 5:return"TXT"}},rules:[/^(?:info\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:showInfo\b)/i,/^(?:$)/i,/^(?:.)/i],conditions:{INITIAL:{rules:[0,1,2,3,4,5],inclusive:!0}}};function i(){this.yy={}}return n.lexer=r,i.prototype=n,n.Parser=i,new i}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,4],n=[1,5],r=[1,6],i=[1,7],a=[1,9],o=[1,10,12,19,20,21,22],s=[1,6,10,12,19,20,21,22],c=[19,20,21],u=[1,22],l=[6,19,20,21,22],h={trace:function(){},yy:{},symbols_:{error:2,start:3,eol:4,directive:5,PIE:6,document:7,line:8,statement:9,txt:10,value:11,title:12,title_value:13,openDirective:14,typeDirective:15,closeDirective:16,":":17,argDirective:18,NEWLINE:19,";":20,EOF:21,open_directive:22,type_directive:23,arg_directive:24,close_directive:25,$accept:0,$end:1},terminals_:{2:"error",6:"PIE",10:"txt",11:"value",12:"title",13:"title_value",17:":",19:"NEWLINE",20:";",21:"EOF",22:"open_directive",23:"type_directive",24:"arg_directive",25:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[7,0],[7,2],[8,2],[9,0],[9,2],[9,2],[9,1],[5,3],[5,5],[4,1],[4,1],[4,1],[14,1],[15,1],[18,1],[16,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 6:this.$=a[s-1];break;case 8:r.addSection(a[s-1],r.cleanupValue(a[s]));break;case 9:this.$=a[s].trim(),r.setTitle(this.$);break;case 16:r.parseDirective("%%{","open_directive");break;case 17:r.parseDirective(a[s],"type_directive");break;case 18:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 19:r.parseDirective("}%%","close_directive","pie")}},table:[{3:1,4:2,5:3,6:e,14:8,19:n,20:r,21:i,22:a},{1:[3]},{3:10,4:2,5:3,6:e,14:8,19:n,20:r,21:i,22:a},{3:11,4:2,5:3,6:e,14:8,19:n,20:r,21:i,22:a},t(o,[2,4],{7:12}),t(s,[2,13]),t(s,[2,14]),t(s,[2,15]),{15:13,23:[1,14]},{23:[2,16]},{1:[2,1]},{1:[2,2]},t(c,[2,7],{14:8,8:15,9:16,5:19,1:[2,3],10:[1,17],12:[1,18],22:a}),{16:20,17:[1,21],25:u},t([17,25],[2,17]),t(o,[2,5]),{4:23,19:n,20:r,21:i},{11:[1,24]},{13:[1,25]},t(c,[2,10]),t(l,[2,11]),{18:26,24:[1,27]},t(l,[2,19]),t(o,[2,6]),t(c,[2,8]),t(c,[2,9]),{16:28,25:u},{25:[2,18]},t(l,[2,12])],defaultActions:{9:[2,16],10:[2,1],11:[2,2],27:[2,18]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},f={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),22;case 1:return this.begin("type_directive"),23;case 2:return this.popState(),this.begin("arg_directive"),17;case 3:return this.popState(),this.popState(),25;case 4:return 24;case 5:case 6:break;case 7:return 19;case 8:case 9:break;case 10:return this.begin("title"),12;case 11:return this.popState(),"title_value";case 12:this.begin("string");break;case 13:this.popState();break;case 14:return"txt";case 15:return 6;case 16:return"value";case 17:return 21}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n\r]+)/i,/^(?:%%[^\n]*)/i,/^(?:[\s]+)/i,/^(?:title\b)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:pie\b)/i,/^(?::[\s]*[\d]+(?:\.[\d]+)?)/i,/^(?:$)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},title:{rules:[11],inclusive:!1},string:{rules:[13,14],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,12,15,16,17],inclusive:!0}}};function d(){this.yy={}}return h.lexer=f,d.prototype=h,h.Parser=d,new d}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,23,37],i=[1,17],a=[1,20],o=[1,25],s=[1,26],c=[1,27],u=[1,28],l=[1,37],h=[23,34,35],f=[4,6,9,11,23,37],d=[30,31,32,33],p=[22,27],g={trace:function(){},yy:{},symbols_:{error:2,start:3,ER_DIAGRAM:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,entityName:17,relSpec:18,role:19,BLOCK_START:20,attributes:21,BLOCK_STOP:22,ALPHANUM:23,attribute:24,attributeType:25,attributeName:26,ATTRIBUTE_WORD:27,cardinality:28,relType:29,ZERO_OR_ONE:30,ZERO_OR_MORE:31,ONE_OR_MORE:32,ONLY_ONE:33,NON_IDENTIFYING:34,IDENTIFYING:35,WORD:36,open_directive:37,type_directive:38,arg_directive:39,close_directive:40,$accept:0,$end:1},terminals_:{2:"error",4:"ER_DIAGRAM",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",20:"BLOCK_START",22:"BLOCK_STOP",23:"ALPHANUM",27:"ATTRIBUTE_WORD",30:"ZERO_OR_ONE",31:"ZERO_OR_MORE",32:"ONE_OR_MORE",33:"ONLY_ONE",34:"NON_IDENTIFYING",35:"IDENTIFYING",36:"WORD",37:"open_directive",38:"type_directive",39:"arg_directive",40:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,5],[10,4],[10,3],[10,1],[17,1],[21,1],[21,2],[24,2],[25,1],[26,1],[18,3],[28,1],[28,1],[28,1],[28,1],[29,1],[29,1],[19,1],[19,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:break;case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 12:r.addEntity(a[s-4]),r.addEntity(a[s-2]),r.addRelationship(a[s-4],a[s],a[s-2],a[s-3]);break;case 13:r.addEntity(a[s-3]),r.addAttributes(a[s-3],a[s-1]);break;case 14:r.addEntity(a[s-2]);break;case 15:r.addEntity(a[s]);break;case 16:this.$=a[s];break;case 17:this.$=[a[s]];break;case 18:a[s].push(a[s-1]),this.$=a[s];break;case 19:this.$={attributeType:a[s-1],attributeName:a[s]};break;case 20:case 21:this.$=a[s];break;case 22:this.$={cardA:a[s],relType:a[s-1],cardB:a[s-2]};break;case 23:this.$=r.Cardinality.ZERO_OR_ONE;break;case 24:this.$=r.Cardinality.ZERO_OR_MORE;break;case 25:this.$=r.Cardinality.ONE_OR_MORE;break;case 26:this.$=r.Cardinality.ONLY_ONE;break;case 27:this.$=r.Identification.NON_IDENTIFYING;break;case 28:this.$=r.Identification.IDENTIFYING;break;case 29:this.$=a[s].replace(/"/g,"");break;case 30:this.$=a[s];break;case 31:r.parseDirective("%%{","open_directive");break;case 32:r.parseDirective(a[s],"type_directive");break;case 33:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 34:r.parseDirective("}%%","close_directive","er")}},table:[{3:1,4:e,7:3,12:4,37:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,37:n},{13:8,38:[1,9]},{38:[2,31]},{6:[1,10],7:15,8:11,9:[1,12],10:13,11:[1,14],12:4,17:16,23:i,37:n},{1:[2,2]},{14:18,15:[1,19],40:a},t([15,40],[2,32]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:15,10:21,12:4,17:16,23:i,37:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,15],{18:22,28:24,20:[1,23],30:o,31:s,32:c,33:u}),t([6,9,11,15,20,23,30,31,32,33,37],[2,16]),{11:[1,29]},{16:30,39:[1,31]},{11:[2,34]},t(r,[2,5]),{17:32,23:i},{21:33,22:[1,34],24:35,25:36,27:l},{29:38,34:[1,39],35:[1,40]},t(h,[2,23]),t(h,[2,24]),t(h,[2,25]),t(h,[2,26]),t(f,[2,9]),{14:41,40:a},{40:[2,33]},{15:[1,42]},{22:[1,43]},t(r,[2,14]),{21:44,22:[2,17],24:35,25:36,27:l},{26:45,27:[1,46]},{27:[2,20]},{28:47,30:o,31:s,32:c,33:u},t(d,[2,27]),t(d,[2,28]),{11:[1,48]},{19:49,23:[1,51],36:[1,50]},t(r,[2,13]),{22:[2,18]},t(p,[2,19]),t(p,[2,21]),{23:[2,22]},t(f,[2,10]),t(r,[2,12]),t(r,[2,29]),t(r,[2,30])],defaultActions:{5:[2,31],7:[2,2],20:[2,34],31:[2,33],37:[2,20],44:[2,18],47:[2,22]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},y={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),37;case 1:return this.begin("type_directive"),38;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),40;case 4:return 39;case 5:case 6:break;case 7:return 11;case 8:break;case 9:return 9;case 10:return 36;case 11:return 4;case 12:return this.begin("block"),20;case 13:break;case 14:return 27;case 15:break;case 16:return this.popState(),22;case 17:return e.yytext[0];case 18:return 30;case 19:return 31;case 20:return 32;case 21:return 33;case 22:return 30;case 23:return 31;case 24:return 32;case 25:return 34;case 26:return 35;case 27:case 28:return 34;case 29:return 23;case 30:return e.yytext[0];case 31:return 6}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:[\s]+)/i,/^(?:"[^"]*")/i,/^(?:erDiagram\b)/i,/^(?:\{)/i,/^(?:\s+)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:[\n]+)/i,/^(?:\})/i,/^(?:.)/i,/^(?:\|o\b)/i,/^(?:\}o\b)/i,/^(?:\}\|)/i,/^(?:\|\|)/i,/^(?:o\|)/i,/^(?:o\{)/i,/^(?:\|\{)/i,/^(?:\.\.)/i,/^(?:--)/i,/^(?:\.-)/i,/^(?:-\.)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:.)/i,/^(?:$)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},block:{rules:[13,14,15,16,17],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,18,19,20,21,22,23,24,25,26,27,28,29,30,31],inclusive:!0}}};function v(){this.yy={}}return g.lexer=y,v.prototype=g,g.Parser=v,new v}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){"use strict";var r;Object.defineProperty(e,"__esModule",{value:!0}),function(t){t[t.ALL=0]="ALL",t[t.RGB=1]="RGB",t[t.HSL=2]="HSL"}(r||(r={})),e.TYPE=r},function(t,e,n){"use strict";var r=n(10);t.exports=i;function i(t){this._isDirected=!r.has(t,"directed")||t.directed,this._isMultigraph=!!r.has(t,"multigraph")&&t.multigraph,this._isCompound=!!r.has(t,"compound")&&t.compound,this._label=void 0,this._defaultNodeLabelFn=r.constant(void 0),this._defaultEdgeLabelFn=r.constant(void 0),this._nodes={},this._isCompound&&(this._parent={},this._children={},this._children["\0"]={}),this._in={},this._preds={},this._out={},this._sucs={},this._edgeObjs={},this._edgeLabels={}}function a(t,e){t[e]?t[e]++:t[e]=1}function o(t,e){--t[e]||delete t[e]}function s(t,e,n,i){var a=""+e,o=""+n;if(!t&&a>o){var s=a;a=o,o=s}return a+""+o+""+(r.isUndefined(i)?"\0":i)}function c(t,e,n,r){var i=""+e,a=""+n;if(!t&&i>a){var o=i;i=a,a=o}var s={v:i,w:a};return r&&(s.name=r),s}function u(t,e){return s(t,e.v,e.w,e.name)}i.prototype._nodeCount=0,i.prototype._edgeCount=0,i.prototype.isDirected=function(){return this._isDirected},i.prototype.isMultigraph=function(){return this._isMultigraph},i.prototype.isCompound=function(){return this._isCompound},i.prototype.setGraph=function(t){return this._label=t,this},i.prototype.graph=function(){return this._label},i.prototype.setDefaultNodeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultNodeLabelFn=t,this},i.prototype.nodeCount=function(){return this._nodeCount},i.prototype.nodes=function(){return r.keys(this._nodes)},i.prototype.sources=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._in[e])}))},i.prototype.sinks=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._out[e])}))},i.prototype.setNodes=function(t,e){var n=arguments,i=this;return r.each(t,(function(t){n.length>1?i.setNode(t,e):i.setNode(t)})),this},i.prototype.setNode=function(t,e){return r.has(this._nodes,t)?(arguments.length>1&&(this._nodes[t]=e),this):(this._nodes[t]=arguments.length>1?e:this._defaultNodeLabelFn(t),this._isCompound&&(this._parent[t]="\0",this._children[t]={},this._children["\0"][t]=!0),this._in[t]={},this._preds[t]={},this._out[t]={},this._sucs[t]={},++this._nodeCount,this)},i.prototype.node=function(t){return this._nodes[t]},i.prototype.hasNode=function(t){return r.has(this._nodes,t)},i.prototype.removeNode=function(t){var e=this;if(r.has(this._nodes,t)){var n=function(t){e.removeEdge(e._edgeObjs[t])};delete this._nodes[t],this._isCompound&&(this._removeFromParentsChildList(t),delete this._parent[t],r.each(this.children(t),(function(t){e.setParent(t)})),delete this._children[t]),r.each(r.keys(this._in[t]),n),delete this._in[t],delete this._preds[t],r.each(r.keys(this._out[t]),n),delete this._out[t],delete this._sucs[t],--this._nodeCount}return this},i.prototype.setParent=function(t,e){if(!this._isCompound)throw new Error("Cannot set parent in a non-compound graph");if(r.isUndefined(e))e="\0";else{for(var n=e+="";!r.isUndefined(n);n=this.parent(n))if(n===t)throw new Error("Setting "+e+" as parent of "+t+" would create a cycle");this.setNode(e)}return this.setNode(t),this._removeFromParentsChildList(t),this._parent[t]=e,this._children[e][t]=!0,this},i.prototype._removeFromParentsChildList=function(t){delete this._children[this._parent[t]][t]},i.prototype.parent=function(t){if(this._isCompound){var e=this._parent[t];if("\0"!==e)return e}},i.prototype.children=function(t){if(r.isUndefined(t)&&(t="\0"),this._isCompound){var e=this._children[t];if(e)return r.keys(e)}else{if("\0"===t)return this.nodes();if(this.hasNode(t))return[]}},i.prototype.predecessors=function(t){var e=this._preds[t];if(e)return r.keys(e)},i.prototype.successors=function(t){var e=this._sucs[t];if(e)return r.keys(e)},i.prototype.neighbors=function(t){var e=this.predecessors(t);if(e)return r.union(e,this.successors(t))},i.prototype.isLeaf=function(t){return 0===(this.isDirected()?this.successors(t):this.neighbors(t)).length},i.prototype.filterNodes=function(t){var e=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});e.setGraph(this.graph());var n=this;r.each(this._nodes,(function(n,r){t(r)&&e.setNode(r,n)})),r.each(this._edgeObjs,(function(t){e.hasNode(t.v)&&e.hasNode(t.w)&&e.setEdge(t,n.edge(t))}));var i={};return this._isCompound&&r.each(e.nodes(),(function(t){e.setParent(t,function t(r){var a=n.parent(r);return void 0===a||e.hasNode(a)?(i[r]=a,a):a in i?i[a]:t(a)}(t))})),e},i.prototype.setDefaultEdgeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultEdgeLabelFn=t,this},i.prototype.edgeCount=function(){return this._edgeCount},i.prototype.edges=function(){return r.values(this._edgeObjs)},i.prototype.setPath=function(t,e){var n=this,i=arguments;return r.reduce(t,(function(t,r){return i.length>1?n.setEdge(t,r,e):n.setEdge(t,r),r})),this},i.prototype.setEdge=function(){var t,e,n,i,o=!1,u=arguments[0];"object"==typeof u&&null!==u&&"v"in u?(t=u.v,e=u.w,n=u.name,2===arguments.length&&(i=arguments[1],o=!0)):(t=u,e=arguments[1],n=arguments[3],arguments.length>2&&(i=arguments[2],o=!0)),t=""+t,e=""+e,r.isUndefined(n)||(n=""+n);var l=s(this._isDirected,t,e,n);if(r.has(this._edgeLabels,l))return o&&(this._edgeLabels[l]=i),this;if(!r.isUndefined(n)&&!this._isMultigraph)throw new Error("Cannot set a named edge when isMultigraph = false");this.setNode(t),this.setNode(e),this._edgeLabels[l]=o?i:this._defaultEdgeLabelFn(t,e,n);var h=c(this._isDirected,t,e,n);return t=h.v,e=h.w,Object.freeze(h),this._edgeObjs[l]=h,a(this._preds[e],t),a(this._sucs[t],e),this._in[e][l]=h,this._out[t][l]=h,this._edgeCount++,this},i.prototype.edge=function(t,e,n){var r=1===arguments.length?u(this._isDirected,arguments[0]):s(this._isDirected,t,e,n);return this._edgeLabels[r]},i.prototype.hasEdge=function(t,e,n){var i=1===arguments.length?u(this._isDirected,arguments[0]):s(this._isDirected,t,e,n);return r.has(this._edgeLabels,i)},i.prototype.removeEdge=function(t,e,n){var r=1===arguments.length?u(this._isDirected,arguments[0]):s(this._isDirected,t,e,n),i=this._edgeObjs[r];return i&&(t=i.v,e=i.w,delete this._edgeLabels[r],delete this._edgeObjs[r],o(this._preds[e],t),o(this._sucs[t],e),delete this._in[e][r],delete this._out[t][r],this._edgeCount--),this},i.prototype.inEdges=function(t,e){var n=this._in[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.v===e})):i}},i.prototype.outEdges=function(t,e){var n=this._out[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.w===e})):i}},i.prototype.nodeEdges=function(t,e){var n=this.inEdges(t,e);if(n)return n.concat(this.outEdges(t,e))}},function(t,e,n){var r=n(33)(n(16),"Map");t.exports=r},function(t,e,n){var r=n(217),i=n(224),a=n(226),o=n(227),s=n(228);function c(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e-1&&t%1==0&&t<=9007199254740991}},function(t,e,n){(function(t){var r=n(109),i=e&&!e.nodeType&&e,a=i&&"object"==typeof t&&t&&!t.nodeType&&t,o=a&&a.exports===i&&r.process,s=function(){try{var t=a&&a.require&&a.require("util").types;return t||o&&o.binding&&o.binding("util")}catch(t){}}();t.exports=s}).call(this,n(7)(t))},function(t,e,n){var r=n(62),i=n(234),a=Object.prototype.hasOwnProperty;t.exports=function(t){if(!r(t))return i(t);var e=[];for(var n in Object(t))a.call(t,n)&&"constructor"!=n&&e.push(n);return e}},function(t,e,n){var r=n(116),i=n(117),a=Object.prototype.propertyIsEnumerable,o=Object.getOwnPropertySymbols,s=o?function(t){return null==t?[]:(t=Object(t),r(o(t),(function(e){return a.call(t,e)})))}:i;t.exports=s},function(t,e){t.exports=function(t,e){for(var n=-1,r=e.length,i=t.length;++n0&&a(l)?n>1?t(l,n-1,a,o,s):r(s,l):o||(s[s.length]=l)}return s}},function(t,e,n){var r=n(42);t.exports=function(t,e,n){for(var i=-1,a=t.length;++i4,u=c?1:17,l=c?8:4,h=s?0:-1,f=c?255:15;return i.default.set({r:(r>>l*(h+3)&f)*u,g:(r>>l*(h+2)&f)*u,b:(r>>l*(h+1)&f)*u,a:s?(r&f)*u/255:1},t)}}},stringify:function(t){return t.a<1?"#"+a.DEC2HEX[Math.round(t.r)]+a.DEC2HEX[Math.round(t.g)]+a.DEC2HEX[Math.round(t.b)]+r.default.unit.frac2hex(t.a):"#"+a.DEC2HEX[Math.round(t.r)]+a.DEC2HEX[Math.round(t.g)]+a.DEC2HEX[Math.round(t.b)]}};e.default=o},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a=n(15);e.default=function(t,e,n,o){void 0===o&&(o=1);var s=i.default.set({h:r.default.channel.clamp.h(t),s:r.default.channel.clamp.s(e),l:r.default.channel.clamp.l(n),a:r.default.channel.clamp.a(o)});return a.default.stringify(s)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"a")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15);e.default=function(t){var e=i.default.parse(t),n=e.r,a=e.g,o=e.b,s=.2126*r.default.channel.toLinear(n)+.7152*r.default.channel.toLinear(a)+.0722*r.default.channel.toLinear(o);return r.default.lang.round(s)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(102);e.default=function(t){return r.default(t)>=.5}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"a",e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"a",-e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15),i=n(52);e.default=function(t,e){var n=r.default.parse(t),a={};for(var o in e)e[o]&&(a[o]=n[o]+e[o]);return i.default(t,a)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15),i=n(51);e.default=function(t,e,n){void 0===n&&(n=50);var a=r.default.parse(t),o=a.r,s=a.g,c=a.b,u=a.a,l=r.default.parse(e),h=l.r,f=l.g,d=l.b,p=l.a,g=n/100,y=2*g-1,v=u-p,m=((y*v==-1?y:(y+v)/(1+y*v))+1)/2,b=1-m,x=o*m+h*b,_=s*m+f*b,k=c*m+d*b,w=u*g+p*(1-g);return i.default(x,_,k,w)}},function(t,e,n){var r=n(53),i=n(79),a=n(58),o=n(229),s=n(235),c=n(114),u=n(115),l=n(238),h=n(239),f=n(119),d=n(240),p=n(41),g=n(244),y=n(245),v=n(124),m=n(5),b=n(39),x=n(249),_=n(11),k=n(251),w=n(30),E={};E["[object Arguments]"]=E["[object Array]"]=E["[object ArrayBuffer]"]=E["[object DataView]"]=E["[object Boolean]"]=E["[object Date]"]=E["[object Float32Array]"]=E["[object Float64Array]"]=E["[object Int8Array]"]=E["[object Int16Array]"]=E["[object Int32Array]"]=E["[object Map]"]=E["[object Number]"]=E["[object Object]"]=E["[object RegExp]"]=E["[object Set]"]=E["[object String]"]=E["[object Symbol]"]=E["[object Uint8Array]"]=E["[object Uint8ClampedArray]"]=E["[object Uint16Array]"]=E["[object Uint32Array]"]=!0,E["[object Error]"]=E["[object Function]"]=E["[object WeakMap]"]=!1,t.exports=function t(e,n,T,C,A,S){var M,O=1&n,D=2&n,N=4&n;if(T&&(M=A?T(e,C,A,S):T(e)),void 0!==M)return M;if(!_(e))return e;var B=m(e);if(B){if(M=g(e),!O)return u(e,M)}else{var L=p(e),F="[object Function]"==L||"[object GeneratorFunction]"==L;if(b(e))return c(e,O);if("[object Object]"==L||"[object Arguments]"==L||F&&!A){if(M=D||F?{}:v(e),!O)return D?h(e,s(M,e)):l(e,o(M,e))}else{if(!E[L])return A?e:{};M=y(e,L,O)}}S||(S=new r);var P=S.get(e);if(P)return P;S.set(e,M),k(e)?e.forEach((function(r){M.add(t(r,n,T,r,e,S))})):x(e)&&e.forEach((function(r,i){M.set(i,t(r,n,T,i,e,S))}));var I=N?D?d:f:D?keysIn:w,j=B?void 0:I(e);return i(j||e,(function(r,i){j&&(r=e[i=r]),a(M,i,t(r,n,T,i,e,S))})),M}},function(t,e,n){(function(e){var n="object"==typeof e&&e&&e.Object===Object&&e;t.exports=n}).call(this,n(211))},function(t,e){var n=Function.prototype.toString;t.exports=function(t){if(null!=t){try{return n.call(t)}catch(t){}try{return t+""}catch(t){}}return""}},function(t,e,n){var r=n(33),i=function(){try{var t=r(Object,"defineProperty");return t({},"",{}),t}catch(t){}}();t.exports=i},function(t,e,n){var r=n(230),i=n(47),a=n(5),o=n(39),s=n(60),c=n(48),u=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=a(t),l=!n&&i(t),h=!n&&!l&&o(t),f=!n&&!l&&!h&&c(t),d=n||l||h||f,p=d?r(t.length,String):[],g=p.length;for(var y in t)!e&&!u.call(t,y)||d&&("length"==y||h&&("offset"==y||"parent"==y)||f&&("buffer"==y||"byteLength"==y||"byteOffset"==y)||s(y,g))||p.push(y);return p}},function(t,e){t.exports=function(t,e){return function(n){return t(e(n))}}},function(t,e,n){(function(t){var r=n(16),i=e&&!e.nodeType&&e,a=i&&"object"==typeof t&&t&&!t.nodeType&&t,o=a&&a.exports===i?r.Buffer:void 0,s=o?o.allocUnsafe:void 0;t.exports=function(t,e){if(e)return t.slice();var n=t.length,r=s?s(n):new t.constructor(n);return t.copy(r),r}}).call(this,n(7)(t))},function(t,e){t.exports=function(t,e){var n=-1,r=t.length;for(e||(e=Array(r));++nl))return!1;var f=c.get(t);if(f&&c.get(e))return f==e;var d=-1,p=!0,g=2&n?new r:void 0;for(c.set(t,e),c.set(e,t);++d0&&(a=c.removeMin(),(o=s[a]).distance!==Number.POSITIVE_INFINITY);)r(a).forEach(u);return s}(t,String(e),n||a,r||function(e){return t.outEdges(e)})};var a=r.constant(1)},function(t,e,n){var r=n(10);function i(){this._arr=[],this._keyIndices={}}t.exports=i,i.prototype.size=function(){return this._arr.length},i.prototype.keys=function(){return this._arr.map((function(t){return t.key}))},i.prototype.has=function(t){return r.has(this._keyIndices,t)},i.prototype.priority=function(t){var e=this._keyIndices[t];if(void 0!==e)return this._arr[e].priority},i.prototype.min=function(){if(0===this.size())throw new Error("Queue underflow");return this._arr[0].key},i.prototype.add=function(t,e){var n=this._keyIndices;if(t=String(t),!r.has(n,t)){var i=this._arr,a=i.length;return n[t]=a,i.push({key:t,priority:e}),this._decrease(a),!0}return!1},i.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var t=this._arr.pop();return delete this._keyIndices[t.key],this._heapify(0),t.key},i.prototype.decrease=function(t,e){var n=this._keyIndices[t];if(e>this._arr[n].priority)throw new Error("New priority is greater than current priority. Key: "+t+" Old: "+this._arr[n].priority+" New: "+e);this._arr[n].priority=e,this._decrease(n)},i.prototype._heapify=function(t){var e=this._arr,n=2*t,r=n+1,i=t;n>1].priority2?e[2]:void 0;for(u&&a(e[0],e[1],u)&&(r=1);++n1&&o.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,a=Math.sqrt(r*r+i*i),o=e.x-n.x,s=e.y-n.y,c=Math.sqrt(o*o+s*s);return aMath.abs(o)*u?(s<0&&(u=-u),n=0===s?0:u*o/s,r=u):(o<0&&(c=-c),n=c,r=0===o?0:c*s/o);return{x:i+n,y:a+r}}},function(t,e,n){t.exports=function t(e){"use strict";var n=/^\0+/g,r=/[\0\r\f]/g,i=/: */g,a=/zoo|gra/,o=/([,: ])(transform)/g,s=/,+\s*(?![^(]*[)])/g,c=/ +\s*(?![^(]*[)])/g,u=/ *[\0] */g,l=/,\r+?/g,h=/([\t\r\n ])*\f?&/g,f=/:global\(((?:[^\(\)\[\]]*|\[.*\]|\([^\(\)]*\))*)\)/g,d=/\W+/g,p=/@(k\w+)\s*(\S*)\s*/,g=/::(place)/g,y=/:(read-only)/g,v=/\s+(?=[{\];=:>])/g,m=/([[}=:>])\s+/g,b=/(\{[^{]+?);(?=\})/g,x=/\s{2,}/g,_=/([^\(])(:+) */g,k=/[svh]\w+-[tblr]{2}/,w=/\(\s*(.*)\s*\)/g,E=/([\s\S]*?);/g,T=/-self|flex-/g,C=/[^]*?(:[rp][el]a[\w-]+)[^]*/,A=/stretch|:\s*\w+\-(?:conte|avail)/,S=/([^-])(image-set\()/,M="-webkit-",O="-moz-",D="-ms-",N=1,B=1,L=0,F=1,P=1,I=1,j=0,R=0,Y=0,z=[],U=[],$=0,W=null,H=0,V=1,G="",q="",X="";function Z(t,e,i,a,o){for(var s,c,l=0,h=0,f=0,d=0,v=0,m=0,b=0,x=0,k=0,E=0,T=0,C=0,A=0,S=0,O=0,D=0,j=0,U=0,W=0,K=i.length,it=K-1,at="",ot="",st="",ct="",ut="",lt="";O0&&(ot=ot.replace(r,"")),ot.trim().length>0)){switch(b){case 32:case 9:case 59:case 13:case 10:break;default:ot+=i.charAt(O)}b=59}if(1===j)switch(b){case 123:case 125:case 59:case 34:case 39:case 40:case 41:case 44:j=0;case 9:case 13:case 10:case 32:break;default:for(j=0,W=O,v=b,O--,b=59;W0&&(++O,b=v);case 123:W=K}}switch(b){case 123:for(v=(ot=ot.trim()).charCodeAt(0),T=1,W=++O;O0&&(ot=ot.replace(r,"")),m=ot.charCodeAt(1)){case 100:case 109:case 115:case 45:s=e;break;default:s=z}if(W=(st=Z(e,s,st,m,o+1)).length,Y>0&&0===W&&(W=ot.length),$>0&&(c=nt(3,st,s=J(z,ot,U),e,B,N,W,m,o,a),ot=s.join(""),void 0!==c&&0===(W=(st=c.trim()).length)&&(m=0,st="")),W>0)switch(m){case 115:ot=ot.replace(w,et);case 100:case 109:case 45:st=ot+"{"+st+"}";break;case 107:st=(ot=ot.replace(p,"$1 $2"+(V>0?G:"")))+"{"+st+"}",st=1===P||2===P&&tt("@"+st,3)?"@"+M+st+"@"+st:"@"+st;break;default:st=ot+st,112===a&&(ct+=st,st="")}else st="";break;default:st=Z(e,J(e,ot,U),st,a,o+1)}ut+=st,C=0,j=0,S=0,D=0,U=0,A=0,ot="",st="",b=i.charCodeAt(++O);break;case 125:case 59:if((W=(ot=(D>0?ot.replace(r,""):ot).trim()).length)>1)switch(0===S&&(45===(v=ot.charCodeAt(0))||v>96&&v<123)&&(W=(ot=ot.replace(" ",":")).length),$>0&&void 0!==(c=nt(1,ot,e,t,B,N,ct.length,a,o,a))&&0===(W=(ot=c.trim()).length)&&(ot="\0\0"),v=ot.charCodeAt(0),m=ot.charCodeAt(1),v){case 0:break;case 64:if(105===m||99===m){lt+=ot+i.charAt(O);break}default:if(58===ot.charCodeAt(W-1))break;ct+=Q(ot,v,m,ot.charCodeAt(2))}C=0,j=0,S=0,D=0,U=0,ot="",b=i.charCodeAt(++O)}}switch(b){case 13:case 10:if(h+d+f+l+R===0)switch(E){case 41:case 39:case 34:case 64:case 126:case 62:case 42:case 43:case 47:case 45:case 58:case 44:case 59:case 123:case 125:break;default:S>0&&(j=1)}47===h?h=0:F+C===0&&107!==a&&ot.length>0&&(D=1,ot+="\0"),$*H>0&&nt(0,ot,e,t,B,N,ct.length,a,o,a),N=1,B++;break;case 59:case 125:if(h+d+f+l===0){N++;break}default:switch(N++,at=i.charAt(O),b){case 9:case 32:if(d+l+h===0)switch(x){case 44:case 58:case 9:case 32:at="";break;default:32!==b&&(at=" ")}break;case 0:at="\\0";break;case 12:at="\\f";break;case 11:at="\\v";break;case 38:d+h+l===0&&F>0&&(U=1,D=1,at="\f"+at);break;case 108:if(d+h+l+L===0&&S>0)switch(O-S){case 2:112===x&&58===i.charCodeAt(O-3)&&(L=x);case 8:111===k&&(L=k)}break;case 58:d+h+l===0&&(S=O);break;case 44:h+f+d+l===0&&(D=1,at+="\r");break;case 34:case 39:0===h&&(d=d===b?0:0===d?b:d);break;case 91:d+h+f===0&&l++;break;case 93:d+h+f===0&&l--;break;case 41:d+h+l===0&&f--;break;case 40:if(d+h+l===0){if(0===C)switch(2*x+3*k){case 533:break;default:T=0,C=1}f++}break;case 64:h+f+d+l+S+A===0&&(A=1);break;case 42:case 47:if(d+l+f>0)break;switch(h){case 0:switch(2*b+3*i.charCodeAt(O+1)){case 235:h=47;break;case 220:W=O,h=42}break;case 42:47===b&&42===x&&W+2!==O&&(33===i.charCodeAt(W+2)&&(ct+=i.substring(W,O+1)),at="",h=0)}}if(0===h){if(F+d+l+A===0&&107!==a&&59!==b)switch(b){case 44:case 126:case 62:case 43:case 41:case 40:if(0===C){switch(x){case 9:case 32:case 10:case 13:at+="\0";break;default:at="\0"+at+(44===b?"":"\0")}D=1}else switch(b){case 40:S+7===O&&108===x&&(S=0),C=++T;break;case 41:0==(C=--T)&&(D=1,at+="\0")}break;case 9:case 32:switch(x){case 0:case 123:case 125:case 59:case 44:case 12:case 9:case 32:case 10:case 13:break;default:0===C&&(D=1,at+="\0")}}ot+=at,32!==b&&9!==b&&(E=b)}}k=x,x=b,O++}if(W=ct.length,Y>0&&0===W&&0===ut.length&&0===e[0].length==0&&(109!==a||1===e.length&&(F>0?q:X)===e[0])&&(W=e.join(",").length+2),W>0){if(s=0===F&&107!==a?function(t){for(var e,n,i=0,a=t.length,o=Array(a);i1)){if(f=c.charCodeAt(c.length-1),d=n.charCodeAt(0),e="",0!==l)switch(f){case 42:case 126:case 62:case 43:case 32:case 40:break;default:e=" "}switch(d){case 38:n=e+q;case 126:case 62:case 43:case 32:case 41:case 40:break;case 91:n=e+n+q;break;case 58:switch(2*n.charCodeAt(1)+3*n.charCodeAt(2)){case 530:if(I>0){n=e+n.substring(8,h-1);break}default:(l<1||s[l-1].length<1)&&(n=e+q+n)}break;case 44:e="";default:n=h>1&&n.indexOf(":")>0?e+n.replace(_,"$1"+q+"$2"):e+n+q}c+=n}o[i]=c.replace(r,"").trim()}return o}(e):e,$>0&&void 0!==(c=nt(2,ct,s,t,B,N,W,a,o,a))&&0===(ct=c).length)return lt+ct+ut;if(ct=s.join(",")+"{"+ct+"}",P*L!=0){switch(2!==P||tt(ct,2)||(L=0),L){case 111:ct=ct.replace(y,":-moz-$1")+ct;break;case 112:ct=ct.replace(g,"::-webkit-input-$1")+ct.replace(g,"::-moz-$1")+ct.replace(g,":-ms-input-$1")+ct}L=0}}return lt+ct+ut}function J(t,e,n){var r=e.trim().split(l),i=r,a=r.length,o=t.length;switch(o){case 0:case 1:for(var s=0,c=0===o?"":t[0]+" ";s0&&F>0)return i.replace(f,"$1").replace(h,"$1"+X);break;default:return t.trim()+i.replace(h,"$1"+t.trim())}default:if(n*F>0&&i.indexOf("\f")>0)return i.replace(h,(58===t.charCodeAt(0)?"":"$1")+t.trim())}return t+i}function Q(t,e,n,r){var u,l=0,h=t+";",f=2*e+3*n+4*r;if(944===f)return function(t){var e=t.length,n=t.indexOf(":",9)+1,r=t.substring(0,n).trim(),i=t.substring(n,e-1).trim();switch(t.charCodeAt(9)*V){case 0:break;case 45:if(110!==t.charCodeAt(10))break;default:var a=i.split((i="",s)),o=0;for(n=0,e=a.length;o64&&h<90||h>96&&h<123||95===h||45===h&&45!==u.charCodeAt(1)))switch(isNaN(parseFloat(u))+(-1!==u.indexOf("("))){case 1:switch(u){case"infinite":case"alternate":case"backwards":case"running":case"normal":case"forwards":case"both":case"none":case"linear":case"ease":case"ease-in":case"ease-out":case"ease-in-out":case"paused":case"reverse":case"alternate-reverse":case"inherit":case"initial":case"unset":case"step-start":case"step-end":break;default:u+=G}}l[n++]=u}i+=(0===o?"":",")+l.join(" ")}}return i=r+i+";",1===P||2===P&&tt(i,1)?M+i+i:i}(h);if(0===P||2===P&&!tt(h,1))return h;switch(f){case 1015:return 97===h.charCodeAt(10)?M+h+h:h;case 951:return 116===h.charCodeAt(3)?M+h+h:h;case 963:return 110===h.charCodeAt(5)?M+h+h:h;case 1009:if(100!==h.charCodeAt(4))break;case 969:case 942:return M+h+h;case 978:return M+h+O+h+h;case 1019:case 983:return M+h+O+h+D+h+h;case 883:return 45===h.charCodeAt(8)?M+h+h:h.indexOf("image-set(",11)>0?h.replace(S,"$1-webkit-$2")+h:h;case 932:if(45===h.charCodeAt(4))switch(h.charCodeAt(5)){case 103:return M+"box-"+h.replace("-grow","")+M+h+D+h.replace("grow","positive")+h;case 115:return M+h+D+h.replace("shrink","negative")+h;case 98:return M+h+D+h.replace("basis","preferred-size")+h}return M+h+D+h+h;case 964:return M+h+D+"flex-"+h+h;case 1023:if(99!==h.charCodeAt(8))break;return u=h.substring(h.indexOf(":",15)).replace("flex-","").replace("space-between","justify"),M+"box-pack"+u+M+h+D+"flex-pack"+u+h;case 1005:return a.test(h)?h.replace(i,":"+M)+h.replace(i,":"+O)+h:h;case 1e3:switch(l=(u=h.substring(13).trim()).indexOf("-")+1,u.charCodeAt(0)+u.charCodeAt(l)){case 226:u=h.replace(k,"tb");break;case 232:u=h.replace(k,"tb-rl");break;case 220:u=h.replace(k,"lr");break;default:return h}return M+h+D+u+h;case 1017:if(-1===h.indexOf("sticky",9))return h;case 975:switch(l=(h=t).length-10,f=(u=(33===h.charCodeAt(l)?h.substring(0,l):h).substring(t.indexOf(":",7)+1).trim()).charCodeAt(0)+(0|u.charCodeAt(7))){case 203:if(u.charCodeAt(8)<111)break;case 115:h=h.replace(u,M+u)+";"+h;break;case 207:case 102:h=h.replace(u,M+(f>102?"inline-":"")+"box")+";"+h.replace(u,M+u)+";"+h.replace(u,D+u+"box")+";"+h}return h+";";case 938:if(45===h.charCodeAt(5))switch(h.charCodeAt(6)){case 105:return u=h.replace("-items",""),M+h+M+"box-"+u+D+"flex-"+u+h;case 115:return M+h+D+"flex-item-"+h.replace(T,"")+h;default:return M+h+D+"flex-line-pack"+h.replace("align-content","").replace(T,"")+h}break;case 973:case 989:if(45!==h.charCodeAt(3)||122===h.charCodeAt(4))break;case 931:case 953:if(!0===A.test(t))return 115===(u=t.substring(t.indexOf(":")+1)).charCodeAt(0)?Q(t.replace("stretch","fill-available"),e,n,r).replace(":fill-available",":stretch"):h.replace(u,M+u)+h.replace(u,O+u.replace("fill-",""))+h;break;case 962:if(h=M+h+(102===h.charCodeAt(5)?D+h:"")+h,n+r===211&&105===h.charCodeAt(13)&&h.indexOf("transform",10)>0)return h.substring(0,h.indexOf(";",27)+1).replace(o,"$1-webkit-$2")+h}return h}function tt(t,e){var n=t.indexOf(1===e?":":"{"),r=t.substring(0,3!==e?n:10),i=t.substring(n+1,t.length-1);return W(2!==e?r:r.replace(C,"$1"),i,e)}function et(t,e){var n=Q(e,e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2));return n!==e+";"?n.replace(E," or ($1)").substring(4):"("+e+")"}function nt(t,e,n,r,i,a,o,s,c,u){for(var l,h=0,f=e;h<$;++h)switch(l=U[h].call(at,t,f,n,r,i,a,o,s,c,u)){case void 0:case!1:case!0:case null:break;default:f=l}if(f!==e)return f}function rt(t,e,n,r){for(var i=e+1;i0&&(G=i.replace(d,91===a?"":"-")),a=1,1===F?X=i:q=i;var o,s=[X];$>0&&void 0!==(o=nt(-1,n,s,s,B,N,0,0,0,0))&&"string"==typeof o&&(n=o);var c=Z(z,s,n,0,0);return $>0&&void 0!==(o=nt(-2,c,s,s,B,N,c.length,0,0,0))&&"string"!=typeof(c=o)&&(a=0),G="",X="",q="",L=0,B=1,N=1,j*a==0?c:function(t){return t.replace(r,"").replace(v,"").replace(m,"$1").replace(b,"$1").replace(x," ")}(c)}return at.use=function t(e){switch(e){case void 0:case null:$=U.length=0;break;default:if("function"==typeof e)U[$++]=e;else if("object"==typeof e)for(var n=0,r=e.length;n=255?255:t<0?0:t},g:function(t){return t>=255?255:t<0?0:t},b:function(t){return t>=255?255:t<0?0:t},h:function(t){return t%360},s:function(t){return t>=100?100:t<0?0:t},l:function(t){return t>=100?100:t<0?0:t},a:function(t){return t>=1?1:t<0?0:t}},toLinear:function(t){var e=t/255;return t>.03928?Math.pow((e+.055)/1.055,2.4):e/12.92},hue2rgb:function(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+(e-t)*(2/3-n)*6:t},hsl2rgb:function(t,e){var n=t.h,i=t.s,a=t.l;if(100===i)return 2.55*a;n/=360,i/=100;var o=(a/=100)<.5?a*(1+i):a+i-a*i,s=2*a-o;switch(e){case"r":return 255*r.hue2rgb(s,o,n+1/3);case"g":return 255*r.hue2rgb(s,o,n);case"b":return 255*r.hue2rgb(s,o,n-1/3)}},rgb2hsl:function(t,e){var n=t.r,r=t.g,i=t.b;n/=255,r/=255,i/=255;var a=Math.max(n,r,i),o=Math.min(n,r,i),s=(a+o)/2;if("l"===e)return 100*s;if(a===o)return 0;var c=a-o;if("s"===e)return 100*(s>.5?c/(2-a-o):c/(a+o));switch(a){case n:return 60*((r-i)/c+(r1?e:"0"+e},dec2hex:function(t){var e=Math.round(t).toString(16);return e.length>1?e:"0"+e}};e.default=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(75),a=n(177),o=function(){function t(t,e){this.color=e,this.changed=!1,this.data=t,this.type=new a.default}return t.prototype.set=function(t,e){return this.color=e,this.changed=!1,this.data=t,this.type.type=i.TYPE.ALL,this},t.prototype._ensureHSL=function(){void 0===this.data.h&&(this.data.h=r.default.channel.rgb2hsl(this.data,"h")),void 0===this.data.s&&(this.data.s=r.default.channel.rgb2hsl(this.data,"s")),void 0===this.data.l&&(this.data.l=r.default.channel.rgb2hsl(this.data,"l"))},t.prototype._ensureRGB=function(){void 0===this.data.r&&(this.data.r=r.default.channel.hsl2rgb(this.data,"r")),void 0===this.data.g&&(this.data.g=r.default.channel.hsl2rgb(this.data,"g")),void 0===this.data.b&&(this.data.b=r.default.channel.hsl2rgb(this.data,"b"))},Object.defineProperty(t.prototype,"r",{get:function(){return this.type.is(i.TYPE.HSL)||void 0===this.data.r?(this._ensureHSL(),r.default.channel.hsl2rgb(this.data,"r")):this.data.r},set:function(t){this.type.set(i.TYPE.RGB),this.changed=!0,this.data.r=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"g",{get:function(){return this.type.is(i.TYPE.HSL)||void 0===this.data.g?(this._ensureHSL(),r.default.channel.hsl2rgb(this.data,"g")):this.data.g},set:function(t){this.type.set(i.TYPE.RGB),this.changed=!0,this.data.g=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"b",{get:function(){return this.type.is(i.TYPE.HSL)||void 0===this.data.b?(this._ensureHSL(),r.default.channel.hsl2rgb(this.data,"b")):this.data.b},set:function(t){this.type.set(i.TYPE.RGB),this.changed=!0,this.data.b=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"h",{get:function(){return this.type.is(i.TYPE.RGB)||void 0===this.data.h?(this._ensureRGB(),r.default.channel.rgb2hsl(this.data,"h")):this.data.h},set:function(t){this.type.set(i.TYPE.HSL),this.changed=!0,this.data.h=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"s",{get:function(){return this.type.is(i.TYPE.RGB)||void 0===this.data.s?(this._ensureRGB(),r.default.channel.rgb2hsl(this.data,"s")):this.data.s},set:function(t){this.type.set(i.TYPE.HSL),this.changed=!0,this.data.s=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"l",{get:function(){return this.type.is(i.TYPE.RGB)||void 0===this.data.l?(this._ensureRGB(),r.default.channel.rgb2hsl(this.data,"l")):this.data.l},set:function(t){this.type.set(i.TYPE.HSL),this.changed=!0,this.data.l=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"a",{get:function(){return this.data.a},set:function(t){this.changed=!0,this.data.a=t},enumerable:!0,configurable:!0}),t}();e.default=o},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(75),i=function(){function t(){this.type=r.TYPE.ALL}return t.prototype.get=function(){return this.type},t.prototype.set=function(t){if(this.type&&this.type!==t)throw new Error("Cannot change both RGB and HSL channels at the same time");this.type=t},t.prototype.reset=function(){this.type=r.TYPE.ALL},t.prototype.is=function(t){return this.type===t},t}();e.default=i},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i={};e.DEC2HEX=i;for(var a=0;a<=255;a++)i[a]=r.default.unit.dec2hex(a)},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(99),i={colors:{aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyanaqua:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",transparent:"#00000000",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},parse:function(t){t=t.toLowerCase();var e=i.colors[t];if(e)return r.default.parse(e)},stringify:function(t){var e=r.default.stringify(t);for(var n in i.colors)if(i.colors[n]===e)return n}};e.default=i},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a={re:/^rgba?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?)))?\s*?\)$/i,parse:function(t){var e=t.charCodeAt(0);if(114===e||82===e){var n=t.match(a.re);if(n){var o=n[1],s=n[2],c=n[3],u=n[4],l=n[5],h=n[6],f=n[7],d=n[8];return i.default.set({r:r.default.channel.clamp.r(s?2.55*parseFloat(o):parseFloat(o)),g:r.default.channel.clamp.g(u?2.55*parseFloat(c):parseFloat(c)),b:r.default.channel.clamp.b(h?2.55*parseFloat(l):parseFloat(l)),a:f?r.default.channel.clamp.a(d?parseFloat(f)/100:parseFloat(f)):1},t)}}},stringify:function(t){return t.a<1?"rgba("+r.default.lang.round(t.r)+", "+r.default.lang.round(t.g)+", "+r.default.lang.round(t.b)+", "+r.default.lang.round(t.a)+")":"rgb("+r.default.lang.round(t.r)+", "+r.default.lang.round(t.g)+", "+r.default.lang.round(t.b)+")"}};e.default=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a={re:/^hsla?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(?:deg|grad|rad|turn)?)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(%)?))?\s*?\)$/i,hueRe:/^(.+?)(deg|grad|rad|turn)$/i,_hue2deg:function(t){var e=t.match(a.hueRe);if(e){var n=e[1];switch(e[2]){case"grad":return r.default.channel.clamp.h(.9*parseFloat(n));case"rad":return r.default.channel.clamp.h(180*parseFloat(n)/Math.PI);case"turn":return r.default.channel.clamp.h(360*parseFloat(n))}}return r.default.channel.clamp.h(parseFloat(t))},parse:function(t){var e=t.charCodeAt(0);if(104===e||72===e){var n=t.match(a.re);if(n){var o=n[1],s=n[2],c=n[3],u=n[4],l=n[5];return i.default.set({h:a._hue2deg(o),s:r.default.channel.clamp.s(parseFloat(s)),l:r.default.channel.clamp.l(parseFloat(c)),a:u?r.default.channel.clamp.a(l?parseFloat(u)/100:parseFloat(u)):1},t)}}},stringify:function(t){return t.a<1?"hsla("+r.default.lang.round(t.h)+", "+r.default.lang.round(t.s)+"%, "+r.default.lang.round(t.l)+"%, "+t.a+")":"hsl("+r.default.lang.round(t.h)+", "+r.default.lang.round(t.s)+"%, "+r.default.lang.round(t.l)+"%)"}};e.default=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"r")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"g")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"b")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"h")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"s")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"l")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(103);e.default=function(t){return!r.default(t)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15);e.default=function(t){try{return r.default.parse(t),!0}catch(t){return!1}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"s",e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"s",-e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"l",e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"l",-e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t){return r.default(t,"h",180)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(52);e.default=function(t){return r.default(t,{s:0})}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15),i=n(107);e.default=function(t,e){void 0===e&&(e=100);var n=r.default.parse(t);return n.r=255-n.r,n.g=255-n.g,n.b=255-n.b,i.default(n,t,e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15),a=n(106);e.default=function(t,e){var n,o,s,c=i.default.parse(t),u={};for(var l in e)u[l]=(n=c[l],o=e[l],s=r.default.channel.max[l],o>0?(s-n)*o/100:n*o/100);return a.default(t,u)}},function(t,e,n){t.exports={Graph:n(76),version:n(300)}},function(t,e,n){var r=n(108);t.exports=function(t){return r(t,4)}},function(t,e){t.exports=function(){this.__data__=[],this.size=0}},function(t,e,n){var r=n(55),i=Array.prototype.splice;t.exports=function(t){var e=this.__data__,n=r(e,t);return!(n<0)&&(n==e.length-1?e.pop():i.call(e,n,1),--this.size,!0)}},function(t,e,n){var r=n(55);t.exports=function(t){var e=this.__data__,n=r(e,t);return n<0?void 0:e[n][1]}},function(t,e,n){var r=n(55);t.exports=function(t){return r(this.__data__,t)>-1}},function(t,e,n){var r=n(55);t.exports=function(t,e){var n=this.__data__,i=r(n,t);return i<0?(++this.size,n.push([t,e])):n[i][1]=e,this}},function(t,e,n){var r=n(54);t.exports=function(){this.__data__=new r,this.size=0}},function(t,e){t.exports=function(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n}},function(t,e){t.exports=function(t){return this.__data__.get(t)}},function(t,e){t.exports=function(t){return this.__data__.has(t)}},function(t,e,n){var r=n(54),i=n(77),a=n(78);t.exports=function(t,e){var n=this.__data__;if(n instanceof r){var o=n.__data__;if(!i||o.length<199)return o.push([t,e]),this.size=++n.size,this;n=this.__data__=new a(o)}return n.set(t,e),this.size=n.size,this}},function(t,e,n){var r=n(37),i=n(214),a=n(11),o=n(110),s=/^\[object .+?Constructor\]$/,c=Function.prototype,u=Object.prototype,l=c.toString,h=u.hasOwnProperty,f=RegExp("^"+l.call(h).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=function(t){return!(!a(t)||i(t))&&(r(t)?f:s).test(o(t))}},function(t,e){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(t){"object"==typeof window&&(n=window)}t.exports=n},function(t,e,n){var r=n(38),i=Object.prototype,a=i.hasOwnProperty,o=i.toString,s=r?r.toStringTag:void 0;t.exports=function(t){var e=a.call(t,s),n=t[s];try{t[s]=void 0;var r=!0}catch(t){}var i=o.call(t);return r&&(e?t[s]=n:delete t[s]),i}},function(t,e){var n=Object.prototype.toString;t.exports=function(t){return n.call(t)}},function(t,e,n){var r,i=n(215),a=(r=/[^.]+$/.exec(i&&i.keys&&i.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"";t.exports=function(t){return!!a&&a in t}},function(t,e,n){var r=n(16)["__core-js_shared__"];t.exports=r},function(t,e){t.exports=function(t,e){return null==t?void 0:t[e]}},function(t,e,n){var r=n(218),i=n(54),a=n(77);t.exports=function(){this.size=0,this.__data__={hash:new r,map:new(a||i),string:new r}}},function(t,e,n){var r=n(219),i=n(220),a=n(221),o=n(222),s=n(223);function c(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e0){if(++e>=800)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}},function(t,e,n){var r=n(131),i=n(292),a=n(296),o=n(132),s=n(297),c=n(90);t.exports=function(t,e,n){var u=-1,l=i,h=t.length,f=!0,d=[],p=d;if(n)f=!1,l=a;else if(h>=200){var g=e?null:s(t);if(g)return c(g);f=!1,l=o,p=new r}else p=e?[]:d;t:for(;++u-1}},function(t,e,n){var r=n(145),i=n(294),a=n(295);t.exports=function(t,e,n){return e==e?a(t,e,n):r(t,i,n)}},function(t,e){t.exports=function(t){return t!=t}},function(t,e){t.exports=function(t,e,n){for(var r=n-1,i=t.length;++r1||1===e.length&&t.hasEdge(e[0],e[0])}))}},function(t,e,n){var r=n(10);t.exports=function(t,e,n){return function(t,e,n){var r={},i=t.nodes();return i.forEach((function(t){r[t]={},r[t][t]={distance:0},i.forEach((function(e){t!==e&&(r[t][e]={distance:Number.POSITIVE_INFINITY})})),n(t).forEach((function(n){var i=n.v===t?n.w:n.v,a=e(n);r[t][i]={distance:a,predecessor:t}}))})),i.forEach((function(t){var e=r[t];i.forEach((function(n){var a=r[n];i.forEach((function(n){var r=a[t],i=e[n],o=a[n],s=r.distance+i.distance;s0;){if(n=c.removeMin(),r.has(s,n))o.setEdge(n,s[n]);else{if(l)throw new Error("Input graph is not connected: "+t);l=!0}t.nodeEdges(n).forEach(u)}return o}},function(t,e,n){var r;try{r=n(3)}catch(t){}r||(r=window.graphlib),t.exports=r},function(t,e,n){"use strict";var r=n(4),i=n(345),a=n(348),o=n(349),s=n(8).normalizeRanks,c=n(351),u=n(8).removeEmptyRanks,l=n(352),h=n(353),f=n(354),d=n(355),p=n(364),g=n(8),y=n(17).Graph;t.exports=function(t,e){var n=e&&e.debugTiming?g.time:g.notime;n("layout",(function(){var e=n(" buildLayoutGraph",(function(){return function(t){var e=new y({multigraph:!0,compound:!0}),n=C(t.graph());return e.setGraph(r.merge({},m,T(n,v),r.pick(n,b))),r.forEach(t.nodes(),(function(n){var i=C(t.node(n));e.setNode(n,r.defaults(T(i,x),_)),e.setParent(n,t.parent(n))})),r.forEach(t.edges(),(function(n){var i=C(t.edge(n));e.setEdge(n,r.merge({},w,T(i,k),r.pick(i,E)))})),e}(t)}));n(" runLayout",(function(){!function(t,e){e(" makeSpaceForEdgeLabels",(function(){!function(t){var e=t.graph();e.ranksep/=2,r.forEach(t.edges(),(function(n){var r=t.edge(n);r.minlen*=2,"c"!==r.labelpos.toLowerCase()&&("TB"===e.rankdir||"BT"===e.rankdir?r.width+=r.labeloffset:r.height+=r.labeloffset)}))}(t)})),e(" removeSelfEdges",(function(){!function(t){r.forEach(t.edges(),(function(e){if(e.v===e.w){var n=t.node(e.v);n.selfEdges||(n.selfEdges=[]),n.selfEdges.push({e:e,label:t.edge(e)}),t.removeEdge(e)}}))}(t)})),e(" acyclic",(function(){i.run(t)})),e(" nestingGraph.run",(function(){l.run(t)})),e(" rank",(function(){o(g.asNonCompoundGraph(t))})),e(" injectEdgeLabelProxies",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(n.width&&n.height){var r=t.node(e.v),i={rank:(t.node(e.w).rank-r.rank)/2+r.rank,e:e};g.addDummyNode(t,"edge-proxy",i,"_ep")}}))}(t)})),e(" removeEmptyRanks",(function(){u(t)})),e(" nestingGraph.cleanup",(function(){l.cleanup(t)})),e(" normalizeRanks",(function(){s(t)})),e(" assignRankMinMax",(function(){!function(t){var e=0;r.forEach(t.nodes(),(function(n){var i=t.node(n);i.borderTop&&(i.minRank=t.node(i.borderTop).rank,i.maxRank=t.node(i.borderBottom).rank,e=r.max(e,i.maxRank))})),t.graph().maxRank=e}(t)})),e(" removeEdgeLabelProxies",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);"edge-proxy"===n.dummy&&(t.edge(n.e).labelRank=n.rank,t.removeNode(e))}))}(t)})),e(" normalize.run",(function(){a.run(t)})),e(" parentDummyChains",(function(){c(t)})),e(" addBorderSegments",(function(){h(t)})),e(" order",(function(){d(t)})),e(" insertSelfEdges",(function(){!function(t){var e=g.buildLayerMatrix(t);r.forEach(e,(function(e){var n=0;r.forEach(e,(function(e,i){var a=t.node(e);a.order=i+n,r.forEach(a.selfEdges,(function(e){g.addDummyNode(t,"selfedge",{width:e.label.width,height:e.label.height,rank:a.rank,order:i+ ++n,e:e.e,label:e.label},"_se")})),delete a.selfEdges}))}))}(t)})),e(" adjustCoordinateSystem",(function(){f.adjust(t)})),e(" position",(function(){p(t)})),e(" positionSelfEdges",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);if("selfedge"===n.dummy){var r=t.node(n.e.v),i=r.x+r.width/2,a=r.y,o=n.x-i,s=r.height/2;t.setEdge(n.e,n.label),t.removeNode(e),n.label.points=[{x:i+2*o/3,y:a-s},{x:i+5*o/6,y:a-s},{x:i+o,y:a},{x:i+5*o/6,y:a+s},{x:i+2*o/3,y:a+s}],n.label.x=n.x,n.label.y=n.y}}))}(t)})),e(" removeBorderNodes",(function(){!function(t){r.forEach(t.nodes(),(function(e){if(t.children(e).length){var n=t.node(e),i=t.node(n.borderTop),a=t.node(n.borderBottom),o=t.node(r.last(n.borderLeft)),s=t.node(r.last(n.borderRight));n.width=Math.abs(s.x-o.x),n.height=Math.abs(a.y-i.y),n.x=o.x+n.width/2,n.y=i.y+n.height/2}})),r.forEach(t.nodes(),(function(e){"border"===t.node(e).dummy&&t.removeNode(e)}))}(t)})),e(" normalize.undo",(function(){a.undo(t)})),e(" fixupEdgeLabelCoords",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(r.has(n,"x"))switch("l"!==n.labelpos&&"r"!==n.labelpos||(n.width-=n.labeloffset),n.labelpos){case"l":n.x-=n.width/2+n.labeloffset;break;case"r":n.x+=n.width/2+n.labeloffset}}))}(t)})),e(" undoCoordinateSystem",(function(){f.undo(t)})),e(" translateGraph",(function(){!function(t){var e=Number.POSITIVE_INFINITY,n=0,i=Number.POSITIVE_INFINITY,a=0,o=t.graph(),s=o.marginx||0,c=o.marginy||0;function u(t){var r=t.x,o=t.y,s=t.width,c=t.height;e=Math.min(e,r-s/2),n=Math.max(n,r+s/2),i=Math.min(i,o-c/2),a=Math.max(a,o+c/2)}r.forEach(t.nodes(),(function(e){u(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.has(n,"x")&&u(n)})),e-=s,i-=c,r.forEach(t.nodes(),(function(n){var r=t.node(n);r.x-=e,r.y-=i})),r.forEach(t.edges(),(function(n){var a=t.edge(n);r.forEach(a.points,(function(t){t.x-=e,t.y-=i})),r.has(a,"x")&&(a.x-=e),r.has(a,"y")&&(a.y-=i)})),o.width=n-e+s,o.height=a-i+c}(t)})),e(" assignNodeIntersects",(function(){!function(t){r.forEach(t.edges(),(function(e){var n,r,i=t.edge(e),a=t.node(e.v),o=t.node(e.w);i.points?(n=i.points[0],r=i.points[i.points.length-1]):(i.points=[],n=o,r=a),i.points.unshift(g.intersectRect(a,n)),i.points.push(g.intersectRect(o,r))}))}(t)})),e(" reversePoints",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);n.reversed&&n.points.reverse()}))}(t)})),e(" acyclic.undo",(function(){i.undo(t)}))}(e,n)})),n(" updateInputGraph",(function(){!function(t,e){r.forEach(t.nodes(),(function(n){var r=t.node(n),i=e.node(n);r&&(r.x=i.x,r.y=i.y,e.children(n).length&&(r.width=i.width,r.height=i.height))})),r.forEach(t.edges(),(function(n){var i=t.edge(n),a=e.edge(n);i.points=a.points,r.has(a,"x")&&(i.x=a.x,i.y=a.y)})),t.graph().width=e.graph().width,t.graph().height=e.graph().height}(t,e)}))}))};var v=["nodesep","edgesep","ranksep","marginx","marginy"],m={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},b=["acyclicer","ranker","rankdir","align"],x=["width","height"],_={width:0,height:0},k=["minlen","weight","width","height","labeloffset"],w={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},E=["labelpos"];function T(t,e){return r.mapValues(r.pick(t,e),Number)}function C(t){var e={};return r.forEach(t,(function(t,n){e[n.toLowerCase()]=t})),e}},function(t,e,n){var r=n(108);t.exports=function(t){return r(t,5)}},function(t,e,n){var r=n(315)(n(316));t.exports=r},function(t,e,n){var r=n(25),i=n(24),a=n(30);t.exports=function(t){return function(e,n,o){var s=Object(e);if(!i(e)){var c=r(n,3);e=a(e),n=function(t){return c(s[t],t,s)}}var u=t(e,n,o);return u>-1?s[c?e[u]:u]:void 0}}},function(t,e,n){var r=n(145),i=n(25),a=n(317),o=Math.max;t.exports=function(t,e,n){var s=null==t?0:t.length;if(!s)return-1;var c=null==n?0:a(n);return c<0&&(c=o(s+c,0)),r(t,i(e,3),c)}},function(t,e,n){var r=n(155);t.exports=function(t){var e=r(t),n=e%1;return e==e?n?e-n:e:0}},function(t,e,n){var r=n(11),i=n(42),a=/^\s+|\s+$/g,o=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,c=/^0o[0-7]+$/i,u=parseInt;t.exports=function(t){if("number"==typeof t)return t;if(i(t))return NaN;if(r(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=r(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(a,"");var n=s.test(t);return n||c.test(t)?u(t.slice(2),n?2:8):o.test(t)?NaN:+t}},function(t,e,n){var r=n(89),i=n(127),a=n(40);t.exports=function(t,e){return null==t?t:r(t,i(e),a)}},function(t,e){t.exports=function(t){var e=null==t?0:t.length;return e?t[e-1]:void 0}},function(t,e,n){var r=n(59),i=n(88),a=n(25);t.exports=function(t,e){var n={};return e=a(e,3),i(t,(function(t,i,a){r(n,i,e(t,i,a))})),n}},function(t,e,n){var r=n(95),i=n(323),a=n(35);t.exports=function(t){return t&&t.length?r(t,a,i):void 0}},function(t,e){t.exports=function(t,e){return t>e}},function(t,e,n){var r=n(325),i=n(328)((function(t,e,n){r(t,e,n)}));t.exports=i},function(t,e,n){var r=n(53),i=n(157),a=n(89),o=n(326),s=n(11),c=n(40),u=n(159);t.exports=function t(e,n,l,h,f){e!==n&&a(n,(function(a,c){if(f||(f=new r),s(a))o(e,n,c,l,t,h,f);else{var d=h?h(u(e,c),a,c+"",e,n,f):void 0;void 0===d&&(d=a),i(e,c,d)}}),c)}},function(t,e,n){var r=n(157),i=n(114),a=n(123),o=n(115),s=n(124),c=n(47),u=n(5),l=n(146),h=n(39),f=n(37),d=n(11),p=n(158),g=n(48),y=n(159),v=n(327);t.exports=function(t,e,n,m,b,x,_){var k=y(t,n),w=y(e,n),E=_.get(w);if(E)r(t,n,E);else{var T=x?x(k,w,n+"",t,e,_):void 0,C=void 0===T;if(C){var A=u(w),S=!A&&h(w),M=!A&&!S&&g(w);T=w,A||S||M?u(k)?T=k:l(k)?T=o(k):S?(C=!1,T=i(w,!0)):M?(C=!1,T=a(w,!0)):T=[]:p(w)||c(w)?(T=k,c(k)?T=v(k):d(k)&&!f(k)||(T=s(w))):C=!1}C&&(_.set(w,T),b(T,w,m,x,_),_.delete(w)),r(t,n,T)}}},function(t,e,n){var r=n(46),i=n(40);t.exports=function(t){return r(t,i(t))}},function(t,e,n){var r=n(67),i=n(68);t.exports=function(t){return r((function(e,n){var r=-1,a=n.length,o=a>1?n[a-1]:void 0,s=a>2?n[2]:void 0;for(o=t.length>3&&"function"==typeof o?(a--,o):void 0,s&&i(n[0],n[1],s)&&(o=a<3?void 0:o,a=1),e=Object(e);++r1&&o(t,e[0],e[1])?e=[]:n>2&&o(e[0],e[1],e[2])&&(e=[e[0]]),i(t,r(e,1),[])}));t.exports=s},function(t,e,n){var r=n(66),i=n(25),a=n(141),o=n(340),s=n(61),c=n(341),u=n(35);t.exports=function(t,e,n){var l=-1;e=r(e.length?e:[u],s(i));var h=a(t,(function(t,n,i){return{criteria:r(e,(function(e){return e(t)})),index:++l,value:t}}));return o(h,(function(t,e){return c(t,e,n)}))}},function(t,e){t.exports=function(t,e){var n=t.length;for(t.sort(e);n--;)t[n]=t[n].value;return t}},function(t,e,n){var r=n(342);t.exports=function(t,e,n){for(var i=-1,a=t.criteria,o=e.criteria,s=a.length,c=n.length;++i=c?u:u*("desc"==n[i]?-1:1)}return t.index-e.index}},function(t,e,n){var r=n(42);t.exports=function(t,e){if(t!==e){var n=void 0!==t,i=null===t,a=t==t,o=r(t),s=void 0!==e,c=null===e,u=e==e,l=r(e);if(!c&&!l&&!o&&t>e||o&&s&&u&&!c&&!l||i&&s&&u||!n&&u||!a)return 1;if(!i&&!o&&!l&&t0;--c)if(r=e[c].dequeue()){i=i.concat(s(t,e,n,r,!0));break}}return i}(n.graph,n.buckets,n.zeroIdx);return r.flatten(r.map(u,(function(e){return t.outEdges(e.v,e.w)})),!0)};var o=r.constant(1);function s(t,e,n,i,a){var o=a?[]:void 0;return r.forEach(t.inEdges(i.v),(function(r){var i=t.edge(r),s=t.node(r.v);a&&o.push({v:r.v,w:r.w}),s.out-=i,c(e,n,s)})),r.forEach(t.outEdges(i.v),(function(r){var i=t.edge(r),a=r.w,o=t.node(a);o.in-=i,c(e,n,o)})),t.removeNode(i.v),o}function c(t,e,n){n.out?n.in?t[n.out-n.in+e].enqueue(n):t[t.length-1].enqueue(n):t[0].enqueue(n)}},function(t,e){function n(){var t={};t._next=t._prev=t,this._sentinel=t}function r(t){t._prev._next=t._next,t._next._prev=t._prev,delete t._next,delete t._prev}function i(t,e){if("_next"!==t&&"_prev"!==t)return e}t.exports=n,n.prototype.dequeue=function(){var t=this._sentinel,e=t._prev;if(e!==t)return r(e),e},n.prototype.enqueue=function(t){var e=this._sentinel;t._prev&&t._next&&r(t),t._next=e._next,e._next._prev=t,e._next=t,t._prev=e},n.prototype.toString=function(){for(var t=[],e=this._sentinel,n=e._prev;n!==e;)t.push(JSON.stringify(n,i)),n=n._prev;return"["+t.join(", ")+"]"}},function(t,e,n){"use strict";var r=n(4),i=n(8);t.exports={run:function(t){t.graph().dummyChains=[],r.forEach(t.edges(),(function(e){!function(t,e){var n,r,a,o=e.v,s=t.node(o).rank,c=e.w,u=t.node(c).rank,l=e.name,h=t.edge(e),f=h.labelRank;if(u===s+1)return;for(t.removeEdge(e),a=0,++s;sc.lim&&(u=c,l=!0);var h=r.filter(e.edges(),(function(e){return l===m(t,t.node(e.v),u)&&l!==m(t,t.node(e.w),u)}));return r.minBy(h,(function(t){return a(e,t)}))}function v(t,e,n,i){var a=n.v,o=n.w;t.removeEdge(a,o),t.setEdge(i.v,i.w,{}),d(t),h(t,e),function(t,e){var n=r.find(t.nodes(),(function(t){return!e.node(t).parent})),i=s(t,n);i=i.slice(1),r.forEach(i,(function(n){var r=t.node(n).parent,i=e.edge(n,r),a=!1;i||(i=e.edge(r,n),a=!0),e.node(n).rank=e.node(r).rank+(a?i.minlen:-i.minlen)}))}(t,e)}function m(t,e,n){return n.low<=e.lim&&e.lim<=n.lim}t.exports=l,l.initLowLimValues=d,l.initCutValues=h,l.calcCutValue=f,l.leaveEdge=g,l.enterEdge=y,l.exchangeEdges=v},function(t,e,n){var r=n(4);t.exports=function(t){var e=function(t){var e={},n=0;function i(a){var o=n;r.forEach(t.children(a),i),e[a]={low:o,lim:n++}}return r.forEach(t.children(),i),e}(t);r.forEach(t.graph().dummyChains,(function(n){for(var r=t.node(n),i=r.edgeObj,a=function(t,e,n,r){var i,a,o=[],s=[],c=Math.min(e[n].low,e[r].low),u=Math.max(e[n].lim,e[r].lim);i=n;do{i=t.parent(i),o.push(i)}while(i&&(e[i].low>c||u>e[i].lim));a=i,i=r;for(;(i=t.parent(i))!==a;)s.push(i);return{path:o.concat(s.reverse()),lca:a}}(t,e,i.v,i.w),o=a.path,s=a.lca,c=0,u=o[c],l=!0;n!==i.w;){if(r=t.node(n),l){for(;(u=o[c])!==s&&t.node(u).maxRank=2),s=l.buildLayerMatrix(t);var y=a(t,s);y0;)e%2&&(n+=c[e+1]),c[e=e-1>>1]+=t.weight;u+=t.weight*n}))),u}t.exports=function(t,e){for(var n=0,r=1;r=t.barycenter)&&function(t,e){var n=0,r=0;t.weight&&(n+=t.barycenter*t.weight,r+=t.weight);e.weight&&(n+=e.barycenter*e.weight,r+=e.weight);t.vs=e.vs.concat(t.vs),t.barycenter=n/r,t.weight=r,t.i=Math.min(e.i,t.i),e.merged=!0}(t,e)}}function i(e){return function(n){n.in.push(e),0==--n.indegree&&t.push(n)}}for(;t.length;){var a=t.pop();e.push(a),r.forEach(a.in.reverse(),n(a)),r.forEach(a.out,i(a))}return r.map(r.filter(e,(function(t){return!t.merged})),(function(t){return r.pick(t,["vs","i","barycenter","weight"])}))}(r.filter(n,(function(t){return!t.indegree})))}},function(t,e,n){var r=n(4),i=n(8);function a(t,e,n){for(var i;e.length&&(i=r.last(e)).i<=n;)e.pop(),t.push(i.vs),n++;return n}t.exports=function(t,e){var n=i.partition(t,(function(t){return r.has(t,"barycenter")})),o=n.lhs,s=r.sortBy(n.rhs,(function(t){return-t.i})),c=[],u=0,l=0,h=0;o.sort((f=!!e,function(t,e){return t.barycentere.barycenter?1:f?e.i-t.i:t.i-e.i})),h=a(c,s,h),r.forEach(o,(function(t){h+=t.vs.length,c.push(t.vs),u+=t.barycenter*t.weight,l+=t.weight,h=a(c,s,h)}));var f;var d={vs:r.flatten(c,!0)};l&&(d.barycenter=u/l,d.weight=l);return d}},function(t,e,n){var r=n(4),i=n(17).Graph;t.exports=function(t,e,n){var a=function(t){var e;for(;t.hasNode(e=r.uniqueId("_root")););return e}(t),o=new i({compound:!0}).setGraph({root:a}).setDefaultNodeLabel((function(e){return t.node(e)}));return r.forEach(t.nodes(),(function(i){var s=t.node(i),c=t.parent(i);(s.rank===e||s.minRank<=e&&e<=s.maxRank)&&(o.setNode(i),o.setParent(i,c||a),r.forEach(t[n](i),(function(e){var n=e.v===i?e.w:e.v,a=o.edge(n,i),s=r.isUndefined(a)?0:a.weight;o.setEdge(n,i,{weight:t.edge(e).weight+s})})),r.has(s,"minRank")&&o.setNode(i,{borderLeft:s.borderLeft[e],borderRight:s.borderRight[e]}))})),o}},function(t,e,n){var r=n(4);t.exports=function(t,e,n){var i,a={};r.forEach(n,(function(n){for(var r,o,s=t.parent(n);s;){if((r=t.parent(s))?(o=a[r],a[r]=s):(o=i,i=s),o&&o!==s)return void e.setEdge(o,s);s=r}}))}},function(t,e,n){"use strict";var r=n(4),i=n(8),a=n(365).positionX;t.exports=function(t){(function(t){var e=i.buildLayerMatrix(t),n=t.graph().ranksep,a=0;r.forEach(e,(function(e){var i=r.max(r.map(e,(function(e){return t.node(e).height})));r.forEach(e,(function(e){t.node(e).y=a+i/2})),a+=i+n}))})(t=i.asNonCompoundGraph(t)),r.forEach(a(t),(function(e,n){t.node(n).x=e}))}},function(t,e,n){"use strict";var r=n(4),i=n(17).Graph,a=n(8);function o(t,e){var n={};return r.reduce(e,(function(e,i){var a=0,o=0,s=e.length,u=r.last(i);return r.forEach(i,(function(e,l){var h=function(t,e){if(t.node(e).dummy)return r.find(t.predecessors(e),(function(e){return t.node(e).dummy}))}(t,e),f=h?t.node(h).order:s;(h||e===u)&&(r.forEach(i.slice(o,l+1),(function(e){r.forEach(t.predecessors(e),(function(r){var i=t.node(r),o=i.order;!(os)&&c(n,e,u)}))}))}return r.reduce(e,(function(e,n){var a,o=-1,s=0;return r.forEach(n,(function(r,c){if("border"===t.node(r).dummy){var u=t.predecessors(r);u.length&&(a=t.node(u[0]).order,i(n,s,c,o,a),s=c,o=a)}i(n,s,n.length,a,e.length)})),n})),n}function c(t,e,n){if(e>n){var r=e;e=n,n=r}var i=t[e];i||(t[e]=i={}),i[n]=!0}function u(t,e,n){if(e>n){var i=e;e=n,n=i}return r.has(t[e],n)}function l(t,e,n,i){var a={},o={},s={};return r.forEach(e,(function(t){r.forEach(t,(function(t,e){a[t]=t,o[t]=t,s[t]=e}))})),r.forEach(e,(function(t){var e=-1;r.forEach(t,(function(t){var c=i(t);if(c.length)for(var l=((c=r.sortBy(c,(function(t){return s[t]}))).length-1)/2,h=Math.floor(l),f=Math.ceil(l);h<=f;++h){var d=c[h];o[t]===t&&e0}t.exports=function(t,e,r,i){var a,o,s,c,u,l,h,f,d,p,g,y,v;if(a=e.y-t.y,s=t.x-e.x,u=e.x*t.y-t.x*e.y,d=a*r.x+s*r.y+u,p=a*i.x+s*i.y+u,0!==d&&0!==p&&n(d,p))return;if(o=i.y-r.y,c=r.x-i.x,l=i.x*r.y-r.x*i.y,h=o*t.x+c*t.y+l,f=o*e.x+c*e.y+l,0!==h&&0!==f&&n(h,f))return;if(0===(g=a*c-o*s))return;return y=Math.abs(g/2),{x:(v=s*l-c*u)<0?(v-y)/g:(v+y)/g,y:(v=o*u-a*l)<0?(v-y)/g:(v+y)/g}}},function(t,e,n){var r=n(43),i=n(31),a=n(153).layout;t.exports=function(){var t=n(371),e=n(374),i=n(375),u=n(376),l=n(377),h=n(378),f=n(379),d=n(380),p=n(381),g=function(n,g){!function(t){t.nodes().forEach((function(e){var n=t.node(e);r.has(n,"label")||t.children(e).length||(n.label=e),r.has(n,"paddingX")&&r.defaults(n,{paddingLeft:n.paddingX,paddingRight:n.paddingX}),r.has(n,"paddingY")&&r.defaults(n,{paddingTop:n.paddingY,paddingBottom:n.paddingY}),r.has(n,"padding")&&r.defaults(n,{paddingLeft:n.padding,paddingRight:n.padding,paddingTop:n.padding,paddingBottom:n.padding}),r.defaults(n,o),r.each(["paddingLeft","paddingRight","paddingTop","paddingBottom"],(function(t){n[t]=Number(n[t])})),r.has(n,"width")&&(n._prevWidth=n.width),r.has(n,"height")&&(n._prevHeight=n.height)})),t.edges().forEach((function(e){var n=t.edge(e);r.has(n,"label")||(n.label=""),r.defaults(n,s)}))}(g);var y=c(n,"output"),v=c(y,"clusters"),m=c(y,"edgePaths"),b=i(c(y,"edgeLabels"),g),x=t(c(y,"nodes"),g,d);a(g),l(x,g),h(b,g),u(m,g,p);var _=e(v,g);f(_,g),function(t){r.each(t.nodes(),(function(e){var n=t.node(e);r.has(n,"_prevWidth")?n.width=n._prevWidth:delete n.width,r.has(n,"_prevHeight")?n.height=n._prevHeight:delete n.height,delete n._prevWidth,delete n._prevHeight}))}(g)};return g.createNodes=function(e){return arguments.length?(t=e,g):t},g.createClusters=function(t){return arguments.length?(e=t,g):e},g.createEdgeLabels=function(t){return arguments.length?(i=t,g):i},g.createEdgePaths=function(t){return arguments.length?(u=t,g):u},g.shapes=function(t){return arguments.length?(d=t,g):d},g.arrows=function(t){return arguments.length?(p=t,g):p},g};var o={paddingLeft:10,paddingRight:10,paddingTop:10,paddingBottom:10,rx:0,ry:0,shape:"rect"},s={arrowhead:"normal",curve:i.curveLinear};function c(t,e){var n=t.select("g."+e);return n.empty()&&(n=t.append("g").attr("class",e)),n}},function(t,e,n){"use strict";var r=n(43),i=n(97),a=n(12),o=n(31);t.exports=function(t,e,n){var s,c=e.nodes().filter((function(t){return!a.isSubgraph(e,t)})),u=t.selectAll("g.node").data(c,(function(t){return t})).classed("update",!0);u.exit().remove(),u.enter().append("g").attr("class","node").style("opacity",0),(u=t.selectAll("g.node")).each((function(t){var s=e.node(t),c=o.select(this);a.applyClass(c,s.class,(c.classed("update")?"update ":"")+"node"),c.select("g.label").remove();var u=c.append("g").attr("class","label"),l=i(u,s),h=n[s.shape],f=r.pick(l.node().getBBox(),"width","height");s.elem=this,s.id&&c.attr("id",s.id),s.labelId&&u.attr("id",s.labelId),r.has(s,"width")&&(f.width=s.width),r.has(s,"height")&&(f.height=s.height),f.width+=s.paddingLeft+s.paddingRight,f.height+=s.paddingTop+s.paddingBottom,u.attr("transform","translate("+(s.paddingLeft-s.paddingRight)/2+","+(s.paddingTop-s.paddingBottom)/2+")");var d=o.select(this);d.select(".label-container").remove();var p=h(d,f,s).classed("label-container",!0);a.applyStyle(p,s.style);var g=p.node().getBBox();s.width=g.width,s.height=g.height})),s=u.exit?u.exit():u.selectAll(null);return a.applyTransition(s,e).style("opacity",0).remove(),u}},function(t,e,n){var r=n(12);t.exports=function(t,e){for(var n=t.append("text"),i=function(t){for(var e,n="",r=!1,i=0;i0&&void 0!==arguments[0]?arguments[0]:"fatal";isNaN(t)&&(t=t.toLowerCase(),void 0!==s[t]&&(t=s[t])),c.trace=function(){},c.debug=function(){},c.info=function(){},c.warn=function(){},c.error=function(){},c.fatal=function(){},t<=s.fatal&&(c.fatal=console.error?console.error.bind(console,l("FATAL"),"color: orange"):console.log.bind(console,"",l("FATAL"))),t<=s.error&&(c.error=console.error?console.error.bind(console,l("ERROR"),"color: orange"):console.log.bind(console,"",l("ERROR"))),t<=s.warn&&(c.warn=console.warn?console.warn.bind(console,l("WARN"),"color: orange"):console.log.bind(console,"",l("WARN"))),t<=s.info&&(c.info=console.info?console.info.bind(console,l("INFO"),"color: lightblue"):console.log.bind(console,"",l("INFO"))),t<=s.debug&&(c.debug=console.debug?console.debug.bind(console,l("DEBUG"),"color: lightgreen"):console.log.bind(console,"",l("DEBUG")))},l=function(t){var e=o()().format("ss.SSS");return"%c".concat(e," : ").concat(t," : ")},h=n(169),f=n.n(h),d=n(0),p=n(44),g=n(70),y=function(t){for(var e="",n=0;n>=0;){if(!((n=t.indexOf("=0)){e+=t,n=-1;break}e+=t.substr(0,n),(n=(t=t.substr(n+1)).indexOf("<\/script>"))>=0&&(n+=9,t=t.substr(n))}return e},v=//gi,m=function(t){return t.replace(v,"#br#")},b=function(t){return t.replace(/#br#/g,"
")},x={getRows:function(t){if(!t)return 1;var e=m(t);return(e=e.replace(/\\n/g,"#br#")).split("#br#")},sanitizeText:function(t,e){var n=t,r=!0;if(!e.flowchart||!1!==e.flowchart.htmlLabels&&"false"!==e.flowchart.htmlLabels||(r=!1),r){var i=e.securityLevel;"antiscript"===i?n=y(n):"loose"!==i&&(n=(n=(n=m(n)).replace(//g,">")).replace(/=/g,"="),n=b(n))}return n},hasBreaks:function(t){return//gi.test(t)},splitBreaks:function(t){return t.split(//gi)},lineBreakRegex:v,removeScript:y};function _(t,e){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:null;try{var n=new RegExp("[%]{2}(?![{]".concat(C.source,")(?=[}][%]{2}).*\n"),"ig");t=t.trim().replace(n,"").replace(/'/gm,'"'),c.debug("Detecting diagram directive".concat(null!==e?" type:"+e:""," based on the text:").concat(t));for(var r,i=[];null!==(r=T.exec(t));)if(r.index===T.lastIndex&&T.lastIndex++,r&&!e||e&&r[1]&&r[1].match(e)||e&&r[2]&&r[2].match(e)){var a=r[1]?r[1]:r[2],o=r[3]?r[3].trim():r[4]?JSON.parse(r[4].trim()):null;i.push({type:a,args:o})}return 0===i.length&&i.push({type:t,args:null}),1===i.length?i[0]:i}catch(n){return c.error("ERROR: ".concat(n.message," - Unable to parse directive").concat(null!==e?" type:"+e:""," based on the text:").concat(t)),{type:null,args:null}}},M=function(t){return t=t.replace(T,"").replace(A,"\n"),c.debug("Detecting diagram type based on the text "+t),t.match(/^\s*sequenceDiagram/)?"sequence":t.match(/^\s*gantt/)?"gantt":t.match(/^\s*classDiagram-v2/)?"classDiagram":t.match(/^\s*classDiagram/)?"class":t.match(/^\s*stateDiagram-v2/)?"stateDiagram":t.match(/^\s*stateDiagram/)?"state":t.match(/^\s*gitGraph/)?"git":t.match(/^\s*flowchart/)?"flowchart-v2":t.match(/^\s*info/)?"info":t.match(/^\s*pie/)?"pie":t.match(/^\s*erDiagram/)?"er":t.match(/^\s*journey/)?"journey":"flowchart"},O=function(t,e){var n={};return function(){for(var r=arguments.length,i=new Array(r),a=0;a"},n),x.lineBreakRegex.test(t))return t;var r=t.split(" "),i=[],a="";return r.forEach((function(t,o){var s=z("".concat(t," "),n),c=z(a,n);if(s>e){var u=Y(t,e,"-",n),l=u.hyphenatedStrings,h=u.remainingWord;i.push.apply(i,[a].concat(w(l))),a=h}else c+s>=e?(i.push(a),a=t):a=[a,t].filter(Boolean).join(" ");o+1===r.length&&i.push(a)})),i.filter((function(t){return""!==t})).join(n.joinWith)}),(function(t,e,n){return"".concat(t,"-").concat(e,"-").concat(n.fontSize,"-").concat(n.fontWeight,"-").concat(n.fontFamily,"-").concat(n.joinWith)})),Y=O((function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"-",r=arguments.length>3?arguments[3]:void 0;r=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",margin:0},r);var i=t.split(""),a=[],o="";return i.forEach((function(t,s){var c="".concat(o).concat(t);if(z(c,r)>=e){var u=s+1,l=i.length===u,h="".concat(c).concat(n);a.push(l?c:h),o=""}else o=c})),{hyphenatedStrings:a,remainingWord:o}}),(function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"-",r=arguments.length>3?arguments[3]:void 0;return"".concat(t,"-").concat(e,"-").concat(n,"-").concat(r.fontSize,"-").concat(r.fontWeight,"-").concat(r.fontFamily)})),z=function(t,e){return e=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial"},e),U(t,e).width},U=O((function(t,e){var n=e=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial"},e),r=n.fontSize,i=n.fontFamily,a=n.fontWeight;if(!t)return{width:0,height:0};var o=["sans-serif",i],s=t.split(x.lineBreakRegex),c=[],u=Object(d.select)("body");if(!u.remove)return{width:0,height:0,lineHeight:0};for(var l=u.append("svg"),h=0,f=o;hc[1].height&&c[0].width>c[1].width&&c[0].lineHeight>c[1].lineHeight?0:1]}),(function(t,e){return"".concat(t,"-").concat(e.fontSize,"-").concat(e.fontWeight,"-").concat(e.fontFamily)})),$=function(t,e,n){var r=new Map;return r.set("height",t),n?(r.set("width","100%"),r.set("style","max-width: ".concat(e,"px;"))):r.set("width",e),r},W=function(t,e,n,r){!function(t,e){var n=!0,r=!1,i=void 0;try{for(var a,o=e[Symbol.iterator]();!(n=(a=o.next()).done);n=!0){var s=a.value;t.attr(s[0],s[1])}}catch(t){r=!0,i=t}finally{try{n||null==o.return||o.return()}finally{if(r)throw i}}}(t,$(e,n,r))},H={assignWithDepth:I,wrapLabel:R,calculateTextHeight:function(t,e){return e=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",margin:15},e),U(t,e).height},calculateTextWidth:z,calculateTextDimensions:U,calculateSvgSizeAttrs:$,configureSvgSize:W,detectInit:function(t){var e=S(t,/(?:init\b)|(?:initialize\b)/),n={};if(Array.isArray(e)){var r=e.map((function(t){return t.args}));n=I(n,w(r))}else n=e.args;if(n){var i=M(t);["config"].forEach((function(t){void 0!==n[t]&&("flowchart-v2"===i&&(i="flowchart"),n[i]=n[t],delete n[t])}))}return n},detectDirective:S,detectType:M,isSubstringInArray:function(t,e){for(var n=0;n=1&&(i={x:t.x,y:t.y}),a>0&&a<1&&(i={x:(1-a)*e.x+a*t.x,y:(1-a)*e.y+a*t.y})}}e=t})),i}(t)},calcCardinalityPosition:function(t,e,n){var r;c.info("our points",e),e[0]!==n&&(e=e.reverse()),e.forEach((function(t){N(t,r),r=t}));var i,a=25;r=void 0,e.forEach((function(t){if(r&&!i){var e=N(t,r);if(e=1&&(i={x:t.x,y:t.y}),n>0&&n<1&&(i={x:(1-n)*r.x+n*t.x,y:(1-n)*r.y+n*t.y})}}r=t}));var o=t?10:5,s=Math.atan2(e[0].y-i.y,e[0].x-i.x),u={x:0,y:0};return u.x=Math.sin(s)*o+(e[0].x+i.x)/2,u.y=-Math.cos(s)*o+(e[0].y+i.y)/2,u},calcTerminalLabelPosition:function(t,e,n){var r,i=JSON.parse(JSON.stringify(n));c.info("our points",i),"start_left"!==e&&"start_right"!==e&&(i=i.reverse()),i.forEach((function(t){N(t,r),r=t}));var a,o=25;r=void 0,i.forEach((function(t){if(r&&!a){var e=N(t,r);if(e=1&&(a={x:t.x,y:t.y}),n>0&&n<1&&(a={x:(1-n)*r.x+n*t.x,y:(1-n)*r.y+n*t.y})}}r=t}));var s=10,u=Math.atan2(i[0].y-a.y,i[0].x-a.x),l={x:0,y:0};return l.x=Math.sin(u)*s+(i[0].x+a.x)/2,l.y=-Math.cos(u)*s+(i[0].y+a.y)/2,"start_left"===e&&(l.x=Math.sin(u+Math.PI)*s+(i[0].x+a.x)/2,l.y=-Math.cos(u+Math.PI)*s+(i[0].y+a.y)/2),"end_right"===e&&(l.x=Math.sin(u-Math.PI)*s+(i[0].x+a.x)/2-5,l.y=-Math.cos(u-Math.PI)*s+(i[0].y+a.y)/2-5),"end_left"===e&&(l.x=Math.sin(u)*s+(i[0].x+a.x)/2-5,l.y=-Math.cos(u)*s+(i[0].y+a.y)/2-5),l},formatUrl:function(t,e){var n=t.trim();if(n)return"loose"!==e.securityLevel?Object(g.sanitizeUrl)(n):n},getStylesFromArray:B,generateId:F,random:P,memoize:O,runFunc:function(t){for(var e,n=t.split("."),r=n.length-1,i=n[r],a=window,o=0;o1?s-1:0),u=1;u=0&&(n=!0)})),n},qt=function(t,e){var n=[];return t.nodes.forEach((function(r,i){Gt(e,r)||n.push(t.nodes[i])})),{nodes:n}},Xt={parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},defaultConfig:function(){return gt.flowchart},addVertex:function(t,e,n,r,i){var a,o=t;void 0!==o&&0!==o.trim().length&&(void 0===Dt[o]&&(Dt[o]={id:o,domId:"flowchart-"+o+"-"+Mt,styles:[],classes:[]}),Mt++,void 0!==e?(Ot=_t(),'"'===(a=x.sanitizeText(e.trim(),Ot))[0]&&'"'===a[a.length-1]&&(a=a.substring(1,a.length-1)),Dt[o].text=a):void 0===Dt[o].text&&(Dt[o].text=t),void 0!==n&&(Dt[o].type=n),null!=r&&r.forEach((function(t){Dt[o].styles.push(t)})),null!=i&&i.forEach((function(t){Dt[o].classes.push(t)})))},lookUpDomId:Yt,addLink:function(t,e,n,r){var i,a;for(i=0;i/)&&(At="LR"),At.match(/.*v/)&&(At="TB")},setClass:Ut,setTooltip:function(t,e){t.split(",").forEach((function(t){void 0!==e&&(Pt["gen-1"===St?Yt(t):t]=x.sanitizeText(e,Ot))}))},getTooltip:function(t){return Pt[t]},setClickEvent:function(t,e,n){t.split(",").forEach((function(t){!function(t,e,n){var r=Yt(t);if("loose"===_t().securityLevel&&void 0!==e){var i=[];if("string"==typeof n){i=n.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(var a=0;a=0)&&s.push(t))})),"gen-1"===St){c.warn("LOOKING UP");for(var l=0;l0&&function t(e,n){var r=Lt[n].nodes;if(!((Ht+=1)>2e3)){if(Vt[Ht]=n,Lt[n].id===e)return{result:!0,count:0};for(var i=0,a=1;i=0){var s=t(e,o);if(s.result)return{result:!0,count:a+s.count};a+=s.count}i+=1}return{result:!1,count:a}}}("none",Lt.length-1)},getSubGraphs:function(){return Lt},destructLink:function(t,e){var n,r=function(t){var e=t.trim(),n=e.slice(0,-1),r="arrow_open";switch(e.slice(-1)){case"x":r="arrow_cross","x"===e[0]&&(r="double_"+r,n=n.slice(1));break;case">":r="arrow_point","<"===e[0]&&(r="double_"+r,n=n.slice(1));break;case"o":r="arrow_circle","o"===e[0]&&(r="double_"+r,n=n.slice(1))}var i="normal",a=n.length-1;"="===n[0]&&(i="thick");var o=function(t,e){for(var n=e.length,r=0,i=0;in.height/2-a)){var o=a*a*(1-r*r/(i*i));0!=o&&(o=Math.sqrt(o)),o=a-o,t.y-n.y>0&&(o=-o),e.y+=o}return e},c}function de(t,e,n,r){return t.insert("polygon",":first-child").attr("points",r.map((function(t){return t.x+","+t.y})).join(" ")).attr("transform","translate("+-e/2+","+n/2+")")}var pe={addToRender:function(t){t.shapes().question=ne,t.shapes().hexagon=re,t.shapes().stadium=le,t.shapes().subroutine=he,t.shapes().cylinder=fe,t.shapes().rect_left_inv_arrow=ie,t.shapes().lean_right=ae,t.shapes().lean_left=oe,t.shapes().trapezoid=se,t.shapes().inv_trapezoid=ce,t.shapes().rect_right_inv_arrow=ue},addToRenderV2:function(t){t({question:ne}),t({hexagon:re}),t({stadium:le}),t({subroutine:he}),t({cylinder:fe}),t({rect_left_inv_arrow:ie}),t({lean_right:ae}),t({lean_left:oe}),t({trapezoid:se}),t({inv_trapezoid:ce}),t({rect_right_inv_arrow:ue})}},ge={},ye=function(t,e,n){var r=Object(d.select)('[id="'.concat(n,'"]'));Object.keys(t).forEach((function(n){var i=t[n],a="default";i.classes.length>0&&(a=i.classes.join(" "));var o,s=B(i.styles),u=void 0!==i.text?i.text:i.id;if(_t().flowchart.htmlLabels){var l={label:u.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"")}))};(o=ee()(r,l).node()).parentNode.removeChild(o)}else{var h=document.createElementNS("http://www.w3.org/2000/svg","text");h.setAttribute("style",s.labelStyle.replace("color:","fill:"));for(var f=u.split(x.lineBreakRegex),d=0;d').concat(a.text.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"")})),"")):(u.labelType="text",u.label=a.text.replace(x.lineBreakRegex,"\n"),void 0===a.style&&(u.style=u.style||"stroke: #333; stroke-width: 1.5px;fill:none"),u.labelStyle=u.labelStyle.replace("color:","fill:"))),u.id=o,u.class=s+" "+c,u.minlen=a.length||1,e.setEdge(Xt.lookUpDomId(a.start),Xt.lookUpDomId(a.end),u,i)}))},me=function(t){for(var e=Object.keys(t),n=0;n=0;h--)i=l[h],Xt.addVertex(i.id,i.title,"group",void 0,i.classes);var f=Xt.getVertices();c.warn("Get vertices",f);var p=Xt.getEdges(),g=0;for(g=l.length-1;g>=0;g--){i=l[g],Object(d.selectAll)("cluster").append("text");for(var y=0;y"),c.info("vertexText"+i),function(t){var e,n,r=Object(d.select)(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")),i=r.append("xhtml:div"),a=t.label,o=t.isNode?"nodeLabel":"edgeLabel";return i.html(''+a+""),e=i,(n=t.labelStyle)&&e.attr("style",n),i.style("display","inline-block"),i.style("white-space","nowrap"),i.attr("xmlns","http://www.w3.org/1999/xhtml"),r.node()}({isNode:r,label:i.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"")})),labelStyle:e.replace("fill:","color:")});var a=document.createElementNS("http://www.w3.org/2000/svg","text");a.setAttribute("style",e.replace("color:","fill:"));var o=[];o="string"==typeof i?i.split(/\\n|\n|/gi):Array.isArray(i)?i:[];for(var s=0;s0)t(a,n,r,i);else{var o=n.node(a);c.info("cp ",a," to ",i," with parent ",e),r.setNode(a,o),i!==n.parent(a)&&(c.warn("Setting parent",a,n.parent(a)),r.setParent(a,n.parent(a))),e!==i&&a!==e?(c.debug("Setting parent",a,e),r.setParent(a,e)):(c.info("In copy ",e,"root",i,"data",n.node(e),i),c.debug("Not Setting parent for node=",a,"cluster!==rootId",e!==i,"node!==clusterId",a!==e));var s=n.edges(a);c.debug("Copying Edges",s),s.forEach((function(t){c.info("Edge",t);var a=n.edge(t.v,t.w,t.name);c.info("Edge data",a,i);try{!function(t,e){return c.info("Decendants of ",e," is ",Oe[e]),c.info("Edge is ",t),t.v!==e&&(t.w!==e&&(Oe[e]?(c.info("Here "),Oe[e].indexOf(t.v)>=0||(!!Ne(t.v,e)||(!!Ne(t.w,e)||Oe[e].indexOf(t.w)>=0))):(c.debug("Tilt, ",e,",not in decendants"),!1)))}(t,i)?c.info("Skipping copy of edge ",t.v,"--\x3e",t.w," rootId: ",i," clusterId:",e):(c.info("Copying as ",t.v,t.w,a,t.name),r.setEdge(t.v,t.w,a,t.name),c.info("newGraph edges ",r.edges(),r.edge(r.edges()[0])))}catch(t){c.error(t)}}))}c.debug("Removing node",a),n.removeNode(a)}))},Le=function t(e,n){c.trace("Searching",e);var r=n.children(e);if(c.trace("Searching children of id ",e,r),r.length<1)return c.trace("This is a valid node",e),e;for(var i=0;i ",a),a}},Fe=function(t){return Me[t]&&Me[t].externalConnections&&Me[t]?Me[t].id:t},Pe=function(t,e){!t||e>10?c.debug("Opting out, no graph "):(c.debug("Opting in, graph "),t.nodes().forEach((function(e){t.children(e).length>0&&(c.warn("Cluster identified",e," Replacement id in edges: ",Le(e,t)),Oe[e]=function t(e,n){for(var r=n.children(e),i=[].concat(r),a=0;a0?(c.debug("Cluster identified",e,Oe),r.forEach((function(t){t.v!==e&&t.w!==e&&(Ne(t.v,e)^Ne(t.w,e)&&(c.warn("Edge: ",t," leaves cluster ",e),c.warn("Decendants of XXX ",e,": ",Oe[e]),Me[e].externalConnections=!0))}))):c.debug("Not a cluster ",e,Oe)})),t.edges().forEach((function(e){var n=t.edge(e);c.warn("Edge "+e.v+" -> "+e.w+": "+JSON.stringify(e)),c.warn("Edge "+e.v+" -> "+e.w+": "+JSON.stringify(t.edge(e)));var r=e.v,i=e.w;c.warn("Fix XXX",Me,"ids:",e.v,e.w,"Translateing: ",Me[e.v]," --- ",Me[e.w]),(Me[e.v]||Me[e.w])&&(c.warn("Fixing and trixing - removing XXX",e.v,e.w,e.name),r=Fe(e.v),i=Fe(e.w),t.removeEdge(e.v,e.w,e.name),r!==e.v&&(n.fromCluster=e.v),i!==e.w&&(n.toCluster=e.w),c.warn("Fix Replacing with XXX",r,i,e.name),t.setEdge(r,i,n,e.name))})),c.warn("Adjusted Graph",G.a.json.write(t)),Ie(t,0),c.trace(Me))},Ie=function t(e,n){if(c.warn("extractor - ",n,G.a.json.write(e),e.children("D")),n>10)c.error("Bailing out");else{for(var r=e.nodes(),i=!1,a=0;a0}if(i){c.debug("Nodes = ",r,n);for(var u=0;u0){c.warn("Cluster without external connections, without a parent and with children",l,n);var h=e.graph(),f=new G.a.Graph({multigraph:!0,compound:!0}).setGraph({rankdir:"TB"===h.rankdir?"LR":"TB",nodesep:50,ranksep:50,marginx:8,marginy:8}).setDefaultEdgeLabel((function(){return{}}));c.warn("Old graph before copy",G.a.json.write(e)),Be(l,e,f,l),e.setNode(l,{clusterNode:!0,id:l,clusterData:Me[l].clusterData,labelText:Me[l].labelText,graph:f}),c.warn("New graph after copy node: (",l,")",G.a.json.write(f)),c.debug("Old graph after copy",G.a.json.write(e))}else c.warn("Cluster ** ",l," **not meeting the criteria !externalConnections:",!Me[l].externalConnections," no parent: ",!e.parent(l)," children ",e.children(l)&&e.children(l).length>0,e.children("D"),n),c.debug(Me);else c.debug("Not a cluster",l,n)}r=e.nodes(),c.warn("New list of nodes",r);for(var d=0;d0}var $e=function(t,e,n,r){var i,a,o,s,c,u,l,h,f,d,p,g,y;if(i=e.y-t.y,o=t.x-e.x,c=e.x*t.y-t.x*e.y,f=i*n.x+o*n.y+c,d=i*r.x+o*r.y+c,!(0!==f&&0!==d&&Ue(f,d)||(a=r.y-n.y,s=n.x-r.x,u=r.x*n.y-n.x*r.y,l=a*t.x+s*t.y+u,h=a*e.x+s*e.y+u,0!==l&&0!==h&&Ue(l,h)||0==(p=i*s-a*o))))return g=Math.abs(p/2),{x:(y=o*u-s*c)<0?(y-g)/p:(y+g)/p,y:(y=a*c-i*u)<0?(y-g)/p:(y+g)/p}},We=function(t,e,n){var r=t.x,i=t.y,a=[],o=Number.POSITIVE_INFINITY,s=Number.POSITIVE_INFINITY;"function"==typeof e.forEach?e.forEach((function(t){o=Math.min(o,t.x),s=Math.min(s,t.y)})):(o=Math.min(o,e.x),s=Math.min(s,e.y));for(var c=r-t.width/2-o,u=i-t.height/2-s,l=0;l1&&a.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,a=Math.sqrt(r*r+i*i),o=e.x-n.x,s=e.y-n.y,c=Math.sqrt(o*o+s*s);return aMath.abs(o)*u?(s<0&&(u=-u),n=0===s?0:u*o/s,r=u):(o<0&&(c=-c),n=c,r=0===o?0:c*s/o),{x:i+n,y:a+r}},Ve={node:n.n(Re).a,circle:ze,ellipse:Ye,polygon:We,rect:He},Ge=function(t,e){var n=Ce(t,e,"node "+e.classes,!0),r=n.shapeSvg,i=n.bbox,a=n.halfPadding;c.info("Classes = ",e.classes);var o=r.insert("rect",":first-child");return o.attr("rx",e.rx).attr("ry",e.ry).attr("x",-i.width/2-a).attr("y",-i.height/2-a).attr("width",i.width+e.padding).attr("height",i.height+e.padding),Ae(e,o),e.intersect=function(t){return Ve.rect(e,t)},r};function qe(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e0){var r=t.split("~");n=r[0],e=r[1]}return{className:n,type:e}},tn=function(t){var e=Qe(t);void 0===Ze[e.className]&&(Ze[e.className]={id:e.className,type:e.type,cssClasses:[],methods:[],members:[],annotations:[],domId:"classid-"+e.className+"-"+Je},Je++)},en=function(t){for(var e=Object.keys(Ze),n=0;n>")?r.annotations.push(i.substring(2,i.length-2)):i.indexOf(")")>0?r.methods.push(i):i&&r.members.push(i)}},rn=function(t,e){t.split(",").forEach((function(t){var n=t;t[0].match(/\d/)&&(n="classid-"+n),void 0!==Ze[n]&&Ze[n].cssClasses.push(e)}))},an=function(t,e,n){var r=_t(),i=t,a=en(i);if("loose"===r.securityLevel&&void 0!==e&&void 0!==Ze[i]){var o=[];if("string"==typeof n){o=n.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(var s=0;s1&&a>i&&a<=t.length){var o="",s="",c=t.substring(0,1);c.match(/\w/)?s=t.substring(0,i).trim():(c.match(/\+|-|~|#/)&&(o=c),s=t.substring(1,i).trim());var u=t.substring(i+1,a),l=t.substring(a+1,1);n=yn(l),e=o+s+"("+gn(u.trim())+")",a<"".length&&""!==(r=t.substring(a+2).trim())&&(r=" : "+gn(r))}else e=gn(t);return{displayText:e,cssStyle:n}},pn=function(t,e,n,r){var i=ln(e),a=t.append("tspan").attr("x",r.padding).text(i.displayText);""!==i.cssStyle&&a.attr("style",i.cssStyle),n||a.attr("dy",r.textHeight)},gn=function t(e){var n=e;return-1!=e.indexOf("~")?t(n=(n=n.replace("~","<")).replace("~",">")):n},yn=function(t){switch(t){case"*":return"font-style:italic;";case"$":return"text-decoration:underline;";default:return""}},vn=function(t,e,n){c.info("Rendering class "+e);var r,i=e.id,a={id:i,label:e.id,width:0,height:0},o=t.append("g").attr("id",en(i)).attr("class","classGroup");r=e.link?o.append("svg:a").attr("xlink:href",e.link).attr("target",e.linkTarget).append("text").attr("y",n.textHeight+n.padding).attr("x",0):o.append("text").attr("y",n.textHeight+n.padding).attr("x",0);var s=!0;e.annotations.forEach((function(t){var e=r.append("tspan").text("«"+t+"»");s||e.attr("dy",n.textHeight),s=!1}));var u=e.id;void 0!==e.type&&""!==e.type&&(u+="<"+e.type+">");var l=r.append("tspan").text(u).attr("class","title");s||l.attr("dy",n.textHeight);var h=r.node().getBBox().height,f=o.append("line").attr("x1",0).attr("y1",n.padding+h+n.dividerMargin/2).attr("y2",n.padding+h+n.dividerMargin/2),d=o.append("text").attr("x",n.padding).attr("y",h+n.dividerMargin+n.textHeight).attr("fill","white").attr("class","classText");s=!0,e.members.forEach((function(t){pn(d,t,s,n),s=!1}));var p=d.node().getBBox(),g=o.append("line").attr("x1",0).attr("y1",n.padding+h+n.dividerMargin+p.height).attr("y2",n.padding+h+n.dividerMargin+p.height),y=o.append("text").attr("x",n.padding).attr("y",h+2*n.dividerMargin+p.height+n.textHeight).attr("fill","white").attr("class","classText");s=!0,e.methods.forEach((function(t){pn(y,t,s,n),s=!1}));var v=o.node().getBBox(),m=" ";e.cssClasses.length>0&&(m+=e.cssClasses.join(" "));var b=o.insert("rect",":first-child").attr("x",0).attr("y",0).attr("width",v.width+2*n.padding).attr("height",v.height+n.padding+.5*n.dividerMargin).attr("class",m).node().getBBox().width;return r.node().childNodes.forEach((function(t){t.setAttribute("x",(b-t.getBBox().width)/2)})),e.tooltip&&r.insert("title").text(e.tooltip),f.attr("x2",b),g.attr("x2",b),a.width=b,a.height=v.height+n.padding+.5*n.dividerMargin,a},mn=function(t,e,n,r){var i=function(t){switch(t){case on.AGGREGATION:return"aggregation";case on.EXTENSION:return"extension";case on.COMPOSITION:return"composition";case on.DEPENDENCY:return"dependency"}};e.points=e.points.filter((function(t){return!Number.isNaN(t.y)}));var a,o,s=e.points,u=Object(d.line)().x((function(t){return t.x})).y((function(t){return t.y})).curve(d.curveBasis),l=t.append("path").attr("d",u(s)).attr("id","edge"+un).attr("class","relation"),h="";r.arrowMarkerAbsolute&&(h=(h=(h=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),1==n.relation.lineType&&l.attr("class","relation dashed-line"),"none"!==n.relation.type1&&l.attr("marker-start","url("+h+"#"+i(n.relation.type1)+"Start)"),"none"!==n.relation.type2&&l.attr("marker-end","url("+h+"#"+i(n.relation.type2)+"End)");var f,p,g,y,v=e.points.length,m=H.calcLabelPosition(e.points);if(a=m.x,o=m.y,v%2!=0&&v>1){var b=H.calcCardinalityPosition("none"!==n.relation.type1,e.points,e.points[0]),x=H.calcCardinalityPosition("none"!==n.relation.type2,e.points,e.points[v-1]);c.debug("cardinality_1_point "+JSON.stringify(b)),c.debug("cardinality_2_point "+JSON.stringify(x)),f=b.x,p=b.y,g=x.x,y=x.y}if(void 0!==n.title){var _=t.append("g").attr("class","classLabel"),k=_.append("text").attr("class","label").attr("x",a).attr("y",o).attr("fill","red").attr("text-anchor","middle").text(n.title);window.label=k;var w=k.node().getBBox();_.insert("rect",":first-child").attr("class","box").attr("x",w.x-r.padding/2).attr("y",w.y-r.padding/2).attr("width",w.width+r.padding).attr("height",w.height+r.padding)}(c.info("Rendering relation "+JSON.stringify(n)),void 0!==n.relationTitle1&&"none"!==n.relationTitle1)&&t.append("g").attr("class","cardinality").append("text").attr("class","type1").attr("x",f).attr("y",p).attr("fill","black").attr("font-size","6").text(n.relationTitle1);void 0!==n.relationTitle2&&"none"!==n.relationTitle2&&t.append("g").attr("class","cardinality").append("text").attr("class","type2").attr("x",g).attr("y",y).attr("fill","black").attr("font-size","6").text(n.relationTitle2);un++},bn=function(t,e,n){var r=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),i=70,a=10;"LR"===n&&(i=10,a=70);var o=r.append("rect").style("stroke","black").style("fill","black").attr("x",-1*i/2).attr("y",-1*a/2).attr("width",i).attr("height",a).attr("class","fork-join");return Ae(e,o),e.height=e.height+e.padding/2,e.width=e.width+e.padding/2,e.intersect=function(t){return Ve.rect(e,t)},r},xn={question:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding+(i.height+e.padding),o=[{x:a/2,y:0},{x:a,y:-a/2},{x:a/2,y:-a},{x:0,y:-a/2}];c.info("Question main (Circle)");var s=Se(r,a,a,o);return Ae(e,s),e.intersect=function(t){return c.warn("Intersect called"),Ve.polygon(e,o,t)},r},rect:function(t,e){var n=Ce(t,e,"node "+e.classes,!0),r=n.shapeSvg,i=n.bbox,a=n.halfPadding;c.trace("Classes = ",e.classes);var o=r.insert("rect",":first-child");return o.attr("class","basic label-container").attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("x",-i.width/2-a).attr("y",-i.height/2-a).attr("width",i.width+e.padding).attr("height",i.height+e.padding),Ae(e,o),e.intersect=function(t){return Ve.rect(e,t)},r},rectWithTitle:function(t,e){var n;n=e.classes?"node "+e.classes:"node default";var r=t.insert("g").attr("class",n).attr("id",e.domId||e.id),i=r.insert("rect",":first-child"),a=r.insert("line"),o=r.insert("g").attr("class","label"),s=e.labelText.flat();c.info("Label text",s[0]);var u,l=o.node().appendChild(Te(s[0],e.labelStyle,!0,!0));if(_t().flowchart.htmlLabels){var h=l.children[0],f=Object(d.select)(l);u=h.getBoundingClientRect(),f.attr("width",u.width),f.attr("height",u.height)}c.info("Text 2",s);var p=s.slice(1,s.length),g=l.getBBox(),y=o.node().appendChild(Te(p.join("
"),e.labelStyle,!0,!0));if(_t().flowchart.htmlLabels){var v=y.children[0],m=Object(d.select)(y);u=v.getBoundingClientRect(),m.attr("width",u.width),m.attr("height",u.height)}var b=e.padding/2;return Object(d.select)(y).attr("transform","translate( "+(u.width>g.width?0:(g.width-u.width)/2)+", "+(g.height+b+5)+")"),Object(d.select)(l).attr("transform","translate( "+(u.widthe.height/2-s)){var i=s*s*(1-r*r/(o*o));0!=i&&(i=Math.sqrt(i)),i=s-i,t.y-e.y>0&&(i=-i),n.y+=i}return n},r},start:function(t,e){var n=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),r=n.insert("circle",":first-child");return r.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),Ae(e,r),e.intersect=function(t){return Ve.circle(e,7,t)},n},end:function(t,e){var n=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),r=n.insert("circle",":first-child"),i=n.insert("circle",":first-child");return i.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),r.attr("class","state-end").attr("r",5).attr("width",10).attr("height",10),Ae(e,i),e.intersect=function(t){return Ve.circle(e,7,t)},n},note:Ge,subroutine:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding,o=i.height+e.padding,s=Se(r,a,o,[{x:0,y:0},{x:a,y:0},{x:a,y:-o},{x:0,y:-o},{x:0,y:0},{x:-8,y:0},{x:a+8,y:0},{x:a+8,y:-o},{x:-8,y:-o},{x:-8,y:0}]);return Ae(e,s),e.intersect=function(t){return Ve.polygon(e,t)},r},fork:bn,join:bn,class_box:function(t,e){var n,r=e.padding/2;n=e.classes?"node "+e.classes:"node default";var i=t.insert("g").attr("class",n).attr("id",e.domId||e.id),a=i.insert("rect",":first-child"),o=i.insert("line"),s=i.insert("line"),c=0,u=4,l=i.insert("g").attr("class","label"),h=0,f=e.classData.annotations&&e.classData.annotations[0],p=e.classData.annotations[0]?"«"+e.classData.annotations[0]+"»":"",g=l.node().appendChild(Te(p,e.labelStyle,!0,!0)),y=g.getBBox();if(_t().flowchart.htmlLabels){var v=g.children[0],m=Object(d.select)(g);y=v.getBoundingClientRect(),m.attr("width",y.width),m.attr("height",y.height)}e.classData.annotations[0]&&(u+=y.height+4,c+=y.width);var b=e.classData.id;void 0!==e.classData.type&&""!==e.classData.type&&(b+="<"+e.classData.type+">");var x=l.node().appendChild(Te(b,e.labelStyle,!0,!0));Object(d.select)(x).attr("class","classTitle");var _=x.getBBox();if(_t().flowchart.htmlLabels){var k=x.children[0],w=Object(d.select)(x);_=k.getBoundingClientRect(),w.attr("width",_.width),w.attr("height",_.height)}u+=_.height+4,_.width>c&&(c=_.width);var E=[];e.classData.members.forEach((function(t){var n=ln(t).displayText,r=l.node().appendChild(Te(n,e.labelStyle,!0,!0)),i=r.getBBox();if(_t().flowchart.htmlLabels){var a=r.children[0],o=Object(d.select)(r);i=a.getBoundingClientRect(),o.attr("width",i.width),o.attr("height",i.height)}i.width>c&&(c=i.width),u+=i.height+4,E.push(r)})),u+=8;var T=[];if(e.classData.methods.forEach((function(t){var n=ln(t).displayText,r=l.node().appendChild(Te(n,e.labelStyle,!0,!0)),i=r.getBBox();if(_t().flowchart.htmlLabels){var a=r.children[0],o=Object(d.select)(r);i=a.getBoundingClientRect(),o.attr("width",i.width),o.attr("height",i.height)}i.width>c&&(c=i.width),u+=i.height+4,T.push(r)})),u+=8,f){var C=(c-y.width)/2;Object(d.select)(g).attr("transform","translate( "+(-1*c/2+C)+", "+-1*u/2+")"),h=y.height+4}var A=(c-_.width)/2;return Object(d.select)(x).attr("transform","translate( "+(-1*c/2+A)+", "+(-1*u/2+h)+")"),h+=_.height+4,o.attr("class","divider").attr("x1",-c/2-r).attr("x2",c/2+r).attr("y1",-u/2-r+8+h).attr("y2",-u/2-r+8+h),h+=8,E.forEach((function(t){Object(d.select)(t).attr("transform","translate( "+-c/2+", "+(-1*u/2+h+4)+")"),h+=_.height+4})),h+=8,s.attr("class","divider").attr("x1",-c/2-r).attr("x2",c/2+r).attr("y1",-u/2-r+8+h).attr("y2",-u/2-r+8+h),h+=8,T.forEach((function(t){Object(d.select)(t).attr("transform","translate( "+-c/2+", "+(-1*u/2+h)+")"),h+=_.height+4})),a.attr("class","outer title-state").attr("x",-c/2-r).attr("y",-u/2-r).attr("width",c+e.padding).attr("height",u+e.padding),Ae(e,a),e.intersect=function(t){return Ve.rect(e,t)},i}},_n={},kn=function(t){var e=_n[t.id];c.trace("Transforming node",t,"translate("+(t.x-t.width/2-5)+", "+(t.y-t.height/2-5)+")");t.clusterNode?e.attr("transform","translate("+(t.x-t.width/2-8)+", "+(t.y-t.height/2-8)+")"):e.attr("transform","translate("+t.x+", "+t.y+")")},wn={rect:function(t,e){c.trace("Creating subgraph rect for ",e.id,e);var n=t.insert("g").attr("class","cluster"+(e.class?" "+e.class:"")).attr("id",e.id),r=n.insert("rect",":first-child"),i=n.insert("g").attr("class","cluster-label"),a=i.node().appendChild(Te(e.labelText,e.labelStyle,void 0,!0)),o=a.getBBox();if(_t().flowchart.htmlLabels){var s=a.children[0],u=Object(d.select)(a);o=s.getBoundingClientRect(),u.attr("width",o.width),u.attr("height",o.height)}var l=0*e.padding,h=l/2;c.trace("Data ",e,JSON.stringify(e)),r.attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("x",e.x-e.width/2-h).attr("y",e.y-e.height/2-h).attr("width",e.width+l).attr("height",e.height+l),i.attr("transform","translate("+(e.x-o.width/2)+", "+(e.y-e.height/2+e.padding/3)+")");var f=r.node().getBBox();return e.width=f.width,e.height=f.height,e.intersect=function(t){return He(e,t)},n},roundedWithTitle:function(t,e){var n=t.insert("g").attr("class",e.classes).attr("id",e.id),r=n.insert("rect",":first-child"),i=n.insert("g").attr("class","cluster-label"),a=n.append("rect"),o=i.node().appendChild(Te(e.labelText,e.labelStyle,void 0,!0)),s=o.getBBox();if(_t().flowchart.htmlLabels){var c=o.children[0],u=Object(d.select)(o);s=c.getBoundingClientRect(),u.attr("width",s.width),u.attr("height",s.height)}s=o.getBBox();var l=0*e.padding,h=l/2;r.attr("class","outer").attr("x",e.x-e.width/2-h).attr("y",e.y-e.height/2-h).attr("width",e.width+l).attr("height",e.height+l),a.attr("class","inner").attr("x",e.x-e.width/2-h).attr("y",e.y-e.height/2-h+s.height-1).attr("width",e.width+l).attr("height",e.height+l-s.height-3),i.attr("transform","translate("+(e.x-s.width/2)+", "+(e.y-e.height/2-e.padding/3+(_t().flowchart.htmlLabels?5:3))+")");var f=r.node().getBBox();return e.width=f.width,e.height=f.height,e.intersect=function(t){return He(e,t)},n},noteGroup:function(t,e){var n=t.insert("g").attr("class","note-cluster").attr("id",e.id),r=n.insert("rect",":first-child"),i=0*e.padding,a=i/2;r.attr("rx",e.rx).attr("ry",e.ry).attr("x",e.x-e.width/2-a).attr("y",e.y-e.height/2-a).attr("width",e.width+i).attr("height",e.height+i).attr("fill","none");var o=r.node().getBBox();return e.width=o.width,e.height=o.height,e.intersect=function(t){return He(e,t)},n},divider:function(t,e){var n=t.insert("g").attr("class",e.classes).attr("id",e.id),r=n.insert("rect",":first-child"),i=0*e.padding,a=i/2;r.attr("class","divider").attr("x",e.x-e.width/2-a).attr("y",e.y-e.height/2).attr("width",e.width+i).attr("height",e.height+i);var o=r.node().getBBox();return e.width=o.width,e.height=o.height,e.intersect=function(t){return He(e,t)},n}},En={},Tn={},Cn={},An=function(t,e){var n=t.x,r=t.y,i=Math.abs(e.x-n),a=Math.abs(e.y-r),o=t.width/2,s=t.height/2;return i>=o||a>=s},Sn=function(t,e,n){c.warn("intersection calc o:",e," i:",n,t);var r=t.x,i=t.y,a=Math.abs(r-n.x),o=t.width/2,s=n.xMath.abs(r-e.x)*u){var y=n.y0&&c.info("Recursive edges",n.edge(n.edges()[0]));var s=o.insert("g").attr("class","clusters"),u=o.insert("g").attr("class","edgePaths"),l=o.insert("g").attr("class","edgeLabels"),h=o.insert("g").attr("class","nodes");return n.nodes().forEach((function(e){var o=n.node(e);if(void 0!==i){var s=JSON.parse(JSON.stringify(i.clusterData));c.info("Setting data for cluster XXX (",e,") ",s,i),n.setNode(i.id,s),n.parent(e)||(c.warn("Setting parent",e,i.id),n.setParent(e,i.id,s))}if(c.info("(Insert) Node XXX"+e+": "+JSON.stringify(n.node(e))),o&&o.clusterNode){c.info("Cluster identified",e,o,n.node(e));var u=t(h,o.graph,r,n.node(e));Ae(o,u),function(t,e){_n[e.id]=t}(u,o),c.warn("Recursive render complete",u,o)}else n.children(e).length>0?(c.info("Cluster - the non recursive path XXX",e,o.id,o,n),c.info(Le(o.id,n)),Me[o.id]={id:Le(o.id,n),node:o}):(c.info("Node - the non recursive path",e,o.id,o),function(t,e,n){var r,i;e.link?(r=t.insert("svg:a").attr("xlink:href",e.link).attr("target",e.linkTarget||"_blank"),i=xn[e.shape](r,e,n)):r=i=xn[e.shape](t,e,n),e.tooltip&&i.attr("title",e.tooltip),e.class&&i.attr("class","node default "+e.class),_n[e.id]=r,e.haveCallback&&_n[e.id].attr("class",_n[e.id].attr("class")+" clickable")}(h,n.node(e),a))})),n.edges().forEach((function(t){var e=n.edge(t.v,t.w,t.name);c.info("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(t)),c.info("Edge "+t.v+" -> "+t.w+": ",t," ",JSON.stringify(n.edge(t))),c.info("Fix",Me,"ids:",t.v,t.w,"Translateing: ",Me[t.v],Me[t.w]),function(t,e){var n=Te(e.label,e.labelStyle),r=t.insert("g").attr("class","edgeLabel"),i=r.insert("g").attr("class","label");i.node().appendChild(n);var a=n.getBBox();if(_t().flowchart.htmlLabels){var o=n.children[0],s=Object(d.select)(n);a=o.getBoundingClientRect(),s.attr("width",a.width),s.attr("height",a.height)}if(i.attr("transform","translate("+-a.width/2+", "+-a.height/2+")"),Tn[e.id]=r,e.width=a.width,e.height=a.height,e.startLabelLeft){var c=Te(e.startLabelLeft,e.labelStyle),u=t.insert("g").attr("class","edgeTerminals"),l=u.insert("g").attr("class","inner");l.node().appendChild(c);var h=c.getBBox();l.attr("transform","translate("+-h.width/2+", "+-h.height/2+")"),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].startLeft=u}if(e.startLabelRight){var f=Te(e.startLabelRight,e.labelStyle),p=t.insert("g").attr("class","edgeTerminals"),g=p.insert("g").attr("class","inner");p.node().appendChild(f),g.node().appendChild(f);var y=f.getBBox();g.attr("transform","translate("+-y.width/2+", "+-y.height/2+")"),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].startRight=p}if(e.endLabelLeft){var v=Te(e.endLabelLeft,e.labelStyle),m=t.insert("g").attr("class","edgeTerminals"),b=m.insert("g").attr("class","inner");b.node().appendChild(v);var x=v.getBBox();b.attr("transform","translate("+-x.width/2+", "+-x.height/2+")"),m.node().appendChild(v),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].endLeft=m}if(e.endLabelRight){var _=Te(e.endLabelRight,e.labelStyle),k=t.insert("g").attr("class","edgeTerminals"),w=k.insert("g").attr("class","inner");w.node().appendChild(_);var E=_.getBBox();w.attr("transform","translate("+-E.width/2+", "+-E.height/2+")"),k.node().appendChild(_),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].endRight=k}}(l,e)})),n.edges().forEach((function(t){c.info("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(t))})),c.info("#############################################"),c.info("### Layout ###"),c.info("#############################################"),c.info(n),ke.a.layout(n),c.info("Graph after layout:",G.a.json.write(n)),je(n).forEach((function(t){var e=n.node(t);c.info("Position "+t+": "+JSON.stringify(n.node(t))),c.info("Position "+t+": ("+e.x,","+e.y,") width: ",e.width," height: ",e.height),e&&e.clusterNode?kn(e):n.children(t).length>0?(!function(t,e){c.trace("Inserting cluster");var n=e.shape||"rect";En[e.id]=wn[n](t,e)}(s,e),Me[e.id].node=e):kn(e)})),n.edges().forEach((function(t){var e=n.edge(t);c.info("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(e),e);var i=function(t,e,n,r,i,a){var o=n.points,s=!1,u=a.node(e.v),l=a.node(e.w);if(l.intersect&&u.intersect&&((o=o.slice(1,n.points.length-1)).unshift(u.intersect(o[0])),c.info("Last point",o[o.length-1],l,l.intersect(o[o.length-1])),o.push(l.intersect(o[o.length-1]))),n.toCluster){var h;c.trace("edge",n),c.trace("to cluster",r[n.toCluster]),o=[];var f=!1;n.points.forEach((function(t){var e=r[n.toCluster].node;if(An(e,t)||f)f||o.push(t);else{c.trace("inside",n.toCluster,t,h);var i=Sn(e,h,t),a=!1;o.forEach((function(t){a=a||t.x===i.x&&t.y===i.y})),o.find((function(t){return t.x===i.x&&t.y===i.y}))?c.warn("no intersect",i,o):o.push(i),f=!0}h=t})),s=!0}if(n.fromCluster){c.trace("edge",n),c.warn("from cluster",r[n.fromCluster]);for(var p,g=[],y=!1,v=o.length-1;v>=0;v--){var m=o[v],b=r[n.fromCluster].node;if(An(b,m)||y)c.trace("Outside point",m),y||g.unshift(m);else{c.warn("inside",n.fromCluster,m,b);var x=Sn(b,p,m);g.unshift(x),y=!0}p=m}o=g,s=!0}var _,k=o.filter((function(t){return!Number.isNaN(t.y)})),w=Object(d.line)().x((function(t){return t.x})).y((function(t){return t.y})).curve(d.curveBasis);switch(n.thickness){case"normal":_="edge-thickness-normal";break;case"thick":_="edge-thickness-thick";break;default:_=""}switch(n.pattern){case"solid":_+=" edge-pattern-solid";break;case"dotted":_+=" edge-pattern-dotted";break;case"dashed":_+=" edge-pattern-dashed"}var E=t.append("path").attr("d",w(k)).attr("id",n.id).attr("class"," "+_+(n.classes?" "+n.classes:"")).attr("style",n.style),T="";switch(_t().state.arrowMarkerAbsolute&&(T=(T=(T=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),c.info("arrowTypeStart",n.arrowTypeStart),c.info("arrowTypeEnd",n.arrowTypeEnd),n.arrowTypeStart){case"arrow_cross":E.attr("marker-start","url("+T+"#"+i+"-crossStart)");break;case"arrow_point":E.attr("marker-start","url("+T+"#"+i+"-pointStart)");break;case"arrow_barb":E.attr("marker-start","url("+T+"#"+i+"-barbStart)");break;case"arrow_circle":E.attr("marker-start","url("+T+"#"+i+"-circleStart)");break;case"aggregation":E.attr("marker-start","url("+T+"#"+i+"-aggregationStart)");break;case"extension":E.attr("marker-start","url("+T+"#"+i+"-extensionStart)");break;case"composition":E.attr("marker-start","url("+T+"#"+i+"-compositionStart)");break;case"dependency":E.attr("marker-start","url("+T+"#"+i+"-dependencyStart)")}switch(n.arrowTypeEnd){case"arrow_cross":E.attr("marker-end","url("+T+"#"+i+"-crossEnd)");break;case"arrow_point":E.attr("marker-end","url("+T+"#"+i+"-pointEnd)");break;case"arrow_barb":E.attr("marker-end","url("+T+"#"+i+"-barbEnd)");break;case"arrow_circle":E.attr("marker-end","url("+T+"#"+i+"-circleEnd)");break;case"aggregation":E.attr("marker-end","url("+T+"#"+i+"-aggregationEnd)");break;case"extension":E.attr("marker-end","url("+T+"#"+i+"-extensionEnd)");break;case"composition":E.attr("marker-end","url("+T+"#"+i+"-compositionEnd)");break;case"dependency":E.attr("marker-end","url("+T+"#"+i+"-dependencyEnd)")}var C={};return s&&(C.updatedPath=o),C.originalPath=n.points,C}(u,t,e,Me,r,n);!function(t,e){c.info("Moving label",t.id,t.label,Tn[t.id]);var n=e.updatedPath?e.updatedPath:e.originalPath;if(t.label){var r=Tn[t.id],i=t.x,a=t.y;if(n){var o=H.calcLabelPosition(n);c.info("Moving label from (",i,",",a,") to (",o.x,",",o.y,")")}r.attr("transform","translate("+i+", "+a+")")}if(t.startLabelLeft){var s=Cn[t.id].startLeft,u=t.x,l=t.y;if(n){var h=H.calcTerminalLabelPosition(0,"start_left",n);u=h.x,l=h.y}s.attr("transform","translate("+u+", "+l+")")}if(t.startLabelRight){var f=Cn[t.id].startRight,d=t.x,p=t.y;if(n){var g=H.calcTerminalLabelPosition(0,"start_right",n);d=g.x,p=g.y}f.attr("transform","translate("+d+", "+p+")")}if(t.endLabelLeft){var y=Cn[t.id].endLeft,v=t.x,m=t.y;if(n){var b=H.calcTerminalLabelPosition(0,"end_left",n);v=b.x,m=b.y}y.attr("transform","translate("+v+", "+m+")")}if(t.endLabelRight){var x=Cn[t.id].endRight,_=t.x,k=t.y;if(n){var w=H.calcTerminalLabelPosition(0,"end_right",n);_=w.x,k=w.y}x.attr("transform","translate("+_+", "+k+")")}}(e,i)})),o},On=function(t,e,n,r,i){Ee(t,n,r,i),_n={},Tn={},Cn={},En={},Oe={},De={},Me={},c.warn("Graph at first:",G.a.json.write(e)),Pe(e),c.warn("Graph after:",G.a.json.write(e)),Mn(t,e,r)},Dn={},Nn=function(t,e,n){var r=Object(d.select)('[id="'.concat(n,'"]'));Object.keys(t).forEach((function(n){var i=t[n],a="default";i.classes.length>0&&(a=i.classes.join(" "));var o,s=B(i.styles),u=void 0!==i.text?i.text:i.id;if(_t().flowchart.htmlLabels){var l={label:u.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"")}))};(o=ee()(r,l).node()).parentNode.removeChild(o)}else{var h=document.createElementNS("http://www.w3.org/2000/svg","text");h.setAttribute("style",s.labelStyle.replace("color:","fill:"));for(var f=u.split(x.lineBreakRegex),d=0;d=0;h--)i=l[h],c.info("Subgraph - ",i),Xt.addVertex(i.id,i.title,"group",void 0,i.classes);var f=Xt.getVertices(),p=Xt.getEdges();c.info(p);var g=0;for(g=l.length-1;g>=0;g--){i=l[g],Object(d.selectAll)("cluster").append("text");for(var y=0;y0)switch(e.valign){case"top":case"start":s=function(){return Math.round(e.y+e.textMargin)};break;case"middle":case"center":s=function(){return Math.round(e.y+(n+r+e.textMargin)/2)};break;case"bottom":case"end":s=function(){return Math.round(e.y+(n+r+2*e.textMargin)-e.textMargin)}}if(void 0!==e.anchor&&void 0!==e.textMargin&&void 0!==e.width)switch(e.anchor){case"left":case"start":e.x=Math.round(e.x+e.textMargin),e.anchor="start",e.dominantBaseline="text-after-edge",e.alignmentBaseline="middle";break;case"middle":case"center":e.x=Math.round(e.x+e.width/2),e.anchor="middle",e.dominantBaseline="middle",e.alignmentBaseline="middle";break;case"right":case"end":e.x=Math.round(e.x+e.width-e.textMargin),e.anchor="end",e.dominantBaseline="text-before-edge",e.alignmentBaseline="middle"}for(var c=0;c0&&(r+=(l._groups||l)[0][0].getBBox().height,n=r),a.push(l)}return a},jn=function(t,e){var n,r,i,a,o,s=t.append("polygon");return s.attr("points",(n=e.x,r=e.y,i=e.width,a=e.height,n+","+r+" "+(n+i)+","+r+" "+(n+i)+","+(r+a-(o=7))+" "+(n+i-1.2*o)+","+(r+a)+" "+n+","+(r+a))),s.attr("class","labelBox"),e.y=e.y+e.height/2,In(t,e),s},Rn=-1,Yn=function(){return{x:0,y:0,fill:void 0,anchor:void 0,style:"#666",width:void 0,height:void 0,textMargin:0,rx:0,ry:0,tspan:!0,valign:void 0}},zn=function(){return{x:0,y:0,fill:"#EDF2AE",stroke:"#666",width:100,anchor:"start",height:100,rx:0,ry:0}},Un=function(){function t(t,e,n,i,a,o,s){r(e.append("text").attr("x",n+a/2).attr("y",i+o/2+5).style("text-anchor","middle").text(t),s)}function e(t,e,n,i,a,o,s,c){for(var u=c.actorFontSize,l=c.actorFontFamily,h=c.actorFontWeight,f=t.split(x.lineBreakRegex),d=0;d2&&void 0!==arguments[2]?arguments[2]:{text:void 0,wrap:void 0},r=arguments.length>3?arguments[3]:void 0;if(r===ir.ACTIVE_END){var i=er(t.actor);if(i<1){var a=new Error("Trying to inactivate an inactive participant ("+t.actor+")");throw a.hash={text:"->>-",token:"->>-",line:"1",loc:{first_line:1,last_line:1,first_column:1,last_column:1},expected:["'ACTIVE_PARTICIPANT'"]},a}}return qn.push({from:t,to:e,message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap,type:r}),!0},rr=function(){return Qn},ir={SOLID:0,DOTTED:1,NOTE:2,SOLID_CROSS:3,DOTTED_CROSS:4,SOLID_OPEN:5,DOTTED_OPEN:6,LOOP_START:10,LOOP_END:11,ALT_START:12,ALT_ELSE:13,ALT_END:14,OPT_START:15,OPT_END:16,ACTIVE_START:17,ACTIVE_END:18,PAR_START:19,PAR_AND:20,PAR_END:21,RECT_START:22,RECT_END:23},ar=function(t,e,n){var r={actor:t,placement:e,message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap},i=[].concat(t,t);Xn.push(r),qn.push({from:i[0],to:i[1],message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap,type:ir.NOTE,placement:e})},or=function(t){Zn=t.text,Jn=void 0===t.wrap&&rr()||!!t.wrap},sr={addActor:tr,addMessage:function(t,e,n,r){qn.push({from:t,to:e,message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap,answer:r})},addSignal:nr,autoWrap:rr,setWrap:function(t){Qn=t},enableSequenceNumbers:function(){Kn=!0},showSequenceNumbers:function(){return Kn},getMessages:function(){return qn},getActors:function(){return Gn},getActor:function(t){return Gn[t]},getActorKeys:function(){return Object.keys(Gn)},getTitle:function(){return Zn},parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().sequence},getTitleWrapped:function(){return Jn},clear:function(){Gn={},qn=[]},parseMessage:function(t){var e=t.trim(),n={text:e.replace(/^[:]?(?:no)?wrap:/,"").trim(),wrap:null!==e.match(/^[:]?wrap:/)||null===e.match(/^[:]?nowrap:/)&&void 0};return c.debug("parseMessage:",n),n},LINETYPE:ir,ARROWTYPE:{FILLED:0,OPEN:1},PLACEMENT:{LEFTOF:0,RIGHTOF:1,OVER:2},addNote:ar,setTitle:or,apply:function t(e){if(e instanceof Array)e.forEach((function(e){t(e)}));else switch(e.type){case"addActor":tr(e.actor,e.actor,e.description);break;case"activeStart":case"activeEnd":nr(e.actor,void 0,void 0,e.signalType);break;case"addNote":ar(e.actor,e.placement,e.text);break;case"addMessage":nr(e.from,e.to,e.msg,e.signalType);break;case"loopStart":nr(void 0,void 0,e.loopText,e.signalType);break;case"loopEnd":nr(void 0,void 0,void 0,e.signalType);break;case"rectStart":nr(void 0,void 0,e.color,e.signalType);break;case"rectEnd":nr(void 0,void 0,void 0,e.signalType);break;case"optStart":nr(void 0,void 0,e.optText,e.signalType);break;case"optEnd":nr(void 0,void 0,void 0,e.signalType);break;case"altStart":case"else":nr(void 0,void 0,e.altText,e.signalType);break;case"altEnd":nr(void 0,void 0,void 0,e.signalType);break;case"setTitle":or(e.text);break;case"parStart":case"and":nr(void 0,void 0,e.parText,e.signalType);break;case"parEnd":nr(void 0,void 0,void 0,e.signalType)}}};Wn.parser.yy=sr;var cr={},ur={data:{startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},verticalPos:0,sequenceItems:[],activations:[],models:{getHeight:function(){return Math.max.apply(null,0===this.actors.length?[0]:this.actors.map((function(t){return t.height||0})))+(0===this.loops.length?0:this.loops.map((function(t){return t.height||0})).reduce((function(t,e){return t+e})))+(0===this.messages.length?0:this.messages.map((function(t){return t.height||0})).reduce((function(t,e){return t+e})))+(0===this.notes.length?0:this.notes.map((function(t){return t.height||0})).reduce((function(t,e){return t+e})))},clear:function(){this.actors=[],this.loops=[],this.messages=[],this.notes=[]},addActor:function(t){this.actors.push(t)},addLoop:function(t){this.loops.push(t)},addMessage:function(t){this.messages.push(t)},addNote:function(t){this.notes.push(t)},lastActor:function(){return this.actors[this.actors.length-1]},lastLoop:function(){return this.loops[this.loops.length-1]},lastMessage:function(){return this.messages[this.messages.length-1]},lastNote:function(){return this.notes[this.notes.length-1]},actors:[],loops:[],messages:[],notes:[]},init:function(){this.sequenceItems=[],this.activations=[],this.models.clear(),this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},this.verticalPos=0,pr(Wn.parser.yy.getConfig())},updateVal:function(t,e,n,r){void 0===t[e]?t[e]=n:t[e]=r(n,t[e])},updateBounds:function(t,e,n,r){var i=this,a=0;function o(o){return function(s){a++;var c=i.sequenceItems.length-a+1;i.updateVal(s,"starty",e-c*cr.boxMargin,Math.min),i.updateVal(s,"stopy",r+c*cr.boxMargin,Math.max),i.updateVal(ur.data,"startx",t-c*cr.boxMargin,Math.min),i.updateVal(ur.data,"stopx",n+c*cr.boxMargin,Math.max),"activation"!==o&&(i.updateVal(s,"startx",t-c*cr.boxMargin,Math.min),i.updateVal(s,"stopx",n+c*cr.boxMargin,Math.max),i.updateVal(ur.data,"starty",e-c*cr.boxMargin,Math.min),i.updateVal(ur.data,"stopy",r+c*cr.boxMargin,Math.max))}}this.sequenceItems.forEach(o()),this.activations.forEach(o("activation"))},insert:function(t,e,n,r){var i=Math.min(t,n),a=Math.max(t,n),o=Math.min(e,r),s=Math.max(e,r);this.updateVal(ur.data,"startx",i,Math.min),this.updateVal(ur.data,"starty",o,Math.min),this.updateVal(ur.data,"stopx",a,Math.max),this.updateVal(ur.data,"stopy",s,Math.max),this.updateBounds(i,o,a,s)},newActivation:function(t,e,n){var r=n[t.from.actor],i=gr(t.from.actor).length||0,a=r.x+r.width/2+(i-1)*cr.activationWidth/2;this.activations.push({startx:a,starty:this.verticalPos+2,stopx:a+cr.activationWidth,stopy:void 0,actor:t.from.actor,anchored:$n.anchorElement(e)})},endActivation:function(t){var e=this.activations.map((function(t){return t.actor})).lastIndexOf(t.from.actor);return this.activations.splice(e,1)[0]},createLoop:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{message:void 0,wrap:!1,width:void 0},e=arguments.length>1?arguments[1]:void 0;return{startx:void 0,starty:this.verticalPos,stopx:void 0,stopy:void 0,title:t.message,wrap:t.wrap,width:t.width,height:0,fill:e}},newLoop:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{message:void 0,wrap:!1,width:void 0},e=arguments.length>1?arguments[1]:void 0;this.sequenceItems.push(this.createLoop(t,e))},endLoop:function(){return this.sequenceItems.pop()},addSectionToLoop:function(t){var e=this.sequenceItems.pop();e.sections=e.sections||[],e.sectionTitles=e.sectionTitles||[],e.sections.push({y:ur.getVerticalPos(),height:0}),e.sectionTitles.push(t),this.sequenceItems.push(e)},bumpVerticalPos:function(t){this.verticalPos=this.verticalPos+t,this.data.stopy=this.verticalPos},getVerticalPos:function(){return this.verticalPos},getBounds:function(){return{bounds:this.data,models:this.models}}},lr=function(t){return{fontFamily:t.messageFontFamily,fontSize:t.messageFontSize,fontWeight:t.messageFontWeight}},hr=function(t){return{fontFamily:t.noteFontFamily,fontSize:t.noteFontSize,fontWeight:t.noteFontWeight}},fr=function(t){return{fontFamily:t.actorFontFamily,fontSize:t.actorFontSize,fontWeight:t.actorFontWeight}},dr=function(t,e,n,r){for(var i=0,a=0,o=0;o0&&o.forEach((function(r){if(n=r,i.startx===i.stopx){var a=e[t.from],o=e[t.to];n.from=Math.min(a.x-i.width/2,a.x-a.width/2,n.from),n.to=Math.max(o.x+i.width/2,o.x+a.width/2,n.to),n.width=Math.max(n.width,Math.abs(n.to-n.from))-cr.labelBoxWidth}else n.from=Math.min(i.startx,n.from),n.to=Math.max(i.stopx,n.to),n.width=Math.max(n.width,i.width)-cr.labelBoxWidth})))})),ur.activations=[],c.debug("Loop type widths:",a),a},_r={bounds:ur,drawActors:dr,setConf:pr,draw:function(t,e){cr=_t().sequence,Wn.parser.yy.clear(),Wn.parser.yy.setWrap(cr.wrap),Wn.parser.parse(t+"\n"),ur.init(),c.debug("C:".concat(JSON.stringify(cr,null,2)));var n=Object(d.select)('[id="'.concat(e,'"]')),r=Wn.parser.yy.getActors(),i=Wn.parser.yy.getActorKeys(),a=Wn.parser.yy.getMessages(),o=Wn.parser.yy.getTitle(),s=mr(r,a);cr.height=br(r,s),dr(n,r,i,0);var u=xr(a,r,s);$n.insertArrowHead(n),$n.insertArrowCrossHead(n),$n.insertSequenceNumber(n);var l=1;a.forEach((function(t){var e,i,a;switch(t.type){case Wn.parser.yy.LINETYPE.NOTE:i=t.noteModel,function(t,e){ur.bumpVerticalPos(cr.boxMargin),e.height=cr.boxMargin,e.starty=ur.getVerticalPos();var n=$n.getNoteRect();n.x=e.startx,n.y=e.starty,n.width=e.width||cr.width,n.class="note";var r=t.append("g"),i=$n.drawRect(r,n),a=$n.getTextObj();a.x=e.startx,a.y=e.starty,a.width=n.width,a.dy="1em",a.text=e.message,a.class="noteText",a.fontFamily=cr.noteFontFamily,a.fontSize=cr.noteFontSize,a.fontWeight=cr.noteFontWeight,a.anchor=cr.noteAlign,a.textMargin=cr.noteMargin,a.valign=cr.noteAlign;var o=In(r,a),s=Math.round(o.map((function(t){return(t._groups||t)[0][0].getBBox().height})).reduce((function(t,e){return t+e})));i.attr("height",s+2*cr.noteMargin),e.height+=s+2*cr.noteMargin,ur.bumpVerticalPos(s+2*cr.noteMargin),e.stopy=e.starty+s+2*cr.noteMargin,e.stopx=e.startx+n.width,ur.insert(e.startx,e.starty,e.stopx,e.stopy),ur.models.addNote(e)}(n,i);break;case Wn.parser.yy.LINETYPE.ACTIVE_START:ur.newActivation(t,n,r);break;case Wn.parser.yy.LINETYPE.ACTIVE_END:!function(t,e){var r=ur.endActivation(t);r.starty+18>e&&(r.starty=e-6,e+=12),$n.drawActivation(n,r,e,cr,gr(t.from.actor).length),ur.insert(r.startx,e-10,r.stopx,e)}(t,ur.getVerticalPos());break;case Wn.parser.yy.LINETYPE.LOOP_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.LOOP_END:e=ur.endLoop(),$n.drawLoop(n,e,"loop",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;case Wn.parser.yy.LINETYPE.RECT_START:vr(u,t,cr.boxMargin,cr.boxMargin,(function(t){return ur.newLoop(void 0,t.message)}));break;case Wn.parser.yy.LINETYPE.RECT_END:e=ur.endLoop(),$n.drawBackgroundRect(n,e),ur.models.addLoop(e),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos());break;case Wn.parser.yy.LINETYPE.OPT_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.OPT_END:e=ur.endLoop(),$n.drawLoop(n,e,"opt",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;case Wn.parser.yy.LINETYPE.ALT_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.ALT_ELSE:vr(u,t,cr.boxMargin+cr.boxTextMargin,cr.boxMargin,(function(t){return ur.addSectionToLoop(t)}));break;case Wn.parser.yy.LINETYPE.ALT_END:e=ur.endLoop(),$n.drawLoop(n,e,"alt",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;case Wn.parser.yy.LINETYPE.PAR_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.PAR_AND:vr(u,t,cr.boxMargin+cr.boxTextMargin,cr.boxMargin,(function(t){return ur.addSectionToLoop(t)}));break;case Wn.parser.yy.LINETYPE.PAR_END:e=ur.endLoop(),$n.drawLoop(n,e,"par",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;default:try{(a=t.msgModel).starty=ur.getVerticalPos(),a.sequenceIndex=l,function(t,e){ur.bumpVerticalPos(10);var n=e.startx,r=e.stopx,i=e.starty,a=e.message,o=e.type,s=e.sequenceIndex,c=x.splitBreaks(a).length,u=H.calculateTextDimensions(a,lr(cr)),l=u.height/c;e.height+=l,ur.bumpVerticalPos(l);var h=$n.getTextObj();h.x=n,h.y=i+10,h.width=r-n,h.class="messageText",h.dy="1em",h.text=a,h.fontFamily=cr.messageFontFamily,h.fontSize=cr.messageFontSize,h.fontWeight=cr.messageFontWeight,h.anchor=cr.messageAlign,h.valign=cr.messageAlign,h.textMargin=cr.wrapPadding,h.tspan=!1,In(t,h);var f,d,p=u.height-10,g=u.width;if(n===r){d=ur.getVerticalPos()+p,cr.rightAngles?f=t.append("path").attr("d","M ".concat(n,",").concat(d," H ").concat(n+Math.max(cr.width/2,g/2)," V ").concat(d+25," H ").concat(n)):(p+=cr.boxMargin,d=ur.getVerticalPos()+p,f=t.append("path").attr("d","M "+n+","+d+" C "+(n+60)+","+(d-10)+" "+(n+60)+","+(d+30)+" "+n+","+(d+20))),p+=30;var y=Math.max(g/2,cr.width/2);ur.insert(n-y,ur.getVerticalPos()-10+p,r+y,ur.getVerticalPos()+30+p)}else p+=cr.boxMargin,d=ur.getVerticalPos()+p,(f=t.append("line")).attr("x1",n),f.attr("y1",d),f.attr("x2",r),f.attr("y2",d),ur.insert(n,d-10,r,d);o===Wn.parser.yy.LINETYPE.DOTTED||o===Wn.parser.yy.LINETYPE.DOTTED_CROSS||o===Wn.parser.yy.LINETYPE.DOTTED_OPEN?(f.style("stroke-dasharray","3, 3"),f.attr("class","messageLine1")):f.attr("class","messageLine0");var v="";cr.arrowMarkerAbsolute&&(v=(v=(v=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),f.attr("stroke-width",2),f.attr("stroke","none"),f.style("fill","none"),o!==Wn.parser.yy.LINETYPE.SOLID&&o!==Wn.parser.yy.LINETYPE.DOTTED||f.attr("marker-end","url("+v+"#arrowhead)"),o!==Wn.parser.yy.LINETYPE.SOLID_CROSS&&o!==Wn.parser.yy.LINETYPE.DOTTED_CROSS||f.attr("marker-end","url("+v+"#crosshead)"),(sr.showSequenceNumbers()||cr.showSequenceNumbers)&&(f.attr("marker-start","url("+v+"#sequencenumber)"),t.append("text").attr("x",n).attr("y",d+4).attr("font-family","sans-serif").attr("font-size","12px").attr("text-anchor","middle").attr("textLength","16px").attr("class","sequenceNumber").text(s)),ur.bumpVerticalPos(p),e.height+=p,e.stopy=e.starty+e.height,ur.insert(e.fromBounds,e.starty,e.toBounds,e.stopy)}(n,a),ur.models.addMessage(a)}catch(t){c.error("error while drawing message",t)}}[Wn.parser.yy.LINETYPE.SOLID_OPEN,Wn.parser.yy.LINETYPE.DOTTED_OPEN,Wn.parser.yy.LINETYPE.SOLID,Wn.parser.yy.LINETYPE.DOTTED,Wn.parser.yy.LINETYPE.SOLID_CROSS,Wn.parser.yy.LINETYPE.DOTTED_CROSS].includes(t.type)&&l++})),cr.mirrorActors&&(ur.bumpVerticalPos(2*cr.boxMargin),dr(n,r,i,ur.getVerticalPos()));var h=ur.getBounds().bounds;c.debug("For line height fix Querying: #"+e+" .actor-line"),Object(d.selectAll)("#"+e+" .actor-line").attr("y2",h.stopy);var f=h.stopy-h.starty+2*cr.diagramMarginY;cr.mirrorActors&&(f=f-cr.boxMargin+cr.bottomMarginAdj);var p=h.stopx-h.startx+2*cr.diagramMarginX;o&&n.append("text").text(o).attr("x",(h.stopx-h.startx)/2-2*cr.diagramMarginX).attr("y",-25),W(n,f,p,cr.useMaxWidth);var g=o?40:0;n.attr("viewBox",h.startx-cr.diagramMarginX+" -"+(cr.diagramMarginY+g)+" "+p+" "+(f+g)),c.debug("models:",ur.models)}},kr=n(27),wr=n.n(kr);function Er(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e=6&&n.indexOf("weekends")>=0||(n.indexOf(t.format("dddd").toLowerCase())>=0||n.indexOf(t.format(e.trim()))>=0)},Yr=function(t,e,n){if(n.length&&!t.manualEndTime){var r=o()(t.startTime,e,!0);r.add(1,"d");var i=o()(t.endTime,e,!0),a=zr(r,i,e,n);t.endTime=i.toDate(),t.renderEndTime=a}},zr=function(t,e,n,r){for(var i=!1,a=null;t<=e;)i||(a=e.toDate()),(i=Rr(t,n,r))&&e.add(1,"d"),t.add(1,"d");return a},Ur=function(t,e,n){n=n.trim();var r=/^after\s+([\d\w- ]+)/.exec(n.trim());if(null!==r){var i=null;if(r[1].split(" ").forEach((function(t){var e=Xr(t);void 0!==e&&(i?e.endTime>i.endTime&&(i=e):i=e)})),i)return i.endTime;var a=new Date;return a.setHours(0,0,0,0),a}var s=o()(n,e.trim(),!0);return s.isValid()?s.toDate():(c.debug("Invalid date:"+n),c.debug("With date format:"+e.trim()),new Date)},$r=function(t,e){if(null!==t)switch(t[2]){case"s":e.add(t[1],"seconds");break;case"m":e.add(t[1],"minutes");break;case"h":e.add(t[1],"hours");break;case"d":e.add(t[1],"days");break;case"w":e.add(t[1],"weeks")}return e.toDate()},Wr=function(t,e,n,r){r=r||!1,n=n.trim();var i=o()(n,e.trim(),!0);return i.isValid()?(r&&i.add(1,"d"),i.toDate()):$r(/^([\d]+)([wdhms])/.exec(n.trim()),o()(t))},Hr=0,Vr=function(t){return void 0===t?"task"+(Hr+=1):t},Gr=[],qr={},Xr=function(t){var e=qr[t];return Gr[e]},Zr=function(){for(var t=function(t){var e=Gr[t],n="";switch(Gr[t].raw.startTime.type){case"prevTaskEnd":var r=Xr(e.prevTaskId);e.startTime=r.endTime;break;case"getStartDate":(n=Ur(0,Ar,Gr[t].raw.startTime.startData))&&(Gr[t].startTime=n)}return Gr[t].startTime&&(Gr[t].endTime=Wr(Gr[t].startTime,Ar,Gr[t].raw.endTime.data,Ir),Gr[t].endTime&&(Gr[t].processed=!0,Gr[t].manualEndTime=o()(Gr[t].raw.endTime.data,"YYYY-MM-DD",!0).isValid(),Yr(Gr[t],Ar,Or))),Gr[t].processed},e=!0,n=0;nr?i=1:n0&&(e=t.classes.join(" "));for(var n=0,r=0;rn-e?n+a+1.5*ni.leftPadding>u?e+r-5:n+r+5:(n-e)/2+e+r})).attr("y",(function(t,r){return t.order*e+ni.barHeight/2+(ni.fontSize/2-2)+n})).attr("text-height",i).attr("class",(function(t){var e=o(t.startTime),n=o(t.endTime);t.milestone&&(n=e+i);var r=this.getBBox().width,a="";t.classes.length>0&&(a=t.classes.join(" "));for(var c=0,l=0;ln-e?n+r+1.5*ni.leftPadding>u?a+" taskTextOutsideLeft taskTextOutside"+c+" "+h:a+" taskTextOutsideRight taskTextOutside"+c+" "+h+" width-"+r:a+" taskText taskText"+c+" "+h+" width-"+r}))}(t,i,c,h,r,0,e),function(t,e){for(var n=[],r=0,i=0;i0&&a.setAttribute("dy","1em"),a.textContent=e[i],r.appendChild(a)}return r})).attr("x",10).attr("y",(function(i,a){if(!(a>0))return i[1]*t/2+e;for(var o=0;o "+t.w+": "+JSON.stringify(i.edge(t))),mn(r,i.edge(t),i.edge(t).relation,ci))}));var h=r.node().getBBox(),f=h.width+40,p=h.height+40;W(r,p,f,ci.useMaxWidth);var g="".concat(h.x-20," ").concat(h.y-20," ").concat(f," ").concat(p);c.debug("viewBox ".concat(g)),r.attr("viewBox",g)};ai.parser.yy=cn;var fi={dividerMargin:10,padding:5,textHeight:10},di=function(t){Object.keys(t).forEach((function(e){fi[e]=t[e]}))},pi=function(t,e){c.info("Drawing class"),cn.clear(),ai.parser.parse(t);var n=_t().flowchart;c.info("config:",n);var r=n.nodeSpacing||50,i=n.rankSpacing||50,a=new G.a.Graph({multigraph:!0,compound:!0}).setGraph({rankdir:"TD",nodesep:r,ranksep:i,marginx:8,marginy:8}).setDefaultEdgeLabel((function(){return{}})),o=cn.getClasses(),s=cn.getRelations();c.info(s),function(t,e){var n=Object.keys(t);c.info("keys:",n),c.info(t),n.forEach((function(n){var r=t[n],i="";r.cssClasses.length>0&&(i=i+" "+r.cssClasses.join(" "));var a={labelStyle:""},o=void 0!==r.text?r.text:r.id,s="";switch(r.type){case"class":s="class_box";break;default:s="class_box"}e.setNode(r.id,{labelStyle:a.labelStyle,shape:s,labelText:o,classData:r,rx:0,ry:0,class:i,style:a.style,id:r.id,domId:r.domId,haveCallback:r.haveCallback,link:r.link,width:"group"===r.type?500:void 0,type:r.type,padding:_t().flowchart.padding}),c.info("setNode",{labelStyle:a.labelStyle,shape:s,labelText:o,rx:0,ry:0,class:i,style:a.style,id:r.id,width:"group"===r.type?500:void 0,type:r.type,padding:_t().flowchart.padding})}))}(o,a),function(t,e){var n=0;t.forEach((function(r){n++;var i={classes:"relation"};i.pattern=1==r.relation.lineType?"dashed":"solid",i.id="id"+n,"arrow_open"===r.type?i.arrowhead="none":i.arrowhead="normal",c.info(i,r),i.startLabelRight="none"===r.relationTitle1?"":r.relationTitle1,i.endLabelLeft="none"===r.relationTitle2?"":r.relationTitle2,i.arrowTypeStart=gi(r.relation.type1),i.arrowTypeEnd=gi(r.relation.type2);var a="",o="";if(void 0!==r.style){var s=B(r.style);a=s.style,o=s.labelStyle}else a="fill:none";i.style=a,i.labelStyle=o,void 0!==r.interpolate?i.curve=D(r.interpolate,d.curveLinear):void 0!==t.defaultInterpolate?i.curve=D(t.defaultInterpolate,d.curveLinear):i.curve=D(fi.curve,d.curveLinear),r.text=r.title,void 0===r.text?void 0!==r.style&&(i.arrowheadStyle="fill: #333"):(i.arrowheadStyle="fill: #333",i.labelpos="c",_t().flowchart.htmlLabels,i.labelType="text",i.label=r.text.replace(x.lineBreakRegex,"\n"),void 0===r.style&&(i.style=i.style||"stroke: #333; stroke-width: 1.5px;fill:none"),i.labelStyle=i.labelStyle.replace("color:","fill:")),e.setEdge(r.id1,r.id2,i,n)}))}(s,a);var u=Object(d.select)('[id="'.concat(e,'"]'));u.attr("xmlns:xlink","http://www.w3.org/1999/xlink");var l=Object(d.select)("#"+e+" g");On(l,a,["aggregation","extension","composition","dependency"],"classDiagram",e);var h=u.node().getBBox(),f=h.width+16,p=h.height+16;if(c.debug("new ViewBox 0 0 ".concat(f," ").concat(p),"translate(".concat(8-a._label.marginx,", ").concat(8-a._label.marginy,")")),W(u,p,f,n.useMaxWidth),u.attr("viewBox","0 0 ".concat(f," ").concat(p)),u.select("g").attr("transform","translate(".concat(8-a._label.marginx,", ").concat(8-h.y,")")),!n.htmlLabels)for(var g=document.querySelectorAll('[id="'+e+'"] .edgeLabel .label'),y=0;y0&&o.length>0){var c={stmt:"state",id:F(),type:"divider",doc:mi(o)};i.push(mi(c)),n.doc=i}n.doc.forEach((function(e){return t(n,e,!0)}))}}({id:"root"},{id:"root",doc:bi},!0),{id:"root",doc:bi}},extract:function(t){var e;e=t.doc?t.doc:t,c.info(e),Ei(),c.info("Extract",e),e.forEach((function(t){"state"===t.stmt&&wi(t.id,t.type,t.doc,t.description,t.note),"relation"===t.stmt&&Ti(t.state1.id,t.state2.id,t.description)}))},trimColon:function(t){return t&&":"===t[0]?t.substr(1).trim():t.trim()}},Oi=n(22),Di=n.n(Oi),Ni={},Bi=function(t,e){Ni[t]=e},Li=function(t,e){var n=t.append("text").attr("x",2*_t().state.padding).attr("y",_t().state.textHeight+1.3*_t().state.padding).attr("font-size",_t().state.fontSize).attr("class","state-title").text(e.descriptions[0]).node().getBBox(),r=n.height,i=t.append("text").attr("x",_t().state.padding).attr("y",r+.4*_t().state.padding+_t().state.dividerMargin+_t().state.textHeight).attr("class","state-description"),a=!0,o=!0;e.descriptions.forEach((function(t){a||(!function(t,e,n){var r=t.append("tspan").attr("x",2*_t().state.padding).text(e);n||r.attr("dy",_t().state.textHeight)}(i,t,o),o=!1),a=!1}));var s=t.append("line").attr("x1",_t().state.padding).attr("y1",_t().state.padding+r+_t().state.dividerMargin/2).attr("y2",_t().state.padding+r+_t().state.dividerMargin/2).attr("class","descr-divider"),c=i.node().getBBox(),u=Math.max(c.width,n.width);return s.attr("x2",u+3*_t().state.padding),t.insert("rect",":first-child").attr("x",_t().state.padding).attr("y",_t().state.padding).attr("width",u+2*_t().state.padding).attr("height",c.height+r+2*_t().state.padding).attr("rx",_t().state.radius),t},Fi=function(t,e,n){var r,i=_t().state.padding,a=2*_t().state.padding,o=t.node().getBBox(),s=o.width,c=o.x,u=t.append("text").attr("x",0).attr("y",_t().state.titleShift).attr("font-size",_t().state.fontSize).attr("class","state-title").text(e.id),l=u.node().getBBox().width+a,h=Math.max(l,s);h===s&&(h+=a);var f=t.node().getBBox();e.doc,r=c-i,l>s&&(r=(s-h)/2+i),Math.abs(c-f.x)s&&(r=c-(l-s)/2);var d=1-_t().state.textHeight;return t.insert("rect",":first-child").attr("x",r).attr("y",d).attr("class",n?"alt-composit":"composit").attr("width",h).attr("height",f.height+_t().state.textHeight+_t().state.titleShift+1).attr("rx","0"),u.attr("x",r+i),l<=s&&u.attr("x",c+(h-a)/2-l/2+i),t.insert("rect",":first-child").attr("x",r).attr("y",_t().state.titleShift-_t().state.textHeight-_t().state.padding).attr("width",h).attr("height",3*_t().state.textHeight).attr("rx",_t().state.radius),t.insert("rect",":first-child").attr("x",r).attr("y",_t().state.titleShift-_t().state.textHeight-_t().state.padding).attr("width",h).attr("height",f.height+3+2*_t().state.textHeight).attr("rx",_t().state.radius),t},Pi=function(t,e){e.attr("class","state-note");var n=e.append("rect").attr("x",0).attr("y",_t().state.padding),r=function(t,e,n,r){var i=0,a=r.append("text");a.style("text-anchor","start"),a.attr("class","noteText");var o=t.replace(/\r\n/g,"
"),s=(o=o.replace(/\n/g,"
")).split(x.lineBreakRegex),c=1.25*_t().state.noteMargin,u=!0,l=!1,h=void 0;try{for(var f,d=s[Symbol.iterator]();!(u=(f=d.next()).done);u=!0){var p=f.value.trim();if(p.length>0){var g=a.append("tspan");if(g.text(p),0===c)c+=g.node().getBBox().height;i+=c,g.attr("x",e+_t().state.noteMargin),g.attr("y",n+i+1.25*_t().state.noteMargin)}}}catch(t){l=!0,h=t}finally{try{u||null==d.return||d.return()}finally{if(l)throw h}}return{textWidth:a.node().getBBox().width,textHeight:i}}(t,0,0,e.append("g")),i=r.textWidth,a=r.textHeight;return n.attr("height",a+2*_t().state.noteMargin),n.attr("width",i+2*_t().state.noteMargin),n},Ii=function(t,e){var n=e.id,r={id:n,label:e.id,width:0,height:0},i=t.append("g").attr("id",n).attr("class","stateGroup");"start"===e.type&&function(t){t.append("circle").attr("class","start-state").attr("r",_t().state.sizeUnit).attr("cx",_t().state.padding+_t().state.sizeUnit).attr("cy",_t().state.padding+_t().state.sizeUnit)}(i),"end"===e.type&&function(t){t.append("circle").attr("class","end-state-outer").attr("r",_t().state.sizeUnit+_t().state.miniPadding).attr("cx",_t().state.padding+_t().state.sizeUnit+_t().state.miniPadding).attr("cy",_t().state.padding+_t().state.sizeUnit+_t().state.miniPadding),t.append("circle").attr("class","end-state-inner").attr("r",_t().state.sizeUnit).attr("cx",_t().state.padding+_t().state.sizeUnit+2).attr("cy",_t().state.padding+_t().state.sizeUnit+2)}(i),"fork"!==e.type&&"join"!==e.type||function(t,e){var n=_t().state.forkWidth,r=_t().state.forkHeight;if(e.parentId){var i=n;n=r,r=i}t.append("rect").style("stroke","black").style("fill","black").attr("width",n).attr("height",r).attr("x",_t().state.padding).attr("y",_t().state.padding)}(i,e),"note"===e.type&&Pi(e.note.text,i),"divider"===e.type&&function(t){t.append("line").style("stroke","grey").style("stroke-dasharray","3").attr("x1",_t().state.textHeight).attr("class","divider").attr("x2",2*_t().state.textHeight).attr("y1",0).attr("y2",0)}(i),"default"===e.type&&0===e.descriptions.length&&function(t,e){var n=t.append("text").attr("x",2*_t().state.padding).attr("y",_t().state.textHeight+2*_t().state.padding).attr("font-size",_t().state.fontSize).attr("class","state-title").text(e.id),r=n.node().getBBox();t.insert("rect",":first-child").attr("x",_t().state.padding).attr("y",_t().state.padding).attr("width",r.width+2*_t().state.padding).attr("height",r.height+2*_t().state.padding).attr("rx",_t().state.radius)}(i,e),"default"===e.type&&e.descriptions.length>0&&Li(i,e);var a=i.node().getBBox();return r.width=a.width+2*_t().state.padding,r.height=a.height+2*_t().state.padding,Bi(n,r),r},ji=0;Oi.parser.yy=Mi;var Ri={},Yi=function t(e,n,r,i){var a,o=new G.a.Graph({compound:!0,multigraph:!0}),s=!0;for(a=0;a "+t.w+": "+JSON.stringify(o.edge(t))),function(t,e,n){e.points=e.points.filter((function(t){return!Number.isNaN(t.y)}));var r=e.points,i=Object(d.line)().x((function(t){return t.x})).y((function(t){return t.y})).curve(d.curveBasis),a=t.append("path").attr("d",i(r)).attr("id","edge"+ji).attr("class","transition"),o="";if(_t().state.arrowMarkerAbsolute&&(o=(o=(o=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),a.attr("marker-end","url("+o+"#"+function(t){switch(t){case Mi.relationType.AGGREGATION:return"aggregation";case Mi.relationType.EXTENSION:return"extension";case Mi.relationType.COMPOSITION:return"composition";case Mi.relationType.DEPENDENCY:return"dependency"}}(Mi.relationType.DEPENDENCY)+"End)"),void 0!==n.title){for(var s=t.append("g").attr("class","stateLabel"),u=H.calcLabelPosition(e.points),l=u.x,h=u.y,f=x.getRows(n.title),p=0,g=[],y=0,v=0,m=0;m<=f.length;m++){var b=s.append("text").attr("text-anchor","middle").text(f[m]).attr("x",l).attr("y",h+p),_=b.node().getBBox();if(y=Math.max(y,_.width),v=Math.min(v,_.x),c.info(_.x,l,h+p),0===p){var k=b.node().getBBox();p=k.height,c.info("Title height",p,h)}g.push(b)}var w=p*f.length;if(f.length>1){var E=(f.length-1)*p*.5;g.forEach((function(t,e){return t.attr("y",h+e*p-E)})),w=p*f.length}var T=s.node().getBBox();s.insert("rect",":first-child").attr("class","box").attr("x",l-y/2-_t().state.padding/2).attr("y",h-w/2-_t().state.padding/2-3.5).attr("width",y+_t().state.padding).attr("height",w+_t().state.padding),c.info(T)}ji++}(n,o.edge(t),o.edge(t).relation))})),w=k.getBBox();var E={id:r||"root",label:r||"root",width:0,height:0};return E.width=w.width+2*vi.padding,E.height=w.height+2*vi.padding,c.debug("Doc rendered",E,o),E},zi=function(){},Ui=function(t,e){vi=_t().state,Oi.parser.yy.clear(),Oi.parser.parse(t),c.debug("Rendering diagram "+t);var n=Object(d.select)("[id='".concat(e,"']"));n.append("defs").append("marker").attr("id","dependencyEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z"),new G.a.Graph({multigraph:!0,compound:!0,rankdir:"RL"}).setDefaultEdgeLabel((function(){return{}}));var r=Mi.getRootDoc();Yi(r,n,void 0,!1);var i=vi.padding,a=n.node().getBBox(),o=a.width+2*i,s=a.height+2*i;W(n,s,1.75*o,vi.useMaxWidth),n.attr("viewBox","".concat(a.x-vi.padding," ").concat(a.y-vi.padding," ")+o+" "+s)},$i={},Wi={},Hi=function(t,e,n,r){if("root"!==n.id){var i="rect";!0===n.start&&(i="start"),!1===n.start&&(i="end"),"default"!==n.type&&(i=n.type),Wi[n.id]||(Wi[n.id]={id:n.id,shape:i,description:n.id,classes:"statediagram-state"}),n.description&&(Array.isArray(Wi[n.id].description)?(Wi[n.id].shape="rectWithTitle",Wi[n.id].description.push(n.description)):Wi[n.id].description.length>0?(Wi[n.id].shape="rectWithTitle",Wi[n.id].description===n.id?Wi[n.id].description=[n.description]:Wi[n.id].description=[Wi[n.id].description,n.description]):(Wi[n.id].shape="rect",Wi[n.id].description=n.description)),!Wi[n.id].type&&n.doc&&(c.info("Setting cluser for ",n.id),Wi[n.id].type="group",Wi[n.id].shape="divider"===n.type?"divider":"roundedWithTitle",Wi[n.id].classes=Wi[n.id].classes+" "+(r?"statediagram-cluster statediagram-cluster-alt":"statediagram-cluster"));var a={labelStyle:"",shape:Wi[n.id].shape,labelText:Wi[n.id].description,classes:Wi[n.id].classes,style:"",id:n.id,domId:"state-"+n.id+"-"+Vi,type:Wi[n.id].type,padding:15};if(n.note){var o={labelStyle:"",shape:"note",labelText:n.note.text,classes:"statediagram-note",style:"",id:n.id+"----note",domId:"state-"+n.id+"----note-"+Vi,type:Wi[n.id].type,padding:15},s={labelStyle:"",shape:"noteGroup",labelText:n.note.text,classes:Wi[n.id].classes,style:"",id:n.id+"----parent",domId:"state-"+n.id+"----parent-"+Vi,type:"group",padding:0};Vi++,t.setNode(n.id+"----parent",s),t.setNode(o.id,o),t.setNode(n.id,a),t.setParent(n.id,n.id+"----parent"),t.setParent(o.id,n.id+"----parent");var u=n.id,l=o.id;"left of"===n.note.position&&(u=o.id,l=n.id),t.setEdge(u,l,{arrowhead:"none",arrowType:"",style:"fill:none",labelStyle:"",classes:"transition note-edge",arrowheadStyle:"fill: #333",labelpos:"c",labelType:"text",thickness:"normal"})}else t.setNode(n.id,a)}e&&"root"!==e.id&&(c.info("Setting node ",n.id," to be child of its parent ",e.id),t.setParent(n.id,e.id)),n.doc&&(c.info("Adding nodes children "),Gi(t,n,n.doc,!r))},Vi=0,Gi=function(t,e,n,r){Vi=0,c.trace("items",n),n.forEach((function(n){if("state"===n.stmt||"default"===n.stmt)Hi(t,e,n,r);else if("relation"===n.stmt){Hi(t,e,n.state1,r),Hi(t,e,n.state2,r);var i={id:"edge"+Vi,arrowhead:"normal",arrowTypeEnd:"arrow_barb",style:"fill:none",labelStyle:"",label:n.description,arrowheadStyle:"fill: #333",labelpos:"c",labelType:"text",thickness:"normal",classes:"transition"},a=n.state1.id,o=n.state2.id;t.setEdge(a,o,i,Vi),Vi++}}))},qi=function(t){for(var e=Object.keys(t),n=0;ne.seq?t:e}),t[0]),n="";t.forEach((function(t){n+=t===e?"\t*":"\t|"}));var r,i,a,o=[n,e.id,e.seq];for(var s in Ki)Ki[s]===e.id&&o.push(s);if(c.debug(o.join(" ")),Array.isArray(e.parent)){var u=Zi[e.parent[0]];aa(t,e,u),t.push(Zi[e.parent[1]])}else{if(null==e.parent)return;var l=Zi[e.parent];aa(t,e,l)}r=t,i=function(t){return t.id},a=Object.create(null),oa(t=r.reduce((function(t,e){var n=i(e);return a[n]||(a[n]=!0,t.push(e)),t}),[]))}var sa,ca=function(){var t=Object.keys(Zi).map((function(t){return Zi[t]}));return t.forEach((function(t){c.debug(t.id)})),t.sort((function(t,e){return e.seq-t.seq})),t},ua={setDirection:function(t){ta=t},setOptions:function(t){c.debug("options str",t),t=(t=t&&t.trim())||"{}";try{ia=JSON.parse(t)}catch(t){c.error("error while parsing gitGraph options",t.message)}},getOptions:function(){return ia},commit:function(t){var e={id:na(),message:t,seq:ea++,parent:null==Ji?null:Ji.id};Ji=e,Zi[e.id]=e,Ki[Qi]=e.id,c.debug("in pushCommit "+e.id)},branch:function(t){Ki[t]=null!=Ji?Ji.id:null,c.debug("in createBranch")},merge:function(t){var e=Zi[Ki[Qi]],n=Zi[Ki[t]];if(function(t,e){return t.seq>e.seq&&ra(e,t)}(e,n))c.debug("Already merged");else{if(ra(e,n))Ki[Qi]=Ki[t],Ji=Zi[Ki[Qi]];else{var r={id:na(),message:"merged branch "+t+" into "+Qi,seq:ea++,parent:[null==Ji?null:Ji.id,Ki[t]]};Ji=r,Zi[r.id]=r,Ki[Qi]=r.id}c.debug(Ki),c.debug("in mergeBranch")}},checkout:function(t){c.debug("in checkout");var e=Ki[Qi=t];Ji=Zi[e]},reset:function(t){c.debug("in reset",t);var e=t.split(":")[0],n=parseInt(t.split(":")[1]),r="HEAD"===e?Ji:Zi[Ki[e]];for(c.debug(r,n);n>0;)if(n--,!(r=Zi[r.parent])){var i="Critical error - unique parent commit not found during reset";throw c.error(i),i}Ji=r,Ki[Qi]=r.id},prettyPrint:function(){c.debug(Zi),oa([ca()[0]])},clear:function(){Zi={},Ki={master:Ji=null},Qi="master",ea=0},getBranchesAsObjArray:function(){var t=[];for(var e in Ki)t.push({name:e,commit:Zi[Ki[e]]});return t},getBranches:function(){return Ki},getCommits:function(){return Zi},getCommitsArray:ca,getCurrentBranch:function(){return Qi},getDirection:function(){return ta},getHead:function(){return Ji}},la=n(71),ha=n.n(la),fa={},da={nodeSpacing:150,nodeFillColor:"yellow",nodeStrokeWidth:2,nodeStrokeColor:"grey",lineStrokeWidth:4,branchOffset:50,lineColor:"grey",leftMargin:50,branchColors:["#442f74","#983351","#609732","#AA9A39"],nodeRadius:10,nodeLabel:{width:75,height:100,x:-25,y:0}},pa={};function ga(t,e,n,r){var i=D(r,d.curveBasis),a=da.branchColors[n%da.branchColors.length],o=Object(d.line)().x((function(t){return Math.round(t.x)})).y((function(t){return Math.round(t.y)})).curve(i);t.append("svg:path").attr("d",o(e)).style("stroke",a).style("stroke-width",da.lineStrokeWidth).style("fill","none")}function ya(t,e){e=e||t.node().getBBox();var n=t.node().getCTM();return{left:n.e+e.x*n.a,top:n.f+e.y*n.d,width:e.width,height:e.height}}function va(t,e,n,r,i){c.debug("svgDrawLineForCommits: ",e,n);var a=ya(t.select("#node-"+e+" circle")),o=ya(t.select("#node-"+n+" circle"));switch(r){case"LR":if(a.left-o.left>da.nodeSpacing){var s={x:a.left-da.nodeSpacing,y:o.top+o.height/2};ga(t,[s,{x:o.left+o.width,y:o.top+o.height/2}],i,"linear"),ga(t,[{x:a.left,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:s.y},s],i)}else ga(t,[{x:a.left,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:o.top+o.height/2},{x:o.left+o.width,y:o.top+o.height/2}],i);break;case"BT":if(o.top-a.top>da.nodeSpacing){var u={x:o.left+o.width/2,y:a.top+a.height+da.nodeSpacing};ga(t,[u,{x:o.left+o.width/2,y:o.top}],i,"linear"),ga(t,[{x:a.left+a.width/2,y:a.top+a.height},{x:a.left+a.width/2,y:a.top+a.height+da.nodeSpacing/2},{x:o.left+o.width/2,y:u.y-da.nodeSpacing/2},u],i)}else ga(t,[{x:a.left+a.width/2,y:a.top+a.height},{x:a.left+a.width/2,y:a.top+da.nodeSpacing/2},{x:o.left+o.width/2,y:o.top-da.nodeSpacing/2},{x:o.left+o.width/2,y:o.top}],i)}}function ma(t,e){return t.select(e).node().cloneNode(!0)}function ba(t,e,n,r){var i,a=Object.keys(fa).length;if("string"==typeof e)do{if(i=fa[e],c.debug("in renderCommitHistory",i.id,i.seq),t.select("#node-"+e).size()>0)return;t.append((function(){return ma(t,"#def-commit")})).attr("class","commit").attr("id",(function(){return"node-"+i.id})).attr("transform",(function(){switch(r){case"LR":return"translate("+(i.seq*da.nodeSpacing+da.leftMargin)+", "+sa*da.branchOffset+")";case"BT":return"translate("+(sa*da.branchOffset+da.leftMargin)+", "+(a-i.seq)*da.nodeSpacing+")"}})).attr("fill",da.nodeFillColor).attr("stroke",da.nodeStrokeColor).attr("stroke-width",da.nodeStrokeWidth);var o=void 0;for(var s in n)if(n[s].commit===i){o=n[s];break}o&&(c.debug("found branch ",o.name),t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","branch-label").text(o.name+", ")),t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","commit-id").text(i.id),""!==i.message&&"BT"===r&&t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","commit-msg").text(", "+i.message),e=i.parent}while(e&&fa[e]);Array.isArray(e)&&(c.debug("found merge commmit",e),ba(t,e[0],n,r),sa++,ba(t,e[1],n,r),sa--)}function xa(t,e,n,r){for(r=r||0;e.seq>0&&!e.lineDrawn;)"string"==typeof e.parent?(va(t,e.id,e.parent,n,r),e.lineDrawn=!0,e=fa[e.parent]):Array.isArray(e.parent)&&(va(t,e.id,e.parent[0],n,r),va(t,e.id,e.parent[1],n,r+1),xa(t,fa[e.parent[1]],n,r+1),e.lineDrawn=!0,e=fa[e.parent[0]])}var _a,ka=function(t){pa=t},wa=function(t,e,n){try{var r=ha.a.parser;r.yy=ua,r.yy.clear(),c.debug("in gitgraph renderer",t+"\n","id:",e,n),r.parse(t+"\n"),da=Object.assign(da,pa,ua.getOptions()),c.debug("effective options",da);var i=ua.getDirection();fa=ua.getCommits();var a=ua.getBranchesAsObjArray();"BT"===i&&(da.nodeLabel.x=a.length*da.branchOffset,da.nodeLabel.width="100%",da.nodeLabel.y=-2*da.nodeRadius);var o=Object(d.select)('[id="'.concat(e,'"]'));for(var s in function(t){t.append("defs").append("g").attr("id","def-commit").append("circle").attr("r",da.nodeRadius).attr("cx",0).attr("cy",0),t.select("#def-commit").append("foreignObject").attr("width",da.nodeLabel.width).attr("height",da.nodeLabel.height).attr("x",da.nodeLabel.x).attr("y",da.nodeLabel.y).attr("class","node-label").attr("requiredFeatures","http://www.w3.org/TR/SVG11/feature#Extensibility").append("p").html("")}(o),sa=1,a){var u=a[s];ba(o,u.commit.id,a,i),xa(o,u.commit,i),sa++}o.attr("height",(function(){return"BT"===i?Object.keys(fa).length*da.nodeSpacing:(a.length+1)*da.branchOffset}))}catch(t){c.error("Error while rendering gitgraph"),c.error(t.message)}},Ea="",Ta=!1,Ca={setMessage:function(t){c.debug("Setting message to: "+t),Ea=t},getMessage:function(){return Ea},setInfo:function(t){Ta=t},getInfo:function(){return Ta}},Aa=n(72),Sa=n.n(Aa),Ma={},Oa=function(t){Object.keys(t).forEach((function(e){Ma[e]=t[e]}))},Da=function(t,e,n){try{var r=Sa.a.parser;r.yy=Ca,c.debug("Renering info diagram\n"+t),r.parse(t),c.debug("Parsed info diagram");var i=Object(d.select)("#"+e);i.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size","32px").style("text-anchor","middle").text("v "+n),i.attr("height",100),i.attr("width",400)}catch(t){c.error("Error while rendering info diagram"),c.error(t.message)}},Na={},Ba=function(t){Object.keys(t).forEach((function(e){Na[e]=t[e]}))},La=function(t,e){try{c.debug("Renering svg for syntax error\n");var n=Object(d.select)("#"+t),r=n.append("g");r.append("path").attr("class","error-icon").attr("d","m411.313,123.313c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32-9.375,9.375-20.688-20.688c-12.484-12.5-32.766-12.5-45.25,0l-16,16c-1.261,1.261-2.304,2.648-3.31,4.051-21.739-8.561-45.324-13.426-70.065-13.426-105.867,0-192,86.133-192,192s86.133,192 192,192 192-86.133 192-192c0-24.741-4.864-48.327-13.426-70.065 1.402-1.007 2.79-2.049 4.051-3.31l16-16c12.5-12.492 12.5-32.758 0-45.25l-20.688-20.688 9.375-9.375 32.001-31.999zm-219.313,100.687c-52.938,0-96,43.063-96,96 0,8.836-7.164,16-16,16s-16-7.164-16-16c0-70.578 57.422-128 128-128 8.836,0 16,7.164 16,16s-7.164,16-16,16z"),r.append("path").attr("class","error-icon").attr("d","m459.02,148.98c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l16,16c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16.001-16z"),r.append("path").attr("class","error-icon").attr("d","m340.395,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16-16c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l15.999,16z"),r.append("path").attr("class","error-icon").attr("d","m400,64c8.844,0 16-7.164 16-16v-32c0-8.836-7.156-16-16-16-8.844,0-16,7.164-16,16v32c0,8.836 7.156,16 16,16z"),r.append("path").attr("class","error-icon").attr("d","m496,96.586h-32c-8.844,0-16,7.164-16,16 0,8.836 7.156,16 16,16h32c8.844,0 16-7.164 16-16 0-8.836-7.156-16-16-16z"),r.append("path").attr("class","error-icon").attr("d","m436.98,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688l32-32c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32c-6.251,6.25-6.251,16.375-0.001,22.625z"),r.append("text").attr("class","error-text").attr("x",1240).attr("y",250).attr("font-size","150px").style("text-anchor","middle").text("Syntax error in graph"),r.append("text").attr("class","error-text").attr("x",1050).attr("y",400).attr("font-size","100px").style("text-anchor","middle").text("mermaid version "+e),n.attr("height",100),n.attr("width",400),n.attr("viewBox","768 0 512 512")}catch(t){c.error("Error while rendering info diagram"),c.error(t.message)}},Fa={},Pa="",Ia={parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().pie},addSection:function(t,e){void 0===Fa[t]&&(Fa[t]=e,c.debug("Added new section :",t))},getSections:function(){return Fa},cleanupValue:function(t){return":"===t.substring(0,1)?(t=t.substring(1).trim(),Number(t.trim())):Number(t.trim())},clear:function(){Fa={},Pa=""},setTitle:function(t){Pa=t},getTitle:function(){return Pa}},ja=n(73),Ra=n.n(ja),Ya={},za=function(t){Object.keys(t).forEach((function(e){Ya[e]=t[e]}))},Ua=function(t,e){try{var n=Ra.a.parser;n.yy=Ia,c.debug("Rendering info diagram\n"+t),n.yy.clear(),n.parse(t),c.debug("Parsed info diagram");var r=document.getElementById(e);void 0===(_a=r.parentElement.offsetWidth)&&(_a=1200),void 0!==Ya.useWidth&&(_a=Ya.useWidth);var i=Object(d.select)("#"+e);W(i,450,_a,Ya.useMaxWidth),r.setAttribute("viewBox","0 0 "+_a+" 450");var a=Math.min(_a,450)/2-40,o=i.append("g").attr("transform","translate("+_a/2+",225)"),s=Ia.getSections(),u=0;Object.keys(s).forEach((function(t){u+=s[t]}));var l=Object(d.scaleOrdinal)().domain(s).range(d.schemeSet2),h=Object(d.pie)().value((function(t){return t.value}))(Object(d.entries)(s)),f=Object(d.arc)().innerRadius(0).outerRadius(a);o.selectAll("mySlices").data(h).enter().append("path").attr("d",f).attr("fill",(function(t){return l(t.data.key)})).attr("stroke","black").style("stroke-width","2px").style("opacity",.7),o.selectAll("mySlices").data(h).enter().append("text").text((function(t){return(t.data.value/u*100).toFixed(0)+"%"})).attr("transform",(function(t){return"translate("+f.centroid(t)+")"})).style("text-anchor","middle").attr("class","slice").style("font-size",17),o.append("text").text(n.yy.getTitle()).attr("x",0).attr("y",-200).attr("class","pieTitleText");var p=o.selectAll(".legend").data(l.domain()).enter().append("g").attr("class","legend").attr("transform",(function(t,e){return"translate(216,"+(22*e-22*l.domain().length/2)+")"}));p.append("rect").attr("width",18).attr("height",18).style("fill",l).style("stroke",l),p.append("text").attr("x",22).attr("y",14).text((function(t){return t}))}catch(t){c.error("Error while rendering info diagram"),c.error(t)}},$a={},Wa=[],Ha="",Va=function(t){return void 0===$a[t]&&($a[t]={attributes:[]},c.info("Added new entity :",t)),$a[t]},Ga={Cardinality:{ZERO_OR_ONE:"ZERO_OR_ONE",ZERO_OR_MORE:"ZERO_OR_MORE",ONE_OR_MORE:"ONE_OR_MORE",ONLY_ONE:"ONLY_ONE"},Identification:{NON_IDENTIFYING:"NON_IDENTIFYING",IDENTIFYING:"IDENTIFYING"},parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().er},addEntity:Va,addAttributes:function(t,e){var n,r=Va(t);for(n=e.length-1;n>=0;n--)r.attributes.push(e[n]),c.debug("Added attribute ",e[n].attributeName)},getEntities:function(){return $a},addRelationship:function(t,e,n,r){var i={entityA:t,roleA:e,entityB:n,relSpec:r};Wa.push(i),c.debug("Added new relationship :",i)},getRelationships:function(){return Wa},clear:function(){$a={},Wa=[],Ha=""},setTitle:function(t){Ha=t},getTitle:function(){return Ha}},qa=n(74),Xa=n.n(qa),Za={ONLY_ONE_START:"ONLY_ONE_START",ONLY_ONE_END:"ONLY_ONE_END",ZERO_OR_ONE_START:"ZERO_OR_ONE_START",ZERO_OR_ONE_END:"ZERO_OR_ONE_END",ONE_OR_MORE_START:"ONE_OR_MORE_START",ONE_OR_MORE_END:"ONE_OR_MORE_END",ZERO_OR_MORE_START:"ZERO_OR_MORE_START",ZERO_OR_MORE_END:"ZERO_OR_MORE_END"},Ja=Za,Ka=function(t,e){var n;t.append("defs").append("marker").attr("id",Za.ONLY_ONE_START).attr("refX",0).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M9,0 L9,18 M15,0 L15,18"),t.append("defs").append("marker").attr("id",Za.ONLY_ONE_END).attr("refX",18).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M3,0 L3,18 M9,0 L9,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_ONE_START).attr("refX",0).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",21).attr("cy",9).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M9,0 L9,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_ONE_END).attr("refX",30).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",9).attr("cy",9).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M21,0 L21,18"),t.append("defs").append("marker").attr("id",Za.ONE_OR_MORE_START).attr("refX",18).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27"),t.append("defs").append("marker").attr("id",Za.ONE_OR_MORE_END).attr("refX",27).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_MORE_START).attr("refX",18).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",48).attr("cy",18).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M0,18 Q18,0 36,18 Q18,36 0,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_MORE_END).attr("refX",39).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",9).attr("cy",18).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M21,18 Q39,0 57,18 Q39,36 21,18")},Qa={},to=function(t,e,n){var r;return Object.keys(e).forEach((function(i){var a=t.append("g").attr("id",i);r=void 0===r?i:r;var o="entity-"+i,s=a.append("text").attr("class","er entityLabel").attr("id",o).attr("x",0).attr("y",0).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("style","font-family: "+_t().fontFamily+"; font-size: "+Qa.fontSize+"px").text(i),c=function(t,e,n){var r=Qa.entityPadding/3,i=Qa.entityPadding/3,a=.85*Qa.fontSize,o=e.node().getBBox(),s=[],c=0,u=0,l=o.height+2*r,h=1;n.forEach((function(n){var i="".concat(e.node().id,"-attr-").concat(h),o=t.append("text").attr("class","er entityLabel").attr("id","".concat(i,"-type")).attr("x",0).attr("y",0).attr("dominant-baseline","middle").attr("text-anchor","left").attr("style","font-family: "+_t().fontFamily+"; font-size: "+a+"px").text(n.attributeType),f=t.append("text").attr("class","er entityLabel").attr("id","".concat(i,"-name")).attr("x",0).attr("y",0).attr("dominant-baseline","middle").attr("text-anchor","left").attr("style","font-family: "+_t().fontFamily+"; font-size: "+a+"px").text(n.attributeName);s.push({tn:o,nn:f});var d=o.node().getBBox(),p=f.node().getBBox();c=Math.max(c,d.width),u=Math.max(u,p.width),l+=Math.max(d.height,p.height)+2*r,h+=1}));var f={width:Math.max(Qa.minEntityWidth,Math.max(o.width+2*Qa.entityPadding,c+u+4*i)),height:n.length>0?l:Math.max(Qa.minEntityHeight,o.height+2*Qa.entityPadding)},d=Math.max(0,f.width-(c+u)-4*i);if(n.length>0){e.attr("transform","translate("+f.width/2+","+(r+o.height/2)+")");var p=o.height+2*r,g="attributeBoxOdd";s.forEach((function(e){var n=p+r+Math.max(e.tn.node().getBBox().height,e.nn.node().getBBox().height)/2;e.tn.attr("transform","translate("+i+","+n+")");var a=t.insert("rect","#"+e.tn.node().id).attr("class","er ".concat(g)).attr("fill",Qa.fill).attr("fill-opacity","100%").attr("stroke",Qa.stroke).attr("x",0).attr("y",p).attr("width",c+2*i+d/2).attr("height",e.tn.node().getBBox().height+2*r);e.nn.attr("transform","translate("+(parseFloat(a.attr("width"))+i)+","+n+")"),t.insert("rect","#"+e.nn.node().id).attr("class","er ".concat(g)).attr("fill",Qa.fill).attr("fill-opacity","100%").attr("stroke",Qa.stroke).attr("x","".concat(a.attr("x")+a.attr("width"))).attr("y",p).attr("width",u+2*i+d/2).attr("height",e.nn.node().getBBox().height+2*r),p+=Math.max(e.tn.node().getBBox().height,e.nn.node().getBBox().height)+2*r,g="attributeBoxOdd"==g?"attributeBoxEven":"attributeBoxOdd"}))}else f.height=Math.max(Qa.minEntityHeight,l),e.attr("transform","translate("+f.width/2+","+f.height/2+")");return f}(a,s,e[i].attributes),u=c.width,l=c.height,h=a.insert("rect","#"+o).attr("class","er entityBox").attr("fill",Qa.fill).attr("fill-opacity","100%").attr("stroke",Qa.stroke).attr("x",0).attr("y",0).attr("width",u).attr("height",l).node().getBBox();n.setNode(i,{width:h.width,height:h.height,shape:"rect",id:i})})),r},eo=function(t){return(t.entityA+t.roleA+t.entityB).replace(/\s/g,"")},no=0,ro=function(t){for(var e=Object.keys(t),n=0;n/gi," "),r=t.append("text");r.attr("x",e.x),r.attr("y",e.y),r.attr("class","legend"),r.style("text-anchor",e.anchor),void 0!==e.class&&r.attr("class",e.class);var i=r.append("tspan");return i.attr("x",e.x+2*e.textMargin),i.text(n),r},bo=-1,xo=function(){return{x:0,y:0,width:100,anchor:"start",height:100,rx:0,ry:0}},_o=function(){function t(t,e,n,i,a,o,s,c){r(e.append("text").attr("x",n+a/2).attr("y",i+o/2+5).style("font-color",c).style("text-anchor","middle").text(t),s)}function e(t,e,n,i,a,o,s,c,u){for(var l=c.taskFontSize,h=c.taskFontFamily,f=t.split(//gi),d=0;d3?function(t){var e=Object(d.arc)().startAngle(Math.PI/2).endAngle(Math.PI/2*3).innerRadius(7.5).outerRadius(15/2.2);t.append("path").attr("class","mouth").attr("d",e).attr("transform","translate("+o.cx+","+(o.cy+2)+")")}(s):o.score<3?function(t){var e=Object(d.arc)().startAngle(3*Math.PI/2).endAngle(Math.PI/2*5).innerRadius(7.5).outerRadius(15/2.2);t.append("path").attr("class","mouth").attr("d",e).attr("transform","translate("+o.cx+","+(o.cy+7)+")")}(s):function(t){t.append("line").attr("class","mouth").attr("stroke",2).attr("x1",o.cx-5).attr("y1",o.cy+7).attr("x2",o.cx+5).attr("y2",o.cy+7).attr("class","mouth").attr("stroke-width","1px").attr("stroke","#666")}(s);var c=xo();c.x=e.x,c.y=e.y,c.fill=e.fill,c.width=n.width,c.height=n.height,c.class="task task-type-"+e.num,c.rx=3,c.ry=3,yo(i,c);var u=e.x+14;e.people.forEach((function(t){var n=e.actors[t],r={cx:u,cy:e.y,r:7,fill:n,stroke:"#000",title:t};vo(i,r),u+=10})),_o(n)(e.task,i,c.x,c.y,c.width,c.height,{class:"task"},n,e.colour)},Co=function(t){t.append("defs").append("marker").attr("id","arrowhead").attr("refX",5).attr("refY",2).attr("markerWidth",6).attr("markerHeight",4).attr("orient","auto").append("path").attr("d","M 0,0 V 4 L6,2 Z")};ao.parser.yy=go;var Ao={leftMargin:150,diagramMarginX:50,diagramMarginY:20,taskMargin:50,width:150,height:50,taskFontSize:14,taskFontFamily:'"Open-Sans", "sans-serif"',boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",bottomMarginAdj:1,activationWidth:10,textPlacement:"fo",actorColours:["#8FBC8F","#7CFC00","#00FFFF","#20B2AA","#B0E0E6","#FFFFE0"],sectionFills:["#191970","#8B008B","#4B0082","#2F4F4F","#800000","#8B4513","#00008B"],sectionColours:["#fff"]},So={};var Mo=Ao.leftMargin,Oo={data:{startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},verticalPos:0,sequenceItems:[],init:function(){this.sequenceItems=[],this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},this.verticalPos=0},updateVal:function(t,e,n,r){void 0===t[e]?t[e]=n:t[e]=r(n,t[e])},updateBounds:function(t,e,n,r){var i,a=this,o=0;this.sequenceItems.forEach((function(s){o++;var c=a.sequenceItems.length-o+1;a.updateVal(s,"starty",e-c*Ao.boxMargin,Math.min),a.updateVal(s,"stopy",r+c*Ao.boxMargin,Math.max),a.updateVal(Oo.data,"startx",t-c*Ao.boxMargin,Math.min),a.updateVal(Oo.data,"stopx",n+c*Ao.boxMargin,Math.max),"activation"!==i&&(a.updateVal(s,"startx",t-c*Ao.boxMargin,Math.min),a.updateVal(s,"stopx",n+c*Ao.boxMargin,Math.max),a.updateVal(Oo.data,"starty",e-c*Ao.boxMargin,Math.min),a.updateVal(Oo.data,"stopy",r+c*Ao.boxMargin,Math.max))}))},insert:function(t,e,n,r){var i=Math.min(t,n),a=Math.max(t,n),o=Math.min(e,r),s=Math.max(e,r);this.updateVal(Oo.data,"startx",i,Math.min),this.updateVal(Oo.data,"starty",o,Math.min),this.updateVal(Oo.data,"stopx",a,Math.max),this.updateVal(Oo.data,"stopy",s,Math.max),this.updateBounds(i,o,a,s)},bumpVerticalPos:function(t){this.verticalPos=this.verticalPos+t,this.data.stopy=this.verticalPos},getVerticalPos:function(){return this.verticalPos},getBounds:function(){return this.data}},Do=Ao.sectionFills,No=Ao.sectionColours,Bo=function(t,e,n){for(var r="",i=n+(2*Ao.height+Ao.diagramMarginY),a=0,o="#CCC",s="black",c=0,u=0;u tspan {\n fill: ").concat(t.actorTextColor,";\n stroke: none;\n }\n\n .actor-line {\n stroke: ").concat(t.actorLineColor,";\n }\n\n .messageLine0 {\n stroke-width: 1.5;\n stroke-dasharray: none;\n stroke: ").concat(t.signalColor,";\n }\n\n .messageLine1 {\n stroke-width: 1.5;\n stroke-dasharray: 2, 2;\n stroke: ").concat(t.signalColor,";\n }\n\n #arrowhead path {\n fill: ").concat(t.signalColor,";\n stroke: ").concat(t.signalColor,";\n }\n\n .sequenceNumber {\n fill: ").concat(t.sequenceNumberColor,";\n }\n\n #sequencenumber {\n fill: ").concat(t.signalColor,";\n }\n\n #crosshead path {\n fill: ").concat(t.signalColor,";\n stroke: ").concat(t.signalColor,";\n }\n\n .messageText {\n fill: ").concat(t.signalTextColor,";\n stroke: ").concat(t.signalTextColor,";\n }\n\n .labelBox {\n stroke: ").concat(t.labelBoxBorderColor,";\n fill: ").concat(t.labelBoxBkgColor,";\n }\n\n .labelText, .labelText > tspan {\n fill: ").concat(t.labelTextColor,";\n stroke: none;\n }\n\n .loopText, .loopText > tspan {\n fill: ").concat(t.loopTextColor,";\n stroke: none;\n }\n\n .loopLine {\n stroke-width: 2px;\n stroke-dasharray: 2, 2;\n stroke: ").concat(t.labelBoxBorderColor,";\n fill: ").concat(t.labelBoxBorderColor,";\n }\n\n .note {\n //stroke: #decc93;\n stroke: ").concat(t.noteBorderColor,";\n fill: ").concat(t.noteBkgColor,";\n }\n\n .noteText, .noteText > tspan {\n fill: ").concat(t.noteTextColor,";\n stroke: none;\n }\n\n .activation0 {\n fill: ").concat(t.activationBkgColor,";\n stroke: ").concat(t.activationBorderColor,";\n }\n\n .activation1 {\n fill: ").concat(t.activationBkgColor,";\n stroke: ").concat(t.activationBorderColor,";\n }\n\n .activation2 {\n fill: ").concat(t.activationBkgColor,";\n stroke: ").concat(t.activationBorderColor,";\n }\n")},gantt:function(t){return'\n .mermaid-main-font {\n font-family: "trebuchet ms", verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n\n .section {\n stroke: none;\n opacity: 0.2;\n }\n\n .section0 {\n fill: '.concat(t.sectionBkgColor,";\n }\n\n .section2 {\n fill: ").concat(t.sectionBkgColor2,";\n }\n\n .section1,\n .section3 {\n fill: ").concat(t.altSectionBkgColor,";\n opacity: 0.2;\n }\n\n .sectionTitle0 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle1 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle2 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle3 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle {\n text-anchor: start;\n font-size: 11px;\n text-height: 14px;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n\n }\n\n\n /* Grid and axis */\n\n .grid .tick {\n stroke: ").concat(t.gridColor,";\n opacity: 0.8;\n shape-rendering: crispEdges;\n text {\n font-family: ").concat(t.fontFamily,";\n fill: ").concat(t.textColor,";\n }\n }\n\n .grid path {\n stroke-width: 0;\n }\n\n\n /* Today line */\n\n .today {\n fill: none;\n stroke: ").concat(t.todayLineColor,";\n stroke-width: 2px;\n }\n\n\n /* Task styling */\n\n /* Default task */\n\n .task {\n stroke-width: 2;\n }\n\n .taskText {\n text-anchor: middle;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n\n .taskText:not([font-size]) {\n font-size: 11px;\n }\n\n .taskTextOutsideRight {\n fill: ").concat(t.taskTextDarkColor,";\n text-anchor: start;\n font-size: 11px;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n\n }\n\n .taskTextOutsideLeft {\n fill: ").concat(t.taskTextDarkColor,";\n text-anchor: end;\n font-size: 11px;\n }\n\n /* Special case clickable */\n .task.clickable {\n cursor: pointer;\n }\n .taskText.clickable {\n cursor: pointer;\n fill: ").concat(t.taskTextClickableColor," !important;\n font-weight: bold;\n }\n\n .taskTextOutsideLeft.clickable {\n cursor: pointer;\n fill: ").concat(t.taskTextClickableColor," !important;\n font-weight: bold;\n }\n\n .taskTextOutsideRight.clickable {\n cursor: pointer;\n fill: ").concat(t.taskTextClickableColor," !important;\n font-weight: bold;\n }\n\n /* Specific task settings for the sections*/\n\n .taskText0,\n .taskText1,\n .taskText2,\n .taskText3 {\n fill: ").concat(t.taskTextColor,";\n }\n\n .task0,\n .task1,\n .task2,\n .task3 {\n fill: ").concat(t.taskBkgColor,";\n stroke: ").concat(t.taskBorderColor,";\n }\n\n .taskTextOutside0,\n .taskTextOutside2\n {\n fill: ").concat(t.taskTextOutsideColor,";\n }\n\n .taskTextOutside1,\n .taskTextOutside3 {\n fill: ").concat(t.taskTextOutsideColor,";\n }\n\n\n /* Active task */\n\n .active0,\n .active1,\n .active2,\n .active3 {\n fill: ").concat(t.activeTaskBkgColor,";\n stroke: ").concat(t.activeTaskBorderColor,";\n }\n\n .activeText0,\n .activeText1,\n .activeText2,\n .activeText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n\n /* Completed task */\n\n .done0,\n .done1,\n .done2,\n .done3 {\n stroke: ").concat(t.doneTaskBorderColor,";\n fill: ").concat(t.doneTaskBkgColor,";\n stroke-width: 2;\n }\n\n .doneText0,\n .doneText1,\n .doneText2,\n .doneText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n\n /* Tasks on the critical line */\n\n .crit0,\n .crit1,\n .crit2,\n .crit3 {\n stroke: ").concat(t.critBorderColor,";\n fill: ").concat(t.critBkgColor,";\n stroke-width: 2;\n }\n\n .activeCrit0,\n .activeCrit1,\n .activeCrit2,\n .activeCrit3 {\n stroke: ").concat(t.critBorderColor,";\n fill: ").concat(t.activeTaskBkgColor,";\n stroke-width: 2;\n }\n\n .doneCrit0,\n .doneCrit1,\n .doneCrit2,\n .doneCrit3 {\n stroke: ").concat(t.critBorderColor,";\n fill: ").concat(t.doneTaskBkgColor,";\n stroke-width: 2;\n cursor: pointer;\n shape-rendering: crispEdges;\n }\n\n .milestone {\n transform: rotate(45deg) scale(0.8,0.8);\n }\n\n .milestoneText {\n font-style: italic;\n }\n .doneCritText0,\n .doneCritText1,\n .doneCritText2,\n .doneCritText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n .activeCritText0,\n .activeCritText1,\n .activeCritText2,\n .activeCritText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n .titleText {\n text-anchor: middle;\n font-size: 18px;\n fill: ").concat(t.textColor," ;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n")},classDiagram:Po,"classDiagram-v2":Po,class:Po,stateDiagram:jo,state:jo,git:function(){return"\n .commit-id,\n .commit-msg,\n .branch-label {\n fill: lightgrey;\n color: lightgrey;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n"},info:function(){return""},pie:function(t){return".pieTitleText {\n text-anchor: middle;\n font-size: 25px;\n fill: ".concat(t.taskTextDarkColor,";\n font-family: ").concat(t.fontFamily,";\n }\n .slice {\n font-family: ").concat(t.fontFamily,";\n fill: ").concat(t.textColor,";\n // fill: white;\n }\n .legend text {\n fill: ").concat(t.taskTextDarkColor,";\n font-family: ").concat(t.fontFamily,";\n font-size: 17px;\n }\n")},er:function(t){return"\n .entityBox {\n fill: ".concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n }\n\n .attributeBoxOdd {\n fill: #ffffff;\n stroke: ").concat(t.nodeBorder,";\n }\n\n .attributeBoxEven {\n fill: #f2f2f2;\n stroke: ").concat(t.nodeBorder,";\n }\n\n .relationshipLabelBox {\n fill: ").concat(t.tertiaryColor,";\n opacity: 0.7;\n background-color: ").concat(t.tertiaryColor,";\n rect {\n opacity: 0.5;\n }\n }\n\n .relationshipLine {\n stroke: ").concat(t.lineColor,";\n }\n")},journey:function(t){return".label {\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n color: ".concat(t.textColor,";\n }\n .mouth {\n stroke: #666;\n }\n\n line {\n stroke: ").concat(t.textColor,"\n }\n\n .legend {\n fill: ").concat(t.textColor,";\n }\n\n .label text {\n fill: #333;\n }\n .label {\n color: ").concat(t.textColor,"\n }\n\n .face {\n fill: #FFF8DC;\n stroke: #999;\n }\n\n .node rect,\n .node circle,\n .node ellipse,\n .node polygon,\n .node path {\n fill: ").concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n stroke-width: 1px;\n }\n\n .node .label {\n text-align: center;\n }\n .node.clickable {\n cursor: pointer;\n }\n\n .arrowheadPath {\n fill: ").concat(t.arrowheadColor,";\n }\n\n .edgePath .path {\n stroke: ").concat(t.lineColor,";\n stroke-width: 1.5px;\n }\n\n .flowchart-link {\n stroke: ").concat(t.lineColor,";\n fill: none;\n }\n\n .edgeLabel {\n background-color: ").concat(t.edgeLabelBackground,";\n rect {\n opacity: 0.5;\n }\n text-align: center;\n }\n\n .cluster rect {\n }\n\n .cluster text {\n fill: ").concat(t.titleColor,";\n }\n\n div.mermaidTooltip {\n position: absolute;\n text-align: center;\n max-width: 200px;\n padding: 2px;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n font-size: 12px;\n background: ").concat(t.tertiaryColor,";\n border: 1px solid ").concat(t.border2,";\n border-radius: 2px;\n pointer-events: none;\n z-index: 100;\n }\n\n .task-type-0, .section-type-0 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType0):"",";\n }\n .task-type-1, .section-type-1 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType1):"",";\n }\n .task-type-2, .section-type-2 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType2):"",";\n }\n .task-type-3, .section-type-3 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType3):"",";\n }\n .task-type-4, .section-type-4 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType4):"",";\n }\n .task-type-5, .section-type-5 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType5):"",";\n }\n .task-type-6, .section-type-6 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType6):"",";\n }\n .task-type-7, .section-type-7 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType7):"",";\n }\n")}},Yo=function(t,e,n){return" {\n font-family: ".concat(n.fontFamily,";\n font-size: ").concat(n.fontSize,";\n fill: ").concat(n.textColor,"\n }\n\n /* Classes common for multiple diagrams */\n\n .error-icon {\n fill: ").concat(n.errorBkgColor,";\n }\n .error-text {\n fill: ").concat(n.errorTextColor,";\n stroke: ").concat(n.errorTextColor,";\n }\n\n .edge-thickness-normal {\n stroke-width: 2px;\n }\n .edge-thickness-thick {\n stroke-width: 3.5px\n }\n .edge-pattern-solid {\n stroke-dasharray: 0;\n }\n\n .edge-pattern-dashed{\n stroke-dasharray: 3;\n }\n .edge-pattern-dotted {\n stroke-dasharray: 2;\n }\n\n .marker {\n fill: ").concat(n.lineColor,";\n }\n .marker.cross {\n stroke: ").concat(n.lineColor,";\n }\n\n svg {\n font-family: ").concat(n.fontFamily,";\n font-size: ").concat(n.fontSize,";\n }\n\n ").concat(Ro[t](n),"\n\n ").concat(e,"\n\n ").concat(t," { fill: apa;}\n")};function zo(t){return(zo="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var Uo={},$o=function(t,e,n){switch(c.debug("Directive type=".concat(e.type," with args:"),e.args),e.type){case"init":case"initialize":["config"].forEach((function(t){void 0!==e.args[t]&&("flowchart-v2"===n&&(n="flowchart"),e.args[n]=e.args[t],delete e.args[t])})),e.args,wt(e.args);break;case"wrap":case"nowrap":t&&t.setWrap&&t.setWrap("wrap"===e.type);break;default:c.warn("Unhandled directive: source: '%%{".concat(e.type,": ").concat(JSON.stringify(e.args?e.args:{}),"}%%"),e)}};function Wo(t){ka(t.git),me(t.flowchart),Ln(t.flowchart),void 0!==t.sequenceDiagram&&_r.setConf(I(t.sequence,t.sequenceDiagram)),_r.setConf(t.sequence),ri(t.gantt),li(t.class),zi(t.state),qi(t.state),Oa(t.class),za(t.class),ro(t.er),Lo(t.journey),Ba(t.class)}function Ho(){}var Vo=Object.freeze({render:function(t,e,n,r){Et();var i=e,a=H.detectInit(i);a&&wt(a);var o=_t();if(e.length>o.maxTextSize&&(i="graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa"),void 0!==r)r.innerHTML="",Object(d.select)(r).append("div").attr("id","d"+t).attr("style","font-family: "+o.fontFamily).append("svg").attr("id",t).attr("width","100%").attr("xmlns","http://www.w3.org/2000/svg").append("g");else{var s=document.getElementById(t);s&&s.remove();var u=document.querySelector("#d"+t);u&&u.remove(),Object(d.select)("body").append("div").attr("id","d"+t).append("svg").attr("id",t).attr("width","100%").attr("xmlns","http://www.w3.org/2000/svg").append("g")}window.txt=i,i=function(t){var e=t;return e=(e=(e=e.replace(/style.*:\S*#.*;/g,(function(t){return t.substring(0,t.length-1)}))).replace(/classDef.*:\S*#.*;/g,(function(t){return t.substring(0,t.length-1)}))).replace(/#\w+;/g,(function(t){var e=t.substring(1,t.length-1);return/^\+?\d+$/.test(e)?"fl°°"+e+"¶ß":"fl°"+e+"¶ß"}))}(i);var l=Object(d.select)("#d"+t).node(),h=H.detectType(i),g=l.firstChild,y=g.firstChild,v="";if(void 0!==o.themeCSS&&(v+="\n".concat(o.themeCSS)),void 0!==o.fontFamily&&(v+="\n:root { --mermaid-font-family: ".concat(o.fontFamily,"}")),void 0!==o.altFontFamily&&(v+="\n:root { --mermaid-alt-font-family: ".concat(o.altFontFamily,"}")),"flowchart"===h||"flowchart-v2"===h||"graph"===h){var m=be(i);for(var b in m)v+="\n.".concat(b," > * { ").concat(m[b].styles.join(" !important; ")," !important; }"),m[b].textStyles&&(v+="\n.".concat(b," tspan { ").concat(m[b].textStyles.join(" !important; ")," !important; }"))}var x=(new f.a)("#".concat(t),Yo(h,v,o.themeVariables)),_=document.createElement("style");_.innerHTML=x,g.insertBefore(_,y);try{switch(h){case"git":o.flowchart.arrowMarkerAbsolute=o.arrowMarkerAbsolute,ka(o.git),wa(i,t,!1);break;case"flowchart":o.flowchart.arrowMarkerAbsolute=o.arrowMarkerAbsolute,me(o.flowchart),xe(i,t,!1);break;case"flowchart-v2":o.flowchart.arrowMarkerAbsolute=o.arrowMarkerAbsolute,Ln(o.flowchart),Fn(i,t,!1);break;case"sequence":o.sequence.arrowMarkerAbsolute=o.arrowMarkerAbsolute,o.sequenceDiagram?(_r.setConf(Object.assign(o.sequence,o.sequenceDiagram)),console.error("`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.")):_r.setConf(o.sequence),_r.draw(i,t);break;case"gantt":o.gantt.arrowMarkerAbsolute=o.arrowMarkerAbsolute,ri(o.gantt),ii(i,t);break;case"class":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,li(o.class),hi(i,t);break;case"classDiagram":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,di(o.class),pi(i,t);break;case"state":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,zi(o.state),Ui(i,t);break;case"stateDiagram":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,qi(o.state),Xi(i,t);break;case"info":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,Oa(o.class),Da(i,t,p.version);break;case"pie":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,za(o.pie),Ua(i,t,p.version);break;case"er":ro(o.er),io(i,t,p.version);break;case"journey":Lo(o.journey),Fo(i,t,p.version)}}catch(e){throw La(t,p.version),e}Object(d.select)('[id="'.concat(t,'"]')).selectAll("foreignobject > *").attr("xmlns","http://www.w3.org/1999/xhtml");var k=Object(d.select)("#d"+t).node().innerHTML;if(c.debug("cnf.arrowMarkerAbsolute",o.arrowMarkerAbsolute),o.arrowMarkerAbsolute&&"false"!==o.arrowMarkerAbsolute||(k=k.replace(/marker-end="url\(.*?#/g,'marker-end="url(#',"g")),k=function(t){var e=t;return e=(e=(e=e.replace(/fl°°/g,(function(){return"&#"}))).replace(/fl°/g,(function(){return"&"}))).replace(/¶ß/g,(function(){return";"}))}(k),void 0!==n)switch(h){case"flowchart":case"flowchart-v2":n(k,Xt.bindFunctions);break;case"gantt":n(k,Qr.bindFunctions);break;case"class":case"classDiagram":n(k,cn.bindFunctions);break;default:n(k)}else c.debug("CB = undefined!");var w=Object(d.select)("#d"+t).node();return null!==w&&"function"==typeof w.remove&&Object(d.select)("#d"+t).node().remove(),k},parse:function(t){var e=H.detectInit(t);e&&c.debug("reinit ",e);var n,r=H.detectType(t);switch(c.debug("Type "+r),r){case"git":(n=ha.a).parser.yy=ua;break;case"flowchart":case"flowchart-v2":Xt.clear(),(n=Jt.a).parser.yy=Xt;break;case"sequence":(n=Hn.a).parser.yy=sr;break;case"gantt":(n=wr.a).parser.yy=Qr;break;case"class":case"classDiagram":(n=oi.a).parser.yy=cn;break;case"state":case"stateDiagram":(n=Di.a).parser.yy=Mi;break;case"info":c.debug("info info info"),(n=Sa.a).parser.yy=Ca;break;case"pie":c.debug("pie"),(n=Ra.a).parser.yy=Ia;break;case"er":c.debug("er"),(n=Xa.a).parser.yy=Ga;break;case"journey":c.debug("Journey"),(n=oo.a).parser.yy=go}return n.parser.yy.graphType=r,n.parser.yy.parseError=function(t,e){throw{str:t,hash:e}},n.parse(t),n},parseDirective:function(t,e,n,r){try{if(void 0!==e)switch(e=e.trim(),n){case"open_directive":Uo={};break;case"type_directive":Uo.type=e.toLowerCase();break;case"arg_directive":Uo.args=JSON.parse(e);break;case"close_directive":$o(t,Uo,r),Uo=null}}catch(t){c.error("Error while rendering sequenceDiagram directive: ".concat(e," jison context: ").concat(n)),c.error(t.message)}},initialize:function(t){t&&t.fontFamily&&(t.themeVariables&&t.themeVariables.fontFamily||(t.themeVariables={fontFamily:t.fontFamily})),dt=I({},t),t&&t.theme&&ht[t.theme]?t.themeVariables=ht[t.theme].getThemeVariables(t.themeVariables):t&&(t.themeVariables=ht.default.getThemeVariables(t.themeVariables));var e="object"===zo(t)?function(t){return yt=I({},gt),yt=I(yt,t),t.theme&&(yt.themeVariables=ht[t.theme].getThemeVariables(t.themeVariables)),mt=bt(yt,vt),yt}(t):xt();Wo(e),u(e.logLevel)},reinitialize:Ho,getConfig:_t,setConfig:function(t){return I(mt,t),_t()},getSiteConfig:xt,updateSiteConfig:function(t){return yt=I(yt,t),bt(yt,vt),yt},reset:function(){Et()},globalReset:function(){Et(),Wo(_t())},defaultConfig:gt});u(_t().logLevel),Et(_t());var Go=Vo,qo=function(){Xo.startOnLoad?Go.getConfig().startOnLoad&&Xo.init():void 0===Xo.startOnLoad&&(c.debug("In start, no config"),Go.getConfig().startOnLoad&&Xo.init())};"undefined"!=typeof document&& +/*! + * Wait for document loaded before starting the execution + */ +window.addEventListener("load",(function(){qo()}),!1);var Xo={startOnLoad:!0,htmlLabels:!0,mermaidAPI:Go,parse:Go.parse,render:Go.render,init:function(){var t,e,n=this,r=Go.getConfig();arguments.length>=2?( +/*! sequence config was passed as #1 */ +void 0!==arguments[0]&&(Xo.sequenceConfig=arguments[0]),t=arguments[1]):t=arguments[0],"function"==typeof arguments[arguments.length-1]?(e=arguments[arguments.length-1],c.debug("Callback function found")):void 0!==r.mermaid&&("function"==typeof r.mermaid.callback?(e=r.mermaid.callback,c.debug("Callback function found")):c.debug("No Callback function found")),t=void 0===t?document.querySelectorAll(".mermaid"):"string"==typeof t?document.querySelectorAll(t):t instanceof window.Node?[t]:t,c.debug("Start On Load before: "+Xo.startOnLoad),void 0!==Xo.startOnLoad&&(c.debug("Start On Load inner: "+Xo.startOnLoad),Go.updateSiteConfig({startOnLoad:Xo.startOnLoad})),void 0!==Xo.ganttConfig&&Go.updateSiteConfig({gantt:Xo.ganttConfig});for(var a,o=H.initIdGeneratior(r.deterministicIds,r.deterministicIDSeed).next,s=function(r){var s=t[r]; +/*! Check if previously processed */if(s.getAttribute("data-processed"))return"continue";s.setAttribute("data-processed",!0);var u="mermaid-".concat(o());a=i(a=s.innerHTML).trim().replace(//gi,"
");var l=H.detectInit(a);l&&c.debug("Detected early reinit: ",l);try{Go.render(u,a,(function(t,n){s.innerHTML=t,void 0!==e&&e(u),n&&n(s)}),s)}catch(t){c.warn("Syntax Error rendering"),c.warn(t),n.parseError&&n.parseError(t)}},u=0;u, cx: &mut Context<'_>) -> Poll>; - + // Optional optimization hint, just like with iterators: #[inline] fn size_hint(&self) -> (usize, Option) { @@ -121,7 +121,7 @@ where impl Stream for AssertUnwindSafe where - S: Stream, + S: Stream, { type Item = ::Item; } @@ -131,13 +131,13 @@ where We should also implement a next method, similar to [the implementation in the futures-util crate](https://docs.rs/futures-util/0.3.5/src/futures_util/stream/stream/next.rs.html#10-12). -In general, we have purposefully kept the core trait definition minimal. -There are a number of useful extension methods that are available, for example, -in the futures-stream crate, but we have not included them because they involve +In general, we have purposefully kept the core trait definition minimal. +There are a number of useful extension methods that are available, for example, +in the futures-stream crate, but we have not included them because they involve closure arguments, and we have not yet finalized the design of async closures. -However, the core methods alone are extremely unergonomic. You can't even iterate -over the items coming out of the stream. Therefore, we include a few minimal +However, the core methods alone are extremely unergonomic. You can't even iterate +over the items coming out of the stream. Therefore, we include a few minimal convenience methods that are not dependent on any unstable features. Most notably, next ```rust @@ -190,20 +190,20 @@ while let Some(x) = s.try_next().await? ``` Adding the `try_next` method is out of the scope of this RFC to keep us focused -on adding the critical methods needed for async streams first, then adding in +on adding the critical methods needed for async streams first, then adding in additional ones at a later date. One thing to note, if a user is using an older version of `futures-util`, they would experience ambiguity when trying to use the `next` method that is added to the standard library (and redirected to from `futures-core`). -This can be done as a non-breaking change, but would require everyone to +This can be done as a non-breaking change, but would require everyone to upgrade rustc. We will want to create a transition plan on what this means for users and pick the timing carefully. ### Why does next require Self:Unpin? -When drafting this RFC, there was a [good deal of discussion](https://github.com/rust-lang/wg-async-foundations/pull/15#discussion_r452482084) around why the `next` method requires `Self:Unpin`. +When drafting this RFC, there was a [good deal of discussion](https://github.com/rust-lang/wg-async/pull/15#discussion_r452482084) around why the `next` method requires `Self:Unpin`. To understand this, it helps to take a closer look at the definition of `Next` (this struct is further discussed later in this RFC) in the [futures-util crate](https://docs.rs/futures-util/0.3.5/src/futures_util/stream/stream/next.rs.html#10-12). @@ -240,15 +240,15 @@ that defines an async `next` method: ```rust trait Stream { type Item; - + async fn next(&mut self) -> Option; } ``` Unfortunately, async methods in traits are not currently supported, and there [are a number of challenges to be -resolved](https://rust-lang.github.io/wg-async-foundations/design_docs/async_fn_in_traits.html) -before they can be added. +resolved](https://rust-lang.github.io/wg-async/design_docs/async_fn_fundamentals.html) +before they can be added. Moreover, it is not clear yet how to make traits that contain async functions be `dyn` safe, and it is imporant to be able to pass around `dyn @@ -268,7 +268,7 @@ Why should we *not* do this? ## Where should stream live? -As mentioned above, `core::stream` is analogous to `core::future`. But, do we want to find +As mentioned above, `core::stream` is analogous to `core::future`. But, do we want to find some other naming scheme that can scale up to other future additions, such as io traits or channels? # Prior art @@ -309,7 +309,7 @@ design issues] to be resolved before they are added. Therefore, we've decided to enable progress on the stream trait by stabilizing a core, and to come back to the problem of extending it with combinators. -[outstanding design issues]: https://rust-lang.github.io/wg-async-foundations/design_docs/async_closures.html +[outstanding design issues]: https://rust-lang.github.io/wg-async/design_docs/async_closures.html This path does carry some risk. Adding combinator methods can cause existing code to stop compiling due to the ambiguities in method @@ -334,7 +334,7 @@ Iterators have an `IntoIterator` that is used with `for` loops to convert items ```rust pub trait IntoIterator where - ::Item == Self::Item, + ::Item == Self::Item, { type Item; @@ -386,7 +386,7 @@ This trait could look like this: ```rust pub trait IntoStream -where +where ::Item == Self::Item, { type Item; @@ -397,7 +397,7 @@ where } ``` -This trait (as expressed by @taiki-e in [a comment on a draft of this RFC](https://github.com/rust-lang/wg-async-foundations/pull/15/files#r449880986)) makes it easy to write streams in combination with [async stream](https://github.com/taiki-e/futures-async-stream). For example: +This trait (as expressed by @taiki-e in [a comment on a draft of this RFC](https://github.com/rust-lang/wg-async/pull/15/files#r449880986)) makes it easy to write streams in combination with [async stream](https://github.com/taiki-e/futures-async-stream). For example: ```rust type S(usize); @@ -414,7 +414,7 @@ impl IntoStream for S { } } } -} +} ``` ### FromStream @@ -480,9 +480,9 @@ pub trait Stream { } ``` -When drafting this RFC, there was [discussion](https://github.com/rust-lang/wg-async-foundations/pull/15#discussion_r451182595) +When drafting this RFC, there was [discussion](https://github.com/rust-lang/wg-async/pull/15#discussion_r451182595) about whether to implement from_stream for all T where `T: FromIterator` as well. -`FromStream` is perhaps more general than `FromIterator` because the await point is allowed to suspend execution of the +`FromStream` is perhaps more general than `FromIterator` because the await point is allowed to suspend execution of the current function, but doesn't have too. Therefore, many (if not all) existing impls of `FromIterator` would work for `FromStream` as well. While this would be a good point for a future discussion, it is not in the scope of this RFC. @@ -509,7 +509,7 @@ As detailed in previous sections, the migrations to add these traits are out of Currently, if someone wishes to iterate over a `Stream` as defined in the `futures` crate, they are not able to use `for` loops, they must use `while let` and `next/try_next` instead. -We may wish to extend the `for` loop so that it works over streams as well. +We may wish to extend the `for` loop so that it works over streams as well. ```rust #[async] @@ -518,12 +518,12 @@ for elem in stream { ... } One of the complications of using `while let` syntax is the need to pin. A `for` loop syntax that takes ownership of the stream would be able to -do the pinning for you. +do the pinning for you. On the other hand, we may not want to make sequential processing "too easy" without also enabling parallel/concurrent processing, which people frequently want. One challenge is that parallel processing wouldn't naively permit early returns and other complex -control flow. We could add a `par_stream()` method, similar to +control flow. We could add a `par_stream()` method, similar to [Rayon's](https://github.com/rayon-rs/rayon) `par_iter()`. Designing this extension is out of scope for this RFC. However, it could be prototyped using procedural macros today. @@ -537,24 +537,24 @@ There has been much discussion around lending streams (also referred to as attac [Source](https://smallcultfollowing.com/babysteps/blog/2019/12/10/async-interview-2-cramertj-part-2/#the-need-for-streaming-streams-and-iterators) -In a **lending** stream (also known as an "attached" stream), the `Item` that gets -returned by `Stream` may be borrowed from `self`. It can only be used as long as +In a **lending** stream (also known as an "attached" stream), the `Item` that gets +returned by `Stream` may be borrowed from `self`. It can only be used as long as the `self` reference remains live. -In a **non-lending** stream (also known as a "detached" stream), the `Item` that -gets returned by `Stream` is "detached" from self. This means it can be stored +In a **non-lending** stream (also known as a "detached" stream), the `Item` that +gets returned by `Stream` is "detached" from self. This means it can be stored and moved about independently from `self`. -This RFC does not cover the addition of lending streams (streams as implemented through +This RFC does not cover the addition of lending streams (streams as implemented through this RFC are all non-lending streams). We can add the `Stream` trait to the standard library now and delay adding in this distinction between the two types of streams - lending and non-lending. The advantage of this is it would allow us to copy the `Stream` -trait from `futures` largely 'as is'. +trait from `futures` largely 'as is'. -The disadvantage of this is functions that consume streams would -first be written to work with `Stream`, and then potentially have +The disadvantage of this is functions that consume streams would +first be written to work with `Stream`, and then potentially have to be rewritten later to work with `LendingStream`s. ### Current Stream Trait @@ -572,7 +572,7 @@ pub trait Stream { } ``` -This trait, like `Iterator`, always gives ownership of each item back to its caller. This offers flexibility - +This trait, like `Iterator`, always gives ownership of each item back to its caller. This offers flexibility - such as the ability to spawn off futures processing each item in parallel. ### Potential Lending Stream Trait @@ -583,7 +583,7 @@ where S: Stream, { type Item<'_> = S::Item; - + fn poll_next<'s>( self: Pin<&'s mut Self>, cx: &mut Context<'_>, @@ -593,23 +593,23 @@ where } ``` -This is a "conversion" trait such that anything which implements `Stream` can also implement +This is a "conversion" trait such that anything which implements `Stream` can also implement `Lending Stream`. -This trait captures the case we re-use internal buffers. This would be less flexible for -consumers, but potentially more efficient. Types could implement the `LendingStream` +This trait captures the case we re-use internal buffers. This would be less flexible for +consumers, but potentially more efficient. Types could implement the `LendingStream` where they need to re-use an internal buffer and `Stream` if they do not. There is room for both. We would also need to pursue the same design for iterators - whether through adding two traits or one new trait with a "conversion" from the old trait. This also brings up the question of whether we should allow conversion in the opposite way - if -every non-lending stream can become a lending one, should _some_ lending streams be able to -become non-lending ones? +every non-lending stream can become a lending one, should _some_ lending streams be able to +become non-lending ones? **Coherence** -The impl above has a problem. As the Rust language stands today, we cannot cleanly convert +The impl above has a problem. As the Rust language stands today, we cannot cleanly convert impl Stream to impl LendingStream due to a coherence conflict. If you have other impls like: @@ -624,7 +624,7 @@ and impl LendingStream for Box where T: LendingStream ``` -There is a coherence conflict for `Box`, so presumably it will fail the coherence rules. +There is a coherence conflict for `Box`, so presumably it will fail the coherence rules. [More examples are available here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a667a7560f8dc97ab82a780e27dfc9eb). @@ -640,16 +640,16 @@ trait Stream: LendingStream This would allow us to leverage `default impl`. -These use cases for lending/non-lending streams need more thought, which is part of the reason it +These use cases for lending/non-lending streams need more thought, which is part of the reason it is out of the scope of this particular RFC. ## Generator syntax [generator syntax]: #generator-syntax -In the future, we may wish to introduce a new form of function - +In the future, we may wish to introduce a new form of function - `gen fn` in iterators and `async gen` in async code that can contain `yield` statements. Calling such a function would -yield a `impl Iterator` or `impl Stream`, for sync and async +yield a `impl Iterator` or `impl Stream`, for sync and async respectively. Given an "attached" or "borrowed" stream, the generator yield could return references to local variables. Given a "detached" or "owned" stream, the generator yield could return things @@ -681,13 +681,13 @@ After desugaring would result in a function like: fn foo() -> impl Stream ``` -If we introduce `-> Stream` first, we will have to permit `LendingStream` in the future. +If we introduce `-> Stream` first, we will have to permit `LendingStream` in the future. Additionally, if we introduce `LendingStream` later, we'll have to figure out how to convert a `LendingStream` into a `Stream` seamlessly. ### Differences between Iterator generators and Async generators -We want `Stream` and `Iterator` to work as analogously as possible, including when used with generators. However, in the current design, there is a crucial difference between the two. +We want `Stream` and `Iterator` to work as analogously as possible, including when used with generators. However, in the current design, there is a crucial difference between the two. Consider Iterator's core `next` method: @@ -710,7 +710,7 @@ pub trait Stream { } ``` -Iterator does not require pinning its core next method. In order for a `gen fn` to operate with the Iterator ecosystem, there must be some kind of initial pinning step that converts its result into an iterator. This will be tricky, since you can't return a pinned value except by boxing. +Iterator does not require pinning its core next method. In order for a `gen fn` to operate with the Iterator ecosystem, there must be some kind of initial pinning step that converts its result into an iterator. This will be tricky, since you can't return a pinned value except by boxing. The general shape will be: @@ -720,7 +720,7 @@ gen_fn().pin_somehow().adapter1().adapter2() With streams, the core interface _is_ pinned, so pinning occurs at the last moment. -The general shape would be +The general shape would be ```rust async_gen_fn().adapter1().adapter2().pin_somehow() @@ -728,7 +728,7 @@ async_gen_fn().adapter1().adapter2().pin_somehow() Pinning at the end, like with a stream, lets you build and return those adapters and then apply pinning at the end. This may be the more efficient setup and implies that, in order to have a `gen fn` that produces iterators, we will need to potentially disallow borrowing yields or implement some kind of `PinnedIterator` trait that can be "adapted" into an iterator by pinning. -For example: +For example: ```rust trait PinIterator { @@ -741,7 +741,7 @@ impl + DerefMut> Iterator for Pin

{ fn next(&mut self) -> Self::Item { self.as_mut().next() } } -// this would be nice.. but would lead to name resolution ambiguity for our combinators 😬 +// this would be nice.. but would lead to name resolution ambiguity for our combinators 😬 default impl PinIterator for T { .. } ``` Pinning also applies to the design of AsyncRead/AsyncWrite, which currently uses Pin even through there is no clear plan to make them implemented with generator type syntax. The asyncification of a signature is currently understood as pinned receiver + context arg + return poll. @@ -751,9 +751,9 @@ Pinning also applies to the design of AsyncRead/AsyncWrite, which currently uses It would be useful to be able to yield from inside a for loop, as long as the for loop is over a borrowed input and not something owned by the stack frame. -In the spirit of experimentation, boats has written the [propane](https://github.com/withoutboats/propane) +In the spirit of experimentation, boats has written the [propane](https://github.com/withoutboats/propane) crate. This crate includes a `#[propane] fn` that changes the function signature -to return `impl Iterator` and lets you `yield`. The non-async version uses +to return `impl Iterator` and lets you `yield`. The non-async version uses `static generator` that is currently in nightly only. Further designing generator functions is out of the scope of this RFC. diff --git a/src/CHARTER.md b/src/CHARTER.md new file mode 100644 index 00000000..c9be0c90 --- /dev/null +++ b/src/CHARTER.md @@ -0,0 +1,52 @@ +# wg-async charter + + +## Goals + + + +This working group is focused around implementation/design of the “foundations” for Async I/O. This means that we are focused on designing and implementing extensions to the language, standard library, and other "core" bits of support offered by the Rust organization. + +## Constraints And Considerations + +We do not directly work on external projects like [tokio], [async-std], [smol], [embassy] and so forth, although we definitely discuss ideas and coordinate with them where appropriate. + +[tokio]: https://tokio.rs/ +[async-std]: https://async.rs/ +[smol]: https://github.com/smol-rs/smol/ +[embassy]: https://github.com/akiles/embassy + + + + +## Membership + +Leads: [@tmandry] and [@nikomatsakis] + +[Members] + +[@tmandry]: https://github.com/tmandry +[@nikomatsakis]: https://github.com/nikomatsakis +[Members]: https://www.rust-lang.org/governance/teams/lang#team-wg-async + +### Membership requirements + +Members are invited at the discretion of the working group leads, usually after a period of sustained contribution to the working group. + +In order to remain an active member, WG members must have active participation in last 6 months, where participation is one of the following: + +* Attended triage/sprint meetings +* Opened rust-lang PRs related to async +* Reviewed rust-lang PRs related to async +* Mentored people to fix polish issues +* Led a focus area or initiative diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c9d6453e..147e103d 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -1,64 +1,170 @@ # Summary -- [👋🏽 Welcome](./welcome.md) -- [🔮 The vision](./vision.md) - - [❓How to vision](./vision/how_to_vision.md) - - [Projects](./vision/how_to_vision/projects.md) - - ["Status quo" stories](./vision/how_to_vision/status_quo.md) - - ["Shiny future" stories](./vision/how_to_vision/shiny_future.md) - - [Commenting](./vision/how_to_vision/comment.md) - - [Awards](./vision/how_to_vision/awards.md) - - [✏️ Design tenets for async](./vision/tenets.md) - - [🙋‍♀️ Cast of characters](./vision/characters.md) - - [Alan](./vision/characters/alan.md) - - [Grace](./vision/characters/grace.md) - - [Niklaus](./vision/characters/niklaus.md) - - [Barbara](./vision/characters/barbara.md) - - [⚡ Projects](./vision/projects.md) - - [Template](./vision/projects/template.md) - - [MonsterMesh (embedded sensors)](./vision/projects/MonsterMesh.md) - - [DistriData (Generic Infrastructure)](./vision/projects/DistriData.md) - - [TrafficMonitor (Custom Infrastructure)](./vision/projects/TrafficMonitor.md) - - [YouBuy (Traditional Server Application)](./vision/projects/YouBuy.md) - - [SLOW (Protocol implementation)](./vision/projects/SLOW.md) - - [😱 Status quo](./vision/status_quo.md) - - [Template](./vision/status_quo/template.md) - - [Alan finds dropping database handles is hard](./vision/status_quo/alan_finds_database_drops_hard.md) - - [Alan hates writing a `Stream`](./vision/status_quo/alan_hates_writing_a_stream.md) - - [Alan lost the world](./vision/status_quo/alan_lost_the_world.md) - - [Alan runs into stack trouble](./vision/status_quo/alan_runs_into_stack_trouble.md) - - [Alan started trusting the Rust compiler, but then... async](./vision/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md) - - [Alan tries to debug a hang](./vision/status_quo/alan_tries_to_debug_a_hang.md) - - [Alan writes a web framework](./vision/status_quo/alan_writes_a_web_framework.md) - - [Barbara carefully dismisses embedded `Future`](./vision/status_quo/barbara_carefully_dismisses_embedded_future.md) - - [Barbara makes their first foray into async](./vision/status_quo/barbara_makes_their_first_steps_into_async.md) - - [Barbara plays with async](./vision/status_quo/barbara_plays_with_async.md) - - [Barbara tries async streams](./vision/status_quo/barbara_tries_async_streams.md) - - [Barbara trims a stacktrace](./vision/status_quo/barbara_trims_a_stacktrace.md) - - [Barbara wants async insights](./vision/status_quo/barbara_wants_async_insights.md) - - [Grace deploys her service and hits obstacles](./vision/status_quo/grace_deploys_her_service.md) - - [Grace tries new libraries](./vision/status_quo/grace_tries_new_libraries.md) - - [Grace wants to integrate a C-API](./vision/status_quo/grace_wants_to_integrate_c_api.md) - - [Niklaus wants to share knowledge](./vision/status_quo/niklaus_wants_to_share_knowledge.md) - - [✨ Shiny future](./vision/shiny_future.md) - - [Template](./vision/shiny_future/template.md) - - [📅 Roadmap for 2021](./vision/roadmap.md) -- [🔍 Triage meetings](./triage.md) -- [🔬 Design docs](./design_docs.md) - - [⚠️ Yield-safe lint](./design_docs/yield_safe.md) - - [☔ Stream trait](./design_docs/stream.md) - - [⚡ Generator syntax](./design_docs/generator_syntax.md) - - [📝 AsyncRead, AsyncWrite traits](./design_docs/async_read_write.md) - - [🧬 Async fn in traits](./design_docs/async_fn_in_traits.md) - - [🔒 Mutex (future-aware)](./design_docs/mutex.md) - - [📺 Async aware channels](./design_docs/channels.md) - - [📦 Async closures](./design_docs/async_closures.md) - - [🤝 Join combinator](./design_docs/join.md) - - [🤷‍♀️ Select combinator](./design_docs/select.md) - - [🚰 Sink trait](./design_docs/sink_trait.md) - - [🎇 Async main](./design_docs/async_main.md) - - [🗑️ Async drop](./design_docs/async_drop.md) - - [♻️ Async lifecycle](./design_docs/async_lifecycle.md) - - [⏳ Completion-based futures](./design_docs/completion_based_futures.md) -- [💬 Conversations](./conversations.md) - - [🐦 2021-02-12 Twitter thread](./conversations/2021-02-12-Twitter-Thread.md) +- [Welcome](./welcome.md) +- [Charter](./CHARTER.md) +- [Meetings](./meetings.md) + - [Discussion schedule](https://github.com/orgs/rust-lang/projects/40/views/1) + - [Triage](./triage.md) +- [The vision](./vision.md) + - [❓How to vision](./vision/how_to_vision.md) + - [Owning a goal or initiative](./vision/how_to_vision/owners.md) + - [Being a stakeholder](./vision/how_to_vision/stakeholders.md) + - [Writing an evaluation](./vision/how_to_vision/evaluations.md) + - [Authoring "Status quo" stories](./vision/how_to_vision/status_quo.md) + - [Authoring "Shiny future" stories](./vision/how_to_vision/shiny_future.md) + - [Commenting on stories](./vision/how_to_vision/comment.md) + - [Awards](./vision/how_to_vision/awards.md) + - [🥰 How using async Rust ought to feel](./vision/how_it_feels.md) + - [🙋‍♀️ Cast of characters](./vision/characters.md) + - [Alan](./vision/characters/alan.md) + - [Grace](./vision/characters/grace.md) + - [Niklaus](./vision/characters/niklaus.md) + - [Barbara](./vision/characters/barbara.md) + - [⚡ Projects](./vision/projects.md) + - [Template](./vision/projects/template.md) + - [MonsterMesh (embedded sensors)](./vision/projects/MonsterMesh.md) + - [DistriData (Generic Infrastructure)](./vision/projects/DistriData.md) + - [TrafficMonitor (Custom Infrastructure)](./vision/projects/TrafficMonitor.md) + - [YouBuy (Traditional Server Application)](./vision/projects/YouBuy.md) + - [SLOW (Protocol implementation)](./vision/projects/SLOW.md) + - [😱 Status quo](./vision/status_quo.md) + - [✨ Shiny future](./vision/shiny_future.md) + - [📚 User's manual of the future](./vision/shiny_future/users_manual.md) + - [📅 Roadmap](./vision/roadmap.md) + - [Async fn everywhere](./vision/roadmap/async_fn.md) + - [Boxable async fn](./vision/roadmap/async_fn/boxable.md) + - [Async main and tests](./vision/roadmap/async_fn/async_main_and_tests.md) + - [Scoped spawn and reliable cancellation](./vision/roadmap/scopes.md) + - [Capability](./vision/roadmap/scopes/capability.md) + - [Variant: Async trait](./vision/roadmap/scopes/capability/variant_async_trait.md) + - [Variant: Leak trait](./vision/roadmap/scopes/capability/variant_leak.md) + - [Scope API](./vision/roadmap/scopes/scope_api.md) + - [Async iteration](./vision/roadmap/async_iter.md) + - [Traits](./vision/roadmap/async_iter/traits.md) + - [Generators](./vision/roadmap/async_iter/generators.md) + - [Portable and interoperable](./vision/roadmap/portable.md) + - [Read/write traits](./vision/roadmap/portable/read_write.md) + - [Timers](./vision/roadmap/portable/timers.md) + - [Spawn](./vision/roadmap/portable/spawn.md) + - [Runtime](./vision/roadmap/portable/runtime.md) + - [Polish](./vision/roadmap/polish.md) + - [must_not_suspend lint](./vision/roadmap/polish/lint_must_not_suspend.md) + - [Lint blocking fns](./vision/roadmap/polish/lint_blocking_fns.md) + - [Lint large copies](./vision/roadmap/polish/lint_large_copies.md) + - [Error messages for the most confusing scenarios](./vision/roadmap/polish/error_messages.md) + - [Stacktraces](./vision/roadmap/polish/stacktraces.md) + - [Precise Generator Captures](./vision/roadmap/polish/precise_generator_captures.md) + - [Sync and async behave the same](./vision/roadmap/polish/sync_and_async.md) + - [Tooling](./vision/roadmap/tooling.md) + - [Crashdump](./vision/roadmap/tooling/crashdump.md) + - [Testing](./vision/roadmap/testing.md) + - [Documentation](./vision/roadmap/documentation.md) + - [Async book](./vision/roadmap/documentation/async_book.md) + - [Threadsafe portability](./vision/roadmap/threadsafe_portability.md) + - [Async overloading](./vision/roadmap/async_overloading.md) + - [❓ Major unresolved questions or controversies](./vision/unresolved_questions.md) + - [Default runtime?](./vision/unresolved_questions/default_runtime.md) + - [How to represent the `AsyncFn` traits?](./vision/unresolved_questions/async_fn.md) + - [How best to integrate voluntary cancellation?](./vision/unresolved_questions/cancellation.md) + - [Extend stdlib to permit portable async without generics?](./vision/unresolved_questions/portable_without_generics.md) + - [To await or not to await?](./vision/unresolved_questions/await_or_not.md) + - [💝 Appendix: Submitted stories](./vision/submitted_stories.md) + - [😱 Status quo](./vision/submitted_stories/status_quo.md) + - [Template](./vision/submitted_stories/status_quo/template.md) + - [Alan builds a task scheduler](./vision/submitted_stories/status_quo/alan_builds_a_task_scheduler.md) + - [Alan creates a hanging alarm](./vision/submitted_stories/status_quo/alan_creates_a_hanging_alarm.md) + - [Alan finds dropping database handles is hard](./vision/submitted_stories/status_quo/alan_finds_database_drops_hard.md) + - [Alan has an external event loop](./vision/submitted_stories/status_quo/alan_has_an_event_loop.md) + - [Alan hates writing a `Stream`](./vision/submitted_stories/status_quo/alan_hates_writing_a_stream.md) + - [Alan iteratively regresses performance](./vision/submitted_stories/status_quo/alan_iteratively_regresses.md) + - [Alan lost the world](./vision/submitted_stories/status_quo/alan_lost_the_world.md) + - [Alan misses C# async](./vision/submitted_stories/status_quo/alan_misses_c_sharp_async.md) + - [Alan needs async in traits](./vision/submitted_stories/status_quo/alan_needs_async_in_traits.md) + - [Alan runs into stack trouble](./vision/submitted_stories/status_quo/alan_runs_into_stack_trouble.md) + - [Alan started trusting the Rust compiler, but then... async](./vision/submitted_stories/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md) + - [Alan thinks he needs async locks](./vision/submitted_stories/status_quo/alan_thinks_he_needs_async_locks.md) + - [Alan tries using a socket Sink](./vision/submitted_stories/status_quo/alan_tries_a_socket_sink.md) + - [Alan tries to debug a hang](./vision/submitted_stories/status_quo/alan_tries_to_debug_a_hang.md) + - [Alan tries to cache requests, which doesn't always happen](./vision/submitted_stories/status_quo/alan_builds_a_cache.md) + - [Alan tries tries processing some files](./vision/submitted_stories/status_quo/alan_tries_processing_some_files.md) + - [Alan wants a prefetch iterator](./vision/submitted_stories/status_quo/alan_wants_prefetch_iterator.md) + - [Alan wants to migrate a web server to Rust](./vision/submitted_stories/status_quo/alan_picks_web_server.md) + - [Alan writes a web framework](./vision/submitted_stories/status_quo/alan_writes_a_web_framework.md) + - [Alan extends an AWS service](./vision/submitted_stories/status_quo/aws_engineer.md) + - [Writing a Java-based service at AWS](./vision/submitted_stories/status_quo/aws_engineer/writing_a_java_based_service.md) + - [Getting started with Rust](./vision/submitted_stories/status_quo/aws_engineer/getting_started_with_rust.md) + - [Coming from Java](./vision/submitted_stories/status_quo/aws_engineer/coming_from_java.md) + - [Exploring the ecosystem](./vision/submitted_stories/status_quo/aws_engineer/ecosystem.md) + - [Juggling error handling](./vision/submitted_stories/status_quo/aws_engineer/juggling_error_handling.md) + - [Failure to parallelize](./vision/submitted_stories/status_quo/aws_engineer/failure_to_parallelize.md) + - [Borrow check errors](./vision/submitted_stories/status_quo/aws_engineer/borrow_check_errors.md) + - [Solving a deadlock](./vision/submitted_stories/status_quo/aws_engineer/solving_a_deadlock.md) + - [Encoutering pin](./vision/submitted_stories/status_quo/aws_engineer/encountering_pin.md) + - [Figuring out the best option](./vision/submitted_stories/status_quo/aws_engineer/figuring_out_the_best_option.md) + - [Testing his service](./vision/submitted_stories/status_quo/aws_engineer/testing_the_service.md) + - [Using the debugger](./vision/submitted_stories/status_quo/aws_engineer/using_the_debugger.md) + - [Missed Waker leads to lost performance](./vision/submitted_stories/status_quo/aws_engineer/missed_waker_leads_to_lost_performance.md) + - [Debugging performance problems](./vision/submitted_stories/status_quo/aws_engineer/debugging_performance_problems.md) + - [Getting ready to deply](./vision/submitted_stories/status_quo/aws_engineer/getting_ready_to_deploy.md) + - [Using JNI](./vision/submitted_stories/status_quo/aws_engineer/using_jni.md) + - [Barbara anguishes over HTTP](./vision/submitted_stories/status_quo/barbara_anguishes_over_http.md) + - [Barbara awants single threaded optimizations](./vision/submitted_stories/status_quo/barbara_wants_single_threaded_optimizations.md) + - [Barbara battles buffered streams](./vision/submitted_stories/status_quo/barbara_battles_buffered_streams.md) + - [Barbara begets backpressure and benchmarks async_trait](./vision/submitted_stories/status_quo/barbara_benchmarks_async_trait.md) + - [Barbara bridges sync and async in `perf.rust-lang.org`](./vision/submitted_stories/status_quo/barbara_bridges_sync_and_async.md) + - [Barbara builds an async executor](./vision/submitted_stories/status_quo/barbara_builds_an_async_executor.md) + - [Barbara carefully dismisses embedded `Future`](./vision/submitted_stories/status_quo/barbara_carefully_dismisses_embedded_future.md) + - [Barbara compares some C++ code](./vision/submitted_stories/status_quo/barbara_compares_some_cpp_code.md) + - [Barbara gets burned by select](./vision/submitted_stories/status_quo/barbara_gets_burned_by_select.md) + - [Barbara makes their first foray into async](./vision/submitted_stories/status_quo/barbara_makes_their_first_steps_into_async.md) + - [Barbara needs Async Helpers](./vision/submitted_stories/status_quo/barbara_needs_async_helpers.md) + - [Barbara plays with async](./vision/submitted_stories/status_quo/barbara_plays_with_async.md) + - [Barbara polls a Mutex](./vision/submitted_stories/status_quo/barbara_polls_a_mutex.md) + - [Barbara tries async streams](./vision/submitted_stories/status_quo/barbara_tries_async_streams.md) + - [Barbara tries Unix socket](./vision/submitted_stories/status_quo/barbara_tries_unix_socket.md) + - [Barbara trims a stacktrace](./vision/submitted_stories/status_quo/barbara_trims_a_stacktrace.md) + - [Barbara wants async insights](./vision/submitted_stories/status_quo/barbara_wants_async_insights.md) + - [Barbara wants to use GhostCell-like cell borrowing](./vision/submitted_stories/status_quo/barbara_wants_to_use_ghostcell.md) + - [Barbara wishes for an easy runtim switch](./vision/submitted_stories/status_quo/barbara_wishes_for_easy_runtime_switch.md) + - [Barbara writes a runtime-agnostic library](./vision/submitted_stories/status_quo/barbara_writes_a_runtime_agnostic_lib.md) + - [Grace debugs a crash dump](./vision/submitted_stories/status_quo/grace_debugs_a_crash_dump.md) + - [Grace deploys her service and hits obstacles](./vision/submitted_stories/status_quo/grace_deploys_her_service.md) + - [Grace tries new libraries](./vision/submitted_stories/status_quo/grace_tries_new_libraries.md) + - [Grace waits for gdb next](./vision/submitted_stories/status_quo/grace_waits_for_gdb_next.md) + - [Grace wants a zero copy API](./vision/submitted_stories/status_quo/grace_wants_a_zero_copy_api.md) + - [Grace wants to integrate a C-API](./vision/submitted_stories/status_quo/grace_wants_to_integrate_c_api.md) + - [Grace writes a new Facebook service](./vision/submitted_stories/status_quo/grace_writes_a_new_fb_service.md) + - [Niklaus builds a hydrodynamics simulator](./vision/submitted_stories/status_quo/niklaus_simulates_hydrodynamics.md) + - [Niklaus wants to share knowledge](./vision/submitted_stories/status_quo/niklaus_wants_to_share_knowledge.md) + - [✨ Shiny future](./vision/submitted_stories/shiny_future.md) + - [Template](./vision/submitted_stories/shiny_future/template.md) + - [Alan learns async on his own](./vision/submitted_stories/shiny_future/alan_learns_async_on_his_own.md) + - [Alan's trust in the compiler is rewarded](./vision/submitted_stories/shiny_future/alans_trust_in_the_compiler_is_rewarded.md) + - [Alan switches runtimes](./vision/submitted_stories/shiny_future/alan_switches_runtimes.md) + - [Barbara appreciates great performance analysis tools](./vision/submitted_stories/shiny_future/barbara_appreciates_performance_tools.md) + - [Barbara enjoys an async sandwich](./vision/submitted_stories/shiny_future/barbara_enjoys_an_async_sandwich.md) + - [Barbara makes a wish](./vision/submitted_stories/shiny_future/barbara_makes_a_wish.md) + - [Barbara wants async read write](./vision/submitted_stories/shiny_future/barbara_wants_async_rw.md) + - [Barbara wants async tracing](./vision/submitted_stories/shiny_future/barbara_wants_async_tracing.md) + - [Grace debugs a crash dump again](./vision/submitted_stories/shiny_future/grace_debugs_a_crash_dump_again.md) +- [Design docs](./design_docs.md) + - [⚠️ Yield-safe lint](./design_docs/yield_safe.md) + - [☔ Stream trait](./design_docs/stream.md) + - [⚡ Generator syntax](./design_docs/generator_syntax.md) + - [📝 AsyncRead, AsyncWrite traits](./design_docs/async_read_write.md) + - [🧬 Async fn in traits](./design_docs/async_fn_in_traits.md) + - [🔒 Mutex (future-aware)](./design_docs/mutex.md) + - [📺 Async aware channels](./design_docs/channels.md) + - [📦 Async closures](./design_docs/async_closures.md) + - [🤝 Join combinator](./design_docs/join.md) + - [🤷‍♀️ Select combinator](./design_docs/select.md) + - [🚰 Sink trait](./design_docs/sink_trait.md) + - [🎇 Async main](./design_docs/async_main.md) + - [🗑️ Async drop](./design_docs/async_drop.md) + - [♻️ Async lifecycle](./design_docs/async_lifecycle.md) + - [⏳ Completion-based futures](./design_docs/completion_based_futures.md) + - [🥞 Async Stack Traces](./design_docs/async_stack_traces.md) +- [Conversations](./conversations.md) + - [2021-02-12 Twitter thread](./conversations/2021-02-12-Twitter-Thread.md) +- [Glossary](./glossary.md) +- [Acknowledgments](./acknowledgments.md) diff --git a/src/acknowledgments.md b/src/acknowledgments.md new file mode 100644 index 00000000..26c5e1e0 --- /dev/null +++ b/src/acknowledgments.md @@ -0,0 +1,163 @@ +# ❤️ Acknowledgments + +Thanks to everyone who helped forming the future of Rust async. + +## ✍️ Participating in an writing session + +Thanks to everyone who helped writing Stories by participating in one of the Async Rust writing sessions. + +* [Darksonn](https://github.com/Darksonn) +* [doc-jones](https://github.com/doc-jones) +* [echorior](https://github.com/echorior) +* [emmanuelantony2000](https://github.com/emmanuelantony2000) +* [eminence](https://github.com/eminence) +* [farnz](https://github.com/farnz) +* [jzrake](https://github.com/jzrake) +* [pcwalton](https://github.com/pcwalton) +* [pickfire](https://github.com/pickfire) +* [pnkfelix](https://github.com/pnkfelix) +* [nikomatsakis](https://github.com/nikomatsakis) +* [rylev](https://github.com/rylev) +* [shanemccrary](https://github.com/shanemccrary) +* [Trvr_B](https://twitter.com/Trvr_B) +* [tmandry](https://github.com/tmandry) +* [yoshuawuyts](https://github.com/yoshuawuyts) +* [wesleywiser](https://github.com/wesleywiser) +* [zeenix](https://github.com/zeenix) + +## 💬 Discussing about stories + +Thanks to everyone who discussed about stories, shiny future and new features. + +* [AldaronLau](https://github.com/AldaronLau) +* [alsuren](https://github.com/alsuren) +* [Ar37-rs](https://github.com/Ar37-rs) +* [AustinWise](https://github.com/AustinWise) +* [broccolihighkicks](https://github.com/broccolihighkicks) +* [BurntSushi](https://github.com/BurntSushi) +* [carols10cents](https://github.com/carols10cents) +* [cortopy](https://github.com/cortopy) +* [cryptoquick](https://github.com/cryptoquick) +* [Darksonn](https://github.com/Darksonn) +* [dlight](https://github.com/dlight) +* [eduardocanellas](https://github.com/eduardocanellas) +* [eminence](https://github.com/eminence) +* [erichgess](https://github.com/erichgess) +* [erickt](https://github.com/erickt) +* [estebank](https://github.com/estebank) +* [evan-brass](https://github.com/evan-brass) +* [farnz](https://github.com/farnz) +* [fasterthanlime](https://github.com/fasterthanlime) +* [Fice](https://github.com/Fice) +* [fpagliughi](https://github.com/fpagliughi) +* [FreddieGilbraith](https://github.com/FreddieGilbraith) +* [Frederik-Baetens](https://github.com/Frederik-Baetens) +* [galich](https://github.com/galich) +* [geropl](https://github.com/geropl) +* [gilescope](https://github.com/gilescope) +* [glommer](https://github.com/glommer) +* [Gottox](https://github.com/Gottox) +* [guswynn](https://github.com/guswynn) +* [ibraheemdev](https://github.com/ibraheemdev) +* [jbr](https://github.com/jbr) +* [jlkiri](https://github.com/jlkiri) +* [John-Nagle](https://github.com/John-Nagle) +* [jonathandturner](https://github.com/jonathandturner) +* [jonathanstrong](https://github.com/jonathanstrong) +* [jonhoo](https://github.com/jonhoo) +* [jrvanwhy](https://github.com/jrvanwhy) +* [jzrake](https://github.com/jzrake) +* [kmeisthax](https://github.com/kmeisthax) +* [Kobzol](https://github.com/Kobzol) +* [kornelski](https://github.com/kornelski) +* [laurieontech](https://github.com/laurieontech) +* [lqf96](https://github.com/lqf96) +* [LucioFranco](https://github.com/LucioFranco) +* [magnet](https://github.com/magnet) +* [Mark-Simulacrum](https://github.com/Mark-Simulacrum) +* [Matthias247](https://github.com/Matthias247) +* [metajack](https://github.com/metajack) +* [MidasLamb](https://github.com/MidasLamb) +* [nagisa](https://github.com/nagisa) +* [nikomatsakis](https://github.com/nikomatsakis) +* [o0Ignition0o](https://github.com/o0Ignition0o) +* [pickfire](https://github.com/pickfire) +* [pnkfelix](https://github.com/pnkfelix) +* [punkeel](https://github.com/punkeel) +* [rgreinho](https://github.com/rgreinho) +* [rhmoller](https://github.com/rhmoller) +* [ririsoft](https://github.com/ririsoft) +* [robjtede](https://github.com/robjtede) +* [rustbot](https://github.com/rustbot) +* [rylev](https://github.com/rylev) +* [sdroege](https://github.com/sdroege) +* [seanmonstar](https://github.com/seanmonstar) +* [stephanemagnenat](https://github.com/stephanemagnenat) +* [sticnarf](https://github.com/sticnarf) +* [stuhood](https://github.com/stuhood) +* [Stupremee](https://github.com/Stupremee) +* [taiki-e](https://github.com/taiki-e) +* [tanriol](https://github.com/tanriol) +* [tmandry](https://github.com/tmandry) +* [uazu](https://github.com/uazu) +* [urhein](https://github.com/urhein) +* [velvia](https://github.com/velvia) +* [vladinator1000](https://github.com/vladinator1000) +* [wesleywiser](https://github.com/wesleywiser) +* [wraithan](https://github.com/wraithan) +* [y21](https://github.com/y21) +* [yoshuawuyts](https://github.com/yoshuawuyts) +* [zeenix](https://github.com/zeenix) + +## ✨ Directly contributing + +Thanks to everyone who opened a Pull Request and wrote a story, shiny future or improved the organization of the repository. + +* [alsuren](https://github.com/alsuren) +* [autarch](https://github.com/autarch) +* [bIgBV](https://github.com/bIgBV) +* [carllerche](https://github.com/carllerche) +* [carols10cents](https://github.com/carols10cents) +* [cortopy](https://github.com/cortopy) +* [cryptoquick](https://github.com/cryptoquick) +* [csmoe](https://github.com/csmoe) +* [Darksonn](https://github.com/Darksonn) +* [doc-jones](https://github.com/doc-jones) +* [ehuss](https://github.com/ehuss) +* [eminence](https://github.com/eminence) +* [emmanuelantony2000](https://github.com/emmanuelantony2000) +* [erichgess](https://github.com/erichgess) +* [farnz](https://github.com/farnz) +* [Fice](https://github.com/Fice) +* [Frederik-Baetens](https://github.com/Frederik-Baetens) +* [Gottox](https://github.com/Gottox) +* [guswynn](https://github.com/guswynn) +* [JakubKoralewski](https://github.com/JakubKoralewski) +* [jonathandturner](https://github.com/jonathandturner) +* [jonhoo](https://github.com/jonhoo) +* [jrvanwhy](https://github.com/jrvanwhy) +* [kmeisthax](https://github.com/kmeisthax) +* [kw-fn](https://github.com/kw-fn) +* [LucioFranco](https://github.com/LucioFranco) +* [magurotuna](https://github.com/magurotuna) +* [Mark-Simulacrum](https://github.com/Mark-Simulacrum) +* [metajack](https://github.com/metajack) +* [MidasLamb](https://github.com/MidasLamb) +* [nealmcb](https://github.com/nealmcb) +* [nellshamrell](https://github.com/nellshamrell) +* [nikomatsakis](https://github.com/nikomatsakis) +* [nyanpasu64](https://github.com/nyanpasu64) +* [o0Ignition0o](https://github.com/o0Ignition0o) +* [pickfire](https://github.com/pickfire) +* [pnkfelix](https://github.com/pnkfelix) +* [punkeel](https://github.com/punkeel) +* [rylev](https://github.com/rylev) +* [seanmonstar](https://github.com/seanmonstar) +* [sticnarf](https://github.com/sticnarf) +* [Stupremee](https://github.com/Stupremee) +* [taiki-e](https://github.com/taiki-e) +* [ThatGeoGuy](https://github.com/ThatGeoGuy) +* [tmandry](https://github.com/tmandry) +* [wesleywiser](https://github.com/wesleywiser) +* [yoshuawuyts](https://github.com/yoshuawuyts) +* [zeenix](https://github.com/zeenix) diff --git a/src/design_docs/async_drop.md b/src/design_docs/async_drop.md index e2796fa3..62b5a5b4 100644 --- a/src/design_docs/async_drop.md +++ b/src/design_docs/async_drop.md @@ -1 +1 @@ -# 🗑️ Async drop \ No newline at end of file +# 🗑️ Async drop diff --git a/src/design_docs/async_fn_in_traits.md b/src/design_docs/async_fn_in_traits.md index 4e12312a..92a34e8b 100644 --- a/src/design_docs/async_fn_in_traits.md +++ b/src/design_docs/async_fn_in_traits.md @@ -1,88 +1 @@ # 🧬 Async fn in traits - -* [Why async fn in traits are hard][wafth] - -[wafth]: http://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/ - -## General goal - -```rust,ignore -trait Foo { - // Currently disallowed: - async fn bar(); -} -``` - -## Concerns - -### How to name the resulting future - -If you wanted to name the future that results from calling `bar` (or whatever), you can't. - -Also true for functions `fn bar() -> impl Trait`. - -### Requiring `Send` on futures - -[Relevant thread](https://internals.rust-lang.org/t/how-often-do-you-want-non-send-futures/10360) - -```rust,ignore -async fn foo() {} - -// desugars to -fn foo() -> impl Future { } // resulting type is Send if it can be - -// alternative desugaring we chose not to adopt would require Send -fn foo() -> impl Future + Send { } -``` - -If I want to constrain the future I get back from a method, it is difficult to do without a name: - -```rust,ignore -trait Service { - async fn request(&self); -} - -fn parallel_service() -where - S::Future: Send, -{ - ... -} -``` - -* Should this be solved at the impl trait layer -* Or should we specialize something for async functions -* Would be nice, if there are many, associated types, to have some shorthand - -## Example use case: the Service - -```rust,ignore -trait Service { - type Future: Future; - - fn request(&self, ...) -> Self::Future; -} - -impl Service for MyService { - type Future = impl Future; - - fn request(&self) -> Self::Future { - async move { .. } - } -} -``` - -* Dependent on impl Trait, see lang-team repo - -## Example use case: capturing lifetimes of arguments - -```rust,ignore -trait MyMethod { - async fn foo(&self); -} -``` - -## 🤔 Frequently Asked Questions - -### **What do people say about this to their friends on twitter?** -* (Explain your key points here) diff --git a/src/design_docs/async_main.md b/src/design_docs/async_main.md index fec7dfe8..33767f54 100644 --- a/src/design_docs/async_main.md +++ b/src/design_docs/async_main.md @@ -1,7 +1 @@ # 🎇 Async main - -## What is it? - -## Motivation - -## Frequently Asked Questions \ No newline at end of file diff --git a/src/design_docs/async_stack_traces.md b/src/design_docs/async_stack_traces.md new file mode 100644 index 00000000..337e4790 --- /dev/null +++ b/src/design_docs/async_stack_traces.md @@ -0,0 +1,765 @@ +# Async Stack Trace Design Notes + +This page has notes on the state of [async stack traces], highlights specific issues with current stack traces, and suggests changes to improve these issues. + +The two main suggestions are: + +1. Allow async runtimes to control where the short backtrace cutoff happens +2. Expand the options allowed in `RUST_BACKTRACE` to support including/excluding frames from certain crates or module paths in the backtrace. + +[async stack traces]: ../vision/roadmap/polish/stacktraces.md + +## The Current State of Things + +The current state of stack traces was captured pretty well in the story [Barbara Trims a Stack Trace][barbara-trims-stack-trace]. We've recreated a similar example to the one in the story here. We'll look at several executors. + +[barbara-trims-stack-trace]: https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/barbara_trims_a_stacktrace.html + +### Tokio + +

Short Backtrace + +``` +thread 'main' panicked at 'explicit panic', C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10:5 +stack backtrace: + 0: std::panicking::begin_panic_handler + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:517 + 1: core::panicking::panic_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:101 + 2: core::panicking::panic + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:50 + 3: common::baz::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10 + 4: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 5: common::bar::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:6 + 6: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 7: common::foo::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:2 + 8: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 9: async_tokio::main::generator$0 + at .\src\main.rs:4 +10: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 +11: tokio::park::thread::impl$5::block_on::closure$0 > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\park\thread.rs:263 +12: tokio::coop::with_budget::closure$0 > >,tokio::park::thread::impl$5::block_on::closure$0> + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\coop.rs:106 +13: std::thread::local::LocalKey >::try_with,tokio::coop::with_budget::closure$0,enum$ > > > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 +14: std::thread::local::LocalKey >::with,tokio::coop::with_budget::closure$0,enum$ > > > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 +15: tokio::coop::with_budget + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\coop.rs:99 +16: tokio::coop::budget + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\coop.rs:76 +17: tokio::park::thread::CachedParkThread::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\park\thread.rs:263 +18: tokio::runtime::enter::Enter::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\runtime\enter.rs:151 +19: tokio::runtime::thread_pool::ThreadPool::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\runtime\thread_pool\mod.rs:77 +20: tokio::runtime::Runtime::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\runtime\mod.rs:463 +21: async_tokio::main + at .\src\main.rs:4 +22: core::ops::function::FnOnce::call_once > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\ops\function.rs:227 +note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. +``` + +
+ +
Full Backtrace + +``` +thread 'main' panicked at 'explicit panic', C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10:5 +stack backtrace: + 0: 0x7ff7986d431e - std::backtrace_rs::backtrace::dbghelp::trace + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\..\..\backtrace\src\backtrace\dbghelp.rs:98 + 1: 0x7ff7986d431e - std::backtrace_rs::backtrace::trace_unsynchronized + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\..\..\backtrace\src\backtrace\mod.rs:66 + 2: 0x7ff7986d431e - std::sys_common::backtrace::_print_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:67 + 3: 0x7ff7986d431e - std::sys_common::backtrace::_print::impl$0::fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:46 + 4: 0x7ff7986e4a8a - core::fmt::write + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\fmt\mod.rs:1150 + 5: 0x7ff7986d22a8 - std::io::Write::write_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\io\mod.rs:1667 + 6: 0x7ff7986d6c96 - std::sys_common::backtrace::_print + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:49 + 7: 0x7ff7986d6c96 - std::sys_common::backtrace::print + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:36 + 8: 0x7ff7986d6c96 - std::panicking::default_hook::closure$1 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:210 + 9: 0x7ff7986d6784 - std::panicking::default_hook + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:227 + 10: 0x7ff7986d72f5 - std::panicking::rust_panic_with_hook + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:624 + 11: 0x7ff7986d6eaf - std::panicking::begin_panic_handler::closure$0 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:519 + 12: 0x7ff7986d4c67 - std::sys_common::backtrace::__rust_end_short_backtrace + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:141 + 13: 0x7ff7986d6e39 - std::panicking::begin_panic_handler + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:517 + 14: 0x7ff7986ea170 - core::panicking::panic_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:101 + 15: 0x7ff7986ea0bc - core::panicking::panic + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:50 + 16: 0x7ff798631d9f - common::baz::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10 + 17: 0x7ff798632139 - core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 18: 0x7ff798631ccb - common::bar::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:6 + 19: 0x7ff7986320a9 - core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 20: 0x7ff798631ef2 - common::foo::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:2 + 21: 0x7ff798632019 - core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 22: 0x7ff798635718 - async_tokio::main::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-tokio\src\main.rs:4 + 23: 0x7ff7986321c9 - core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 24: 0x7ff798631b9a - tokio::park::thread::impl$5::block_on::closure$0 > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\park\thread.rs:263 + 25: 0x7ff798632df9 - tokio::coop::with_budget::closure$0 > >,tokio::park::thread::impl$5::block_on::closure$0> + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\coop.rs:106 + 26: 0x7ff798632652 - std::thread::local::LocalKey >::try_with,tokio::coop::with_budget::closure$0,enum$ > > > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 + 27: 0x7ff79863251d - std::thread::local::LocalKey >::with,tokio::coop::with_budget::closure$0,enum$ > > > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 + 28: 0x7ff79863165c - tokio::coop::with_budget + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\coop.rs:99 + 29: 0x7ff79863165c - tokio::coop::budget + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\coop.rs:76 + 30: 0x7ff79863165c - tokio::park::thread::CachedParkThread::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\park\thread.rs:263 + 31: 0x7ff7986358b4 - tokio::runtime::enter::Enter::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\runtime\enter.rs:151 + 32: 0x7ff798631046 - tokio::runtime::thread_pool::ThreadPool::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\runtime\thread_pool\mod.rs:77 + 33: 0x7ff798632b68 - tokio::runtime::Runtime::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\runtime\mod.rs:463 + 34: 0x7ff798632ca3 - async_tokio::main + at C:\Users\ericholk\repo\backtrace-examples\async-tokio\src\main.rs:4 + 35: 0x7ff7986332ab - core::ops::function::FnOnce::call_once > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\ops\function.rs:227 + 36: 0x7ff7986311fb - std::sys_common::backtrace::__rust_begin_short_backtrace > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\sys_common\backtrace.rs:125 + 37: 0x7ff798631121 - std::rt::lang_start::closure$0 > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\rt.rs:63 + 38: 0x7ff7986d7886 - core::ops::function::impls::impl$2::call_once + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\ops\function.rs:259 + 39: 0x7ff7986d7886 - std::panicking::try::do_call + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:403 + 40: 0x7ff7986d7886 - std::panicking::try + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:367 + 41: 0x7ff7986d7886 - std::panic::catch_unwind + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panic.rs:129 + 42: 0x7ff7986d7886 - std::rt::lang_start_internal::closure$2 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\rt.rs:45 + 43: 0x7ff7986d7886 - std::panicking::try::do_call + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:403 + 44: 0x7ff7986d7886 - std::panicking::try + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:367 + 45: 0x7ff7986d7886 - std::panic::catch_unwind + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panic.rs:129 + 46: 0x7ff7986d7886 - std::rt::lang_start_internal + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\rt.rs:45 + 47: 0x7ff7986310ef - std::rt::lang_start > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\rt.rs:62 + 48: 0x7ff798632d46 - main + 49: 0x7ff7986e8dd0 - invoke_main + at d:\a01\_work\6\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78 + 50: 0x7ff7986e8dd0 - __scrt_common_main_seh + at d:\a01\_work\6\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288 + 51: 0x7ffbe0a26ab0 - BaseThreadInitThunk + 52: 0x7ffbe1771dbb - RtlUserThreadStart +``` + +
+ +### Async-std + +
Short Backtrace + +``` +thread 'main' panicked at 'explicit panic', C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10:5 +stack backtrace: + 0: std::panicking::begin_panic_handler + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:517 + 1: core::panicking::panic_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:101 + 2: core::panicking::panic + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:50 + 3: common::baz::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10 + 4: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 5: common::bar::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:6 + 6: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 7: common::foo::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:2 + 8: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 9: async_std::task::builder::impl$1::poll::closure$0 > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\builder.rs:199 + 10: async_std::task::task_locals_wrapper::impl$0::set_current::closure$0 > > > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\task_locals_wrapper.rs:60 + 11: std::thread::local::LocalKey > >::try_with >,async_std::task::task_locals_wrapper::im + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 + 12: std::thread::local::LocalKey > >::with >,async_std::task::task_locals_wrapper::impl$0 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 + 13: async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current > > > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\task_locals_wrapper.rs:55 + 14: async_std::task::builder::impl$1::poll > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\builder.rs:197 + 15: futures_lite::future::impl$12::poll,async_std::task::builder::SupportTaskLocals >,core::future::from_generator::GenFuture > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\futures-lite-1.12.0\src\future.rs:526 + 16: async_executor::impl$4::run::generator$0,async_std::task::builder::SupportTaskLocals > > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-executor-1.4.1\src\lib.rs:242 + 17: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 18: async_executor::impl$9::run::generator$0,async_std::task::builder::SupportTaskLocals > > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-executor-1.4.1\src\lib.rs:447 + 19: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 20: async_io::driver::block_on,core::future::from_generator::GenFuture > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-io-1.6.0\src\driver.rs:142 + 21: async_global_executor::reactor::block_on::closure$0,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-global-executor-2.0.2\src\reactor.rs:3 + 22: async_global_executor::reactor::block_on,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-global-executor-2.0.2\src\reactor.rs:12 + 23: async_global_executor::executor::block_on::closure$0 >,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-global-executor-2.0.2\src\executor.rs:26 + 24: std::thread::local::LocalKey::try_with > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 + 25: std::thread::local::LocalKey::with > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 + 26: async_global_executor::executor::block_on >,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-global-executor-2.0.2\src\executor.rs:26 + 27: async_std::task::builder::impl$0::blocking::closure$0::closure$0,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\builder.rs:171 + 28: async_std::task::task_locals_wrapper::impl$0::set_current::closure$0 > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\task_locals_wrapper.rs:60 + 29: std::thread::local::LocalKey > >::try_with >,async_std::task::task_locals_wrapper::im + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 + 30: std::thread::local::LocalKey > >::with >,async_std::task::task_locals_wrapper::impl$0 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 + 31: async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\task_locals_wrapper.rs:55 + 32: async_std::task::builder::impl$0::blocking::closure$0,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\builder.rs:168 + 33: std::thread::local::LocalKey >::try_with,async_std::task::builder::impl$0::blocking::closure$0,tuple$<> > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 + 34: std::thread::local::LocalKey >::with,async_std::task::builder::impl$0::blocking::closure$0,tuple$<> > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 + 35: async_std::task::builder::Builder::blocking,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\builder.rs:161 + 36: async_std::task::block_on::block_on,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\block_on.rs:33 + 37: async_std::main + at .\src\main.rs:2 + 38: core::ops::function::FnOnce::call_once > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\ops\function.rs:227 +note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. +error: process didn't exit successfully: `target\debug\async-std.exe` (exit code: 101) +``` + +
+ +
Full Backtrace + +``` +thread 'main' panicked at 'explicit panic', C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10:5 +stack backtrace: + 0: 0x7ff6d4162fee - std::backtrace_rs::backtrace::dbghelp::trace + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\..\..\backtrace\src\backtrace\dbghelp.rs:98 + 1: 0x7ff6d4162fee - std::backtrace_rs::backtrace::trace_unsynchronized + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\..\..\backtrace\src\backtrace\mod.rs:66 + 2: 0x7ff6d4162fee - std::sys_common::backtrace::_print_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:67 + 3: 0x7ff6d4162fee - std::sys_common::backtrace::_print::impl$0::fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:46 + 4: 0x7ff6d4172dba - core::fmt::write + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\fmt\mod.rs:1150 + 5: 0x7ff6d4160fa8 - std::io::Write::write_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\io\mod.rs:1667 + 6: 0x7ff6d4165466 - std::sys_common::backtrace::_print + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:49 + 7: 0x7ff6d4165466 - std::sys_common::backtrace::print + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:36 + 8: 0x7ff6d4165466 - std::panicking::default_hook::closure$1 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:210 + 9: 0x7ff6d4164f54 - std::panicking::default_hook + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:227 + 10: 0x7ff6d4165ac5 - std::panicking::rust_panic_with_hook + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:624 + 11: 0x7ff6d416567f - std::panicking::begin_panic_handler::closure$0 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:519 + 12: 0x7ff6d4163937 - std::sys_common::backtrace::__rust_end_short_backtrace + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:141 + 13: 0x7ff6d4165609 - std::panicking::begin_panic_handler + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:517 + 14: 0x7ff6d417c2d0 - core::panicking::panic_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:101 + 15: 0x7ff6d417c21c - core::panicking::panic + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:50 + 16: 0x7ff6d40c47df - common::baz::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10 + 17: 0x7ff6d40c7749 - core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 18: 0x7ff6d40c470b - common::bar::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:6 + 19: 0x7ff6d40c7869 - core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 20: 0x7ff6d40c4932 - common::foo::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:2 + 21: 0x7ff6d40c77d9 - core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 22: 0x7ff6d40c1403 - async_std::task::builder::impl$1::poll::closure$0 > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\builder.rs:199 + 23: 0x7ff6d40c2f38 - async_std::task::task_locals_wrapper::impl$0::set_current::closure$0 > > > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\task_locals_wrapper.rs:60 + 24: 0x7ff6d40c2212 - std::thread::local::LocalKey > >::try_with >,async_std::task::task_locals_wrapper::im + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 + 25: 0x7ff6d40c1cbd - std::thread::local::LocalKey > >::with >,async_std::task::task_locals_wrapper::impl$0 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 + 26: 0x7ff6d40c2e88 - async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current > > > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\task_locals_wrapper.rs:55 + 27: 0x7ff6d40c13a5 - async_std::task::builder::impl$1::poll > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\builder.rs:197 + 28: 0x7ff6d40c5aeb - futures_lite::future::impl$12::poll,async_std::task::builder::SupportTaskLocals >,core::future::from_generator::GenFuture > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\futures-lite-1.12.0\src\future.rs:526 + 29: 0x7ff6d40c3928 - async_executor::impl$4::run::generator$0,async_std::task::builder::SupportTaskLocals > > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-executor-1.4.1\src\lib.rs:242 + 30: 0x7ff6d40c7629 - core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 31: 0x7ff6d40c3543 - async_executor::impl$9::run::generator$0,async_std::task::builder::SupportTaskLocals > > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-executor-1.4.1\src\lib.rs:447 + 32: 0x7ff6d40c7599 - core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 33: 0x7ff6d40c623c - async_io::driver::block_on,core::future::from_generator::GenFuture > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-io-1.6.0\src\driver.rs:142 + 34: 0x7ff6d40c3e84 - async_global_executor::reactor::block_on::closure$0,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-global-executor-2.0.2\src\reactor.rs:3 + 35: 0x7ff6d40c3e4f - async_global_executor::reactor::block_on,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-global-executor-2.0.2\src\reactor.rs:12 + 36: 0x7ff6d40c31ee - async_global_executor::executor::block_on::closure$0 >,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-global-executor-2.0.2\src\executor.rs:26 + 37: 0x7ff6d40c231a - std::thread::local::LocalKey::try_with > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 + 38: 0x7ff6d40c1d5d - std::thread::local::LocalKey::with > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 + 39: 0x7ff6d40c3198 - async_global_executor::executor::block_on >,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-global-executor-2.0.2\src\executor.rs:26 + 40: 0x7ff6d40c1b09 - async_std::task::builder::impl$0::blocking::closure$0::closure$0,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\builder.rs:171 + 41: 0x7ff6d40c3074 - async_std::task::task_locals_wrapper::impl$0::set_current::closure$0 > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\task_locals_wrapper.rs:60 + 42: 0x7ff6d40c210a - std::thread::local::LocalKey > >::try_with >,async_std::task::task_locals_wrapper::im + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 + 43: 0x7ff6d40c1c63 - std::thread::local::LocalKey > >::with >,async_std::task::task_locals_wrapper::impl$0 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 + 44: 0x7ff6d40c2e48 - async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\task_locals_wrapper.rs:55 + 45: 0x7ff6d40c1a28 - async_std::task::builder::impl$0::blocking::closure$0,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\builder.rs:168 + 46: 0x7ff6d40c1fea - std::thread::local::LocalKey >::try_with,async_std::task::builder::impl$0::blocking::closure$0,tuple$<> > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 + 47: 0x7ff6d40c1bfd - std::thread::local::LocalKey >::with,async_std::task::builder::impl$0::blocking::closure$0,tuple$<> > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 + 48: 0x7ff6d40c17a3 - async_std::task::builder::Builder::blocking,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\builder.rs:161 + 49: 0x7ff6d40c326a - async_std::task::block_on::block_on,tuple$<> > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\async-std-1.10.0\src\task\block_on.rs:33 + 50: 0x7ff6d40c12ae - async_std::main + at C:\Users\ericholk\repo\backtrace-examples\async-std\src\main.rs:2 + 51: 0x7ff6d40c4b5b - core::ops::function::FnOnce::call_once > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\ops\function.rs:227 + 52: 0x7ff6d40c736b - std::sys_common::backtrace::__rust_begin_short_backtrace > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\sys_common\backtrace.rs:125 + 53: 0x7ff6d40c45f1 - std::rt::lang_start::closure$0 > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\rt.rs:63 + 54: 0x7ff6d4165f16 - core::ops::function::impls::impl$2::call_once + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\ops\function.rs:259 + 55: 0x7ff6d4165f16 - std::panicking::try::do_call + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:403 + 56: 0x7ff6d4165f16 - std::panicking::try + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:367 + 57: 0x7ff6d4165f16 - std::panic::catch_unwind + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panic.rs:129 + 58: 0x7ff6d4165f16 - std::rt::lang_start_internal::closure$2 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\rt.rs:45 + 59: 0x7ff6d4165f16 - std::panicking::try::do_call + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:403 + 60: 0x7ff6d4165f16 - std::panicking::try + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:367 + 61: 0x7ff6d4165f16 - std::panic::catch_unwind + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panic.rs:129 + 62: 0x7ff6d4165f16 - std::rt::lang_start_internal + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\rt.rs:45 + 63: 0x7ff6d40c45bf - std::rt::lang_start > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\rt.rs:62 + 64: 0x7ff6d40c12d6 - main + 65: 0x7ff6d417ad1c - invoke_main + at d:\a01\_work\6\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78 + 66: 0x7ff6d417ad1c - __scrt_common_main_seh + at d:\a01\_work\6\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288 + 67: 0x7ffbe0a26ab0 - BaseThreadInitThunk + 68: 0x7ffbe1771dbb - RtlUserThreadStart +error: process didn't exit successfully: `target\debug\async-std.exe` (exit code: 101) +``` + +
+ +## Sync Stack Trace Trimming + +Rust supports both a short and full backtraces, controlled by either `RUST_BACKTRACE=1` or `RUST_BACKTRACE=full`. The differents is that short backtraces (`RUST_BACKTRACE=1`) trims away some of the early and late frames. + +Below is an example of a short backtrace from a simple program where `main` calls `foo` which calls `bar` which calls `baz` which panics. + +``` +thread 'main' panicked at 'explicit panic', src\main.rs:14:5 +stack backtrace: + 0: std::panicking::begin_panic_handler + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:517 + 1: core::panicking::panic_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:101 + 2: core::panicking::panic + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:50 + 3: sync::baz + at .\src\main.rs:14 + 4: sync::bar + at .\src\main.rs:10 + 5: sync::foo + at .\src\main.rs:6 + 6: sync::main + at .\src\main.rs:2 + 7: core::ops::function::FnOnce::call_once > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\ops\function.rs:227 +note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. +``` + +Below is the same thing with `RUST_BACKTRACE=full`. + +
Full Backtrace + +``` +thread 'main' panicked at 'explicit panic', src\main.rs:14:5 +stack backtrace: + 0: 0x7ff6aef16b6e - std::backtrace_rs::backtrace::dbghelp::trace + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\..\..\backtrace\src\backtrace\dbghelp.rs:98 + 1: 0x7ff6aef16b6e - std::backtrace_rs::backtrace::trace_unsynchronized + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\..\..\backtrace\src\backtrace\mod.rs:66 + 2: 0x7ff6aef16b6e - std::sys_common::backtrace::_print_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:67 + 3: 0x7ff6aef16b6e - std::sys_common::backtrace::_print::impl$0::fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:46 + 4: 0x7ff6aef250ea - core::fmt::write + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\fmt\mod.rs:1150 + 5: 0x7ff6aef14e18 - std::io::Write::write_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\io\mod.rs:1667 + 6: 0x7ff6aef18d86 - std::sys_common::backtrace::_print + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:49 + 7: 0x7ff6aef18d86 - std::sys_common::backtrace::print + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:36 + 8: 0x7ff6aef18d86 - std::panicking::default_hook::closure$1 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:210 + 9: 0x7ff6aef18874 - std::panicking::default_hook + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:227 + 10: 0x7ff6aef193e5 - std::panicking::rust_panic_with_hook + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:624 + 11: 0x7ff6aef18f9f - std::panicking::begin_panic_handler::closure$0 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:519 + 12: 0x7ff6aef174b7 - std::sys_common::backtrace::__rust_end_short_backtrace + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:141 + 13: 0x7ff6aef18f29 - std::panicking::begin_panic_handler + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:517 + 14: 0x7ff6aef29940 - core::panicking::panic_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:101 + 15: 0x7ff6aef2988c - core::panicking::panic + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:50 + 16: 0x7ff6aef1122c - sync::baz + at C:\Users\ericholk\repo\backtrace-examples\sync\src\main.rs:14 + 17: 0x7ff6aef11209 - sync::bar + at C:\Users\ericholk\repo\backtrace-examples\sync\src\main.rs:10 + 18: 0x7ff6aef111f9 - sync::foo + at C:\Users\ericholk\repo\backtrace-examples\sync\src\main.rs:6 + 19: 0x7ff6aef111e9 - sync::main + at C:\Users\ericholk\repo\backtrace-examples\sync\src\main.rs:2 + 20: 0x7ff6aef1107b - core::ops::function::FnOnce::call_once > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\ops\function.rs:227 + 21: 0x7ff6aef1116b - std::sys_common::backtrace::__rust_begin_short_backtrace > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\sys_common\backtrace.rs:125 + 22: 0x7ff6aef11101 - std::rt::lang_start::closure$0 > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\rt.rs:63 + 23: 0x7ff6aef19836 - core::ops::function::impls::impl$2::call_once + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\ops\function.rs:259 + 24: 0x7ff6aef19836 - std::panicking::try::do_call + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:403 + 25: 0x7ff6aef19836 - std::panicking::try + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:367 + 26: 0x7ff6aef19836 - std::panic::catch_unwind + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panic.rs:129 + 27: 0x7ff6aef19836 - std::rt::lang_start_internal::closure$2 + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\rt.rs:45 + 28: 0x7ff6aef19836 - std::panicking::try::do_call + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:403 + 29: 0x7ff6aef19836 - std::panicking::try + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:367 + 30: 0x7ff6aef19836 - std::panic::catch_unwind + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panic.rs:129 + 31: 0x7ff6aef19836 - std::rt::lang_start_internal + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\rt.rs:45 + 32: 0x7ff6aef110cf - std::rt::lang_start > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\rt.rs:62 + 33: 0x7ff6aef11246 - main + 34: 0x7ff6aef286e4 - invoke_main + at d:\a01\_work\6\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78 + 35: 0x7ff6aef286e4 - __scrt_common_main_seh + at d:\a01\_work\6\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288 + 36: 0x7ffbe0a26ab0 - BaseThreadInitThunk + 37: 0x7ffbe1771dbb - RtlUserThreadStart +``` + +
+ +The full backtrace is much longer and includes many frames related to process startup and panic handling that the programmer is not likely to care about. + +The mechanism for trimming back traces is apparent within the full backtrace. There are two functions, `__rust_begin_short_backtrace` and `__rust_end_short_backtrace`. These are set up so that they are never inlined. Then, the short printing routine simply ignores any frames that are not within these two calls. + +## Problem Analysis + +The main issue with async backtraces now is that they leak a number of implementation details from the async runtime. +To some extent this is true for sync backtraces as well. +For example, in the sync full backtrace there are 15 frames just related panicking after the last user frame (frame 16, `sync::baz`). +In the sync case, it is pretty easy to filter out the startup frames and the panic frames using `__rust_begin_short_backtrace` and `__rust_end_short_backtrace`. +This approach does not work as well for async code as-is because many of the internal details from the runtime are interspersed between user code frames. + +For example, let's consider the short tokio backtrace to see what additional frames we'd want to remove. +At the bottom of the stack trace, we have 13 frames related to tokio startup: + +``` +10: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 +11: tokio::park::thread::impl$5::block_on::closure$0 > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\park\thread.rs:263 +12: tokio::coop::with_budget::closure$0 > >,tokio::park::thread::impl$5::block_on::closure$0> + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\coop.rs:106 +13: std::thread::local::LocalKey >::try_with,tokio::coop::with_budget::closure$0,enum$ > > > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:399 +14: std::thread::local::LocalKey >::with,tokio::coop::with_budget::closure$0,enum$ > > > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\std\src\thread\local.rs:375 +15: tokio::coop::with_budget + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\coop.rs:99 +16: tokio::coop::budget + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\coop.rs:76 +17: tokio::park::thread::CachedParkThread::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\park\thread.rs:263 +18: tokio::runtime::enter::Enter::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\runtime\enter.rs:151 +19: tokio::runtime::thread_pool::ThreadPool::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\runtime\thread_pool\mod.rs:77 +20: tokio::runtime::Runtime::block_on > + at C:\Users\ericholk\.cargo\registry\src\github.amrom.workers.dev-1ecc6299db9ec823\tokio-1.13.0\src\runtime\mod.rs:463 +21: async_tokio::main + at .\src\main.rs:4 +22: core::ops::function::FnOnce::call_once > + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\ops\function.rs:227 +``` + +If we remove these frames, we have something pretty close to the synchronous short backtrace: + +``` + 0: std::panicking::begin_panic_handler + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:517 + 1: core::panicking::panic_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:101 + 2: core::panicking::panic + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:50 + 3: common::baz::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10 + 4: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 5: common::bar::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:6 + 6: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 7: common::foo::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:2 + 8: core::future::from_generator::impl$1::poll + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\library\core\src\future\mod.rs:80 + 9: async_tokio::main::generator$0 + at .\src\main.rs:4 +``` + +To achieve parity with the synchronous backtrace, there are two more improvements needed. + +The first is that in between each frame in user code, there is a frame with a call to `core::future::from_generator`. + +The second is that rather than seeing a call to `common::foo` or similar, we see `common::foo::generator$0`. + +These two are relatively minor issues. +Fixing them may not be desirable. + +## Proposed Solutions + +There are several improvements we could make that would improve the state of backtraces. + +### Allow runtimes to trim startup code + +This solution probably has the highest impact to effort ratio. +The core of the idea is to move the call to `__rust_begin_short_backtrace` when running under an async executor. +Most likely the way this would happen is to have an alternate startup path that programs can opt in to (how to actually make this work is left as an exercise for the reader). +The alternate path would not call `__rust_begin_short_backtrace` in [`rt.rs`], but would instead expect the program to make sure to call it at the appropriate time. +Then, async runtimes that provide a macro such as `#[tokio::main]` or `#[async_std::main]` would arrange to start up through this alternate path and call `__rust_begin_short_backtrace` shortly before invoking user code. + +[`rt.rs`]: https://github.com/rust-lang/rust/blob/master/library/std/src/rt.rs + +Note that this approach does not help in cases where users create and launch the runtime manually rather than using a library-provided macro. +It does help in the most common cases, however, and when users take a more manual approach they would also be able to control where the short stack trace starts. + +### Allow trimming of internal frames + +There are several ways to do this, with varying levels of implementation effort and cost. + +As to what this would look like for library authors, ideally we would have something like a `#[backtrace_transparent]` attribute that is applied to a function and indicates that the function should be hidden from backtraces by default. + +There are several ways we could do the underlying implementation, which are discussed below. + +The conceptually simplest is to allow multiple `__rust_begin_short_backtrace`/`__rust_end_short_backtrace` pairs. +Implementing this could be done almost entirely with changes to [`_print_fmt`]. +This approach has some serious drawbacks. +First, it requires a lot of work from library authors to annotate each transition point between user and library code. +Second, `__rust_begin_short_backtrace`/`__rust_end_short_backtrace` are built to defeat inlining to ensure they show up as a frame on the stack. +This is fine when the functions are only called during process startup and once when a panic starts, but it would likely be prohibitively expensive if interspersed between every async function call. + +[`_print_fmt`]: https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/backtrace.rs#L52 + +It might be possible instead to include more information in the debugging symbols. +For example, we might be able to add a flag indicating that a certain function should be hidden from the backtrace. +To do this, we would first need to make sure existing debugging formats such as DWARF and PDB are able to encode such information. +If there is already support for this, then it's likely debuggers would benefit as well since they would also be able to display trimmed backtraces. +It is worth noting that this solution would not help much for builds without debug symbols. + +A third option is to use some kind of name based heuristics. +For example, by default we may want to only show frames in the root crate, although this may be too restrictive for large projects. +Probably the best approach here is to extend the set of options allowed for the `RUST_BACKTRACE` environment variable to make it behave more like `RUST_LOG`. +We could allow options such as `RUST_BACKTRACE="short,exclude=tokio::*"` to hide all frames from the `tokio` crate, or `RUST_BACKTRACE="short,include=my_crate::*"` to only show frames from `my_crate`. +These ideas are being prototyped in the [better-backtrace] repo. + +This third option could also be implemented mostly through changes to [`_print_fmt`]. +It also gives a great deal of control. +Libraries or projects could provide suggested backtrace filters in their documentation, and programmers can refine these as necessary depending on their needs. + +This functionality would be helpful in other contexts as well. +For example, iterator-heavy code tends to have stack traces that interleave user code with internal library implementation details. + +One question is whether we want to communicate to the user that frames were omitted. +There are a couple ways we might do this. +For example, we could add a `...` indicating frames are missing: + +``` + 0: std::panicking::begin_panic_handler + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:517 + 1: core::panicking::panic_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:101 + 2: core::panicking::panic + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:50 + 3: common::baz::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10 + ... + 4: common::bar::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:6 + ... + 5: common::foo::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:2 + ... + 6: async_tokio::main::generator$0 + at .\src\main.rs:4 +``` + +Or, we could simply omit the frames and have non-consecutive frame numbers. + +``` + 0: std::panicking::begin_panic_handler + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:517 + 1: core::panicking::panic_fmt + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:101 + 2: core::panicking::panic + at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\panicking.rs:50 + 3: common::baz::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10 + 5: common::bar::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:6 + 7: common::foo::generator$0 + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:2 + 9: async_tokio::main::generator$0 + at .\src\main.rs:4 +``` + +In an interactive context, such as a debugger, the `...` approach is probably best, since it could also provide an option to expand that section and see the frames that are missing. +In non-interactive cases, such as printing a backtrace with some `RUST_BACKTRACE` setting, it may be better to omit the frame numbers that were skipped since that leads to a slightly more compact backtrace. + +### Generate more informative symbol names + +Note that a similar renaming to what is proposed here was implemented in [#92873]. +New approaches to formatting based on these symbols are being explored in the [better-backtrace] repo. + +Currently `async` functions result in a function name like `foo::generator$0`. +This can be confusing to users, since it leaks implementation details about `async` functions. +We probably have a lot of flexibility in the generated names, so `rustc` could encode more information about where this symbol came from. +Backtrace printers could then decode these names to give the information to the user in a meaningful way. + +Here is an example of what this might look like in practice: +``` +panicked at 'explicit panic', C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10:5 +Backtrace: + 0 [12]: async fn common::baz + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:10 + 1 [14]: async fn common::bar + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:6 + 2 [16]: async fn common::foo + at C:\Users\ericholk\repo\backtrace-examples\async-common\src\lib.rs:2 + 3 [18]: async fn async_tokio::main + at C:\Users\ericholk\repo\backtrace-examples\async-tokio\src\main.rs:5 + 4 [30]: fn async_tokio::main + at C:\Users\ericholk\repo\backtrace-examples\async-tokio\src\main.rs:5 +``` + +Here we have done aggressive filtering on the backtrace to remove runtime-internal frames. +The raw frame numbers are still listed in brackets, however, so the user can see if frames have been hidden. +The function names are now printed as either `fn foo` or `async fn bar` to indicate what kind of function was called. + +We could support a couple of suffixes on generated functions, such as: +- `foo::generator$0` - current suffix, but would be used only for generator blocks +- `foo::async_fn$` - indicates that this is the body of an `async fn` +- `foo::async$0` - the function came from an `async {}` block within another function[^async-block] +- `foo::generator_fn$` - analogous to `foo::async_fn$` for when generator functions are supported +For the block suffixes, we could potentially encode the line number or the block or some other way of identifying multiple blocks that would be useful to the user. + +[^async-block]: How we want stack traces involving async blocks to look is still an open question. + +## References + +* [Beautiful tracebacks in Trio v0.7.0 (Python)](https://vorpus.org/blog/beautiful-tracebacks-in-trio-v070/) +* [Faster async functions and Promises (JavaScript)](https://v8.dev/blog/fast-async#improved-developer-experience) +* [Zero-cost async stack traces (JavaScript)](https://docs.google.com/document/d/13Sy_kBIJGP0XT34V1CV3nkWya4TwYx9L3Yv45LdGB6Q/edit#heading=h.e6lcalo0cl47) +* Async stack traces in folly [[1][folly-1]] [[2][folly-2]] [[3][folly-3]] [[4][folly-4]] [[5][folly-5]] + +[#92873]: https://github.com/rust-lang/rust/pull/92873 +[better-backtrace]: https://github.com/eholk/better-backtrace +[folly-1]: https://developers.facebook.com/blog/post/2021/09/16/async-stack-traces-folly-Introduction/ +[folly-2]: https://developers.facebook.com/blog/post/2021/09/23/async-stack-traces-folly-synchronous-asynchronous-stack-traces/ +[folly-3]: https://developers.facebook.com/blog/post/2021/09/30/async-stack-traces-folly-forming-async-stack-individual-frames/ +[folly-4]: https://developers.facebook.com/blog/post/2021/10/14/async-stack-traces-c-plus-plus-coroutines-folly-walking-async-stack/ +[folly-5]: https://developers.facebook.com/blog/post/2021/10/21/async-stack-traces-folly-improving-debugging-developer-lifecycle/ diff --git a/src/design_docs/completion_based_futures.md b/src/design_docs/completion_based_futures.md index d1cacd13..028dc22c 100644 --- a/src/design_docs/completion_based_futures.md +++ b/src/design_docs/completion_based_futures.md @@ -1,5 +1 @@ # ⏳ Completion-based futures - -[Notes on io_uring][withoutboats-blog] - -[withoutboats-blog]: https://boats.gitlab.io/blog/post/io-uring diff --git a/src/design_docs/generator_syntax.md b/src/design_docs/generator_syntax.md index ec2acfb0..3eebeebf 100644 --- a/src/design_docs/generator_syntax.md +++ b/src/design_docs/generator_syntax.md @@ -1,10 +1 @@ -# ⚡ Generator syntax - -* It would be useful to be able to write a function to return an iterator or (in the async context) a generator -* The basic shape might be (modulo bikeshedding) `gen fn` that contains `yield` -* Some question marks: - * How general of a mechanism do we want? - * Just target iterators and streams, or shoot for something more general? -* Some of the question marks that arise if you go beyond iterators and streams: - * Return values that are not unit - * Have yield return a value that is passed by the caller of `next` ("resume args") \ No newline at end of file +# ⚡ Generator syntax diff --git a/src/design_docs/mutex.md b/src/design_docs/mutex.md index bcc49dae..78e08305 100644 --- a/src/design_docs/mutex.md +++ b/src/design_docs/mutex.md @@ -1,7 +1 @@ # 🔒 Mutex (future-aware) - -[Description of various challenges with async mutexes][blog] - -[blog]: https://github.com/Diggsey/posts/tree/master/async-mutexes - - diff --git a/src/design_docs/stream.md b/src/design_docs/stream.md index 94878374..4698ca90 100644 --- a/src/design_docs/stream.md +++ b/src/design_docs/stream.md @@ -1,48 +1 @@ # ☔ Stream trait - -* [Current definition](https://docs.rs/futures/0.3/futures/stream/trait.Stream.html) - -## Trait definition - -```rust,ignore -pub trait Stream { - type Item; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>; - - #[inline] - fn size_hint(&self) -> (usize, Option) { - (0, None) - } -} -``` - -## Concerns - -### Poll-based design - -* You have to think about Pin if you implement this trait. -* Combinators can be more difficult. -* One solution: [generator syntax](./generator_syntax.md). - -### Attached streams are commonly desired - -Sometimes streams need to reuse internal storage ([Discussion]). - -[Discussion]: http://smallcultfollowing.com/babysteps/blog/2019/12/10/async-interview-2-cramertj-part-2/#the-need-for-streaming-streams-and-iterators - -### Combinators - -* Currently the combinations are stored in the [`StreamExt`] module. -* In some cases, this is because of the lack of async closures support. - * Also serves as a "semver barrier". - * Also no-std compatibility. -* One question: what combinators (if any) to include when stabilizing? - * e.g., [`poll_next_unpin`] can make working with pin easier, albeit at a loss of generality - * folks who are new to pinning could use this method, and it can help us to guide the diagnostics by suggesting that they `Box::pin` - -[`StreamExt`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html -[`poll_next_unpin`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.poll_next_unpin diff --git a/src/design_docs/yield_safe.md b/src/design_docs/yield_safe.md index 16b6cab9..ac9c0b1f 100644 --- a/src/design_docs/yield_safe.md +++ b/src/design_docs/yield_safe.md @@ -1,28 +1 @@ # ⚠️ Yield-safe lint - -## Use-case - -Some types should not be held across a "yield" bound. A typical example is a `MutexGuard`: - -```rust,ignore -async fn example(x: &Lock) { - let data = x.lock().unwrap(); - something().await; - *data += 1; -} - -async fn something() { } -``` - -In practice, a lot of these issues are avoided because `MutexGuard` is not `Send`, but single-thread runtimes hit these issues. - -## Types where this would apply - -* `MutexGuard` for mutexes, read-write locks -* Guards for ref-cells -* Things that might use these types internally and wish to bubble it up - -## Precedent and related questions - -* The `#[must_use]` lint on types, we would want their design to work very closely. -* Non-async-friendly functions like `sleep` or `task::block_on`. \ No newline at end of file diff --git a/src/glossary.md b/src/glossary.md new file mode 100644 index 00000000..8bba1e1c --- /dev/null +++ b/src/glossary.md @@ -0,0 +1,20 @@ +# Glossary + +If you follow discussions in the async ecosystem you're likely to find acronyms being tossed around, many of which refer to language features that are in-progress. +What follows is a best-effort list of those acronyms and links to resources discussing the language features. + +## AFIT - async fn in trait +- [RFC #3185](https://github.com/rust-lang/rfcs/pull/3185) +- Allows static dispatch of `async fn`s in traits + +## RPITIT - return position `impl trait` in traits +- [RFC #3425](https://github.com/rust-lang/rfcs/pull/3425) +- Allows `impl trait` as a return type in trait definitions + +## RTN - return type notation +- [Tracking Issue: rust#109417](https://github.com/rust-lang/rust/issues/109417) +- Experimental, pre-RFC feature providing bounds for return types on `async fn`s + +## TAIT - type alias `impl trait` +- [Tracking Issue: rust#63063](https://github.com/rust-lang/rust/issues/63063) +- Allows the ability to write `type Foo = impl Bar` diff --git a/src/meetings.md b/src/meetings.md new file mode 100644 index 00000000..b381bb0c --- /dev/null +++ b/src/meetings.md @@ -0,0 +1,261 @@ +# Meetings + +We have weekly meetings with a rotating agenda for each one. +Once a month (aspirationally on the first Thursday of the month) we have a sprint planning meeting. +The other weeks are used for [reading club], deep dives, or whatever else we have a need for. +Additionally, we have a [triage meeting] on every other Monday. + +Meetings are held either on Zulip or one of the many videoconferencing systems. +For video meetings, we will announce each of them on the [#wg-async] Zulip stream when they are starting. + +All are welcome to attend any meeting! + +See also: + +* [Reading club notes][reading club] +* [Sprint planning notes](https://hackmd.io/gPgXC4fsTZOgOnd-Bwhoag?view) + +## Meeting schedule + +Our weekly meetings are held at [10:00 PT]. + +See [wg-async discussion schedule](https://github.com/orgs/rust-lang/projects/40/views/1) for recent and upcoming meetings. + +## Earlier meetings + +### May 2024 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2024-05-02 | [10:00 PT] | – | [Notes](https://hackmd.io/gqt-5xxwRjmrqJPJF61ELg) | +| 2024-05-09 | [10:00 PT] | – | [Notes](https://hackmd.io/RblquL8zQdqscplFTKFGqw) | + +### April 2024 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2024-04-04 | [10:00 PT] | – | [Notes](https://hackmd.io/kvSrfk3OS_iUoWZ7bILYNg) | +| 2024-04-11 | [10:00 PT] | – | [Notes](https://hackmd.io/bYaPiCdqR3WIyjKhFWzdtQ) | +| 2024-04-18 | [09:00 PT] | – | [Notes](https://hackmd.io/Ed0DtZ2KRGuBa0wCjNet9g) | +| 2024-04-25 | [09:00 PT] | – | [Notes](https://hackmd.io/1FasSjY4T_a78hRAsQjO-Q) | + +### March 2024 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2024-03-07 | [09:00 PT] | – | ? | +| 2024-03-14 | [09:00 PT] | – | ? | +| 2024-03-21 | [09:00 PT] | – | [Notes](https://hackmd.io/vsF4W080SJ6H3PZjp9xlGA) | +| 2024-03-28 | [09:00 PT] | – | [Notes](https://hackmd.io/ZO-gVZxDRcabVp25ooVHTA) | + +### February 2024 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2024-02-01 | [09:00 PT] | – | [Notes](https://hackmd.io/y_KY5pB0Rqq3VNVBe2xG0A) | +| 2024-02-08 | [09:00 PT] | – | [Notes](https://hackmd.io/qsCxElt6SM-riz2pMdKBNA) | +| 2024-02-15 | [09:00 PT] | – | [Notes](https://hackmd.io/EplPcmBCTCSQ9LPOU6IZDw) | +| 2024-02-22 | [09:00 PT] | – | [Notes](https://hackmd.io/918bIfHvRXaoqR6p455Cww) | +| 2024-02-29 | [09:00 PT] | – | [Notes](https://hackmd.io/9o1X1degTECn9gjEvHS-MA) | + +### January 2024 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2024-01-04 | [09:00 PT] | – | [Notes](https://hackmd.io/gPgXC4fsTZOgOnd-Bwhoag) | +| 2024-01-11 | [09:00 PT] | – | [Notes](https://hackmd.io/YFrqx3zsTy-_x5UAgau5Nw) | +| 2024-01-18 | [09:00 PT] | – | [Notes](https://hackmd.io/HKBIGY5nSPuhyrcCe0TOHA) | +| 2024-01-25 | [09:00 PT] | – | [Notes](https://hackmd.io/ZDzG2sySTmm-DrAvI2j1Xw) | + +### December 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-12-07 | [09:00 PT] | – | [Notes](https://hackmd.io/4bnHpZRFQgCFdp9qpU8ANw) | +| 2023-12-14 | [09:00 PT] | – | [Notes](https://hackmd.io/ZcJ3PGpWSLymr65NeyBKWA) | +| 2023-12-21 | [09:00 PT] | – | ? | +| 2023-12-28 | [09:00 PT] | – | ? | + +### November 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-11-02 | [09:00 PT] | – | ? | +| 2023-11-09 | [09:00 PT] | – | [Notes](https://hackmd.io/P6WWnfHvSeeE-Kyp-pp33A) | +| 2023-11-16 | [09:00 PT] | – | [Notes](https://hackmd.io/TbDc4RWCSkGEketBWMLmfA) | +| 2023-11-23 | [09:00 PT] | – | ? | +| 2023-11-30 | [09:00 PT] | – | [Notes](https://hackmd.io/uFSuyncKTiGXaDcP8LbnUg) | + +### October 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-10-05 | [09:00 PT] | Sprint Planning | – | +| 2023-10-12 | [09:00 PT] | Reading Club | [The Async Iterator Trait](https://blog.yoshuawuyts.com/async-iterator-trait/) ([notes](https://hackmd.io/AWbdozcHSO20bzcRxYmQyQ?both=)) | +| 2023-10-19 | [09:00 PT] | Reading Club | [Writing a basic `async` debugger](https://cliffle.com/blog/lildb/) ([notes](https://hackmd.io/IAVYtQlzQayQXVlEobrcjw?both=)) | +| 2023-10-26 | [09:00 PT] | Open Discussion | [Notes](https://hackmd.io/pPSntAu7SCanKdyQAdlMig?both) | + +### November 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-11-02 | [09:00 PT] | Sprint Planning | – | + +### September 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-08-31 | [09:00 PT] | Sprint Planning | – | +| 2023-09-07 | [09:00 PT] | No meeting (work on sprint goals) | – | +| 2023-09-14 | [09:00 PT] | No meeting (cancelled for RustConf) | – | +| 2023-09-21 | [09:00 PT] | Reading Club | [A case for CancellationTokens](https://gist.github.com/Matthias247/354941ebcc4d2270d07ff0c6bf066c64) ([notes](https://hackmd.io/h27_RB3UTpK6V2ao3CntFg?both)) | +| 2023-09-28 | [09:00 PT] | Open Discussion | [Notes](https://hackmd.io/JWIp_2rJRgmfn5axJUbsVg?both) | + +### August 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-08-03 | [09:00 PT] | Sprint Planning | – | +| 2023-08-10 | [09:00 PT] | Deep Dive | 2024 Prelude + Future::map ([notes](https://hackmd.io/LfWcsTSYTPK5TuORJ4l22g?both)) | +| 2023-08-17 | [09:00 PT] | Deep Dive | IntoFuture autoderef + #42940 ([notes](https://hackmd.io/G6ULofyXSIS4CK9u-jwYRg?both)) | +| 2023-08-24 | [09:00 PT] | Open Discussion | [Notes](https://hackmd.io/x8gCTOykQfqoSyGGbZXPlg?both) | + +### July 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-07-06 | [09:00 PT] | Sprint Planning | – | +| 2023-07-13 | [09:00 PT] | Deep Dive | Should wg-async be a team? ([notes](https://hackmd.io/cXffl1cFQA2udGWZm5Nxbg?both)) | +| 2023-07-20 | [09:00 PT] | Reading Club | [Tree-Structured Concurrency](https://blog.yoshuawuyts.com/tree-structured-concurrency/) ([notes](https://hackmd.io/ptZt-YOwQk-CbQn6yMZgRQ?both)) | +| 2023-07-27 | [09:00 PT] | Open Discussion | [Notes](https://hackmd.io/pA-RT8t_SNOhff4SBzkP5Q?both) | + +### June 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-06-01 | [13:30 PT] | Sprint Planning | – | +| 2023-06-08 | [09:00 PT] | Reading Club | [Iterator, Generator](https://without.boats/blog/iterator-generator/) | +| 2023-06-15 | [13:30 PT] | Open Discussion | [Notes](https://hackmd.io/lxpR7bg7Q1isfgfGWW7J_w?both) | +| 2023-06-22 | [09:00 PT] | Open Discussion | [Notes](https://hackmd.io/lxpR7bg7Q1isfgfGWW7J_w?both) | +| 2023-06-29 | [13:30 PT] | Open Discussion | [Notes](https://hackmd.io/lxpR7bg7Q1isfgfGWW7J_w?both) | + +### May 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-05-04 | [11:00 PT] | Sprint Planning | – | +| 2023-05-11 | [09:00 PT] | Reading Club | [Linear types one-pager](https://blog.yoshuawuyts.com/linear-types-one-pager/) ([notes](https://hackmd.io/qxvBTutISVKzjCbaKF5BYQ?view)) | +| 2023-05-18 | [13:30 PT] | Open Discussion | [Notes](https://hackmd.io/B7VCpA4-Q_yTuv96uYa_Bw?both) | +| 2023-05-25 | [09:00 PT] | Open Discussion | [Notes](https://hackmd.io/B7VCpA4-Q_yTuv96uYa_Bw?both) | + +### April 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-04-06 | [11:00 PT] | Sprint Planning | – | +| 2023-04-13 | [09:00 PT] | Reading Club | [Patterns and abstractions](https://without.boats/blog/patterns-and-abstractions/) | +| 2023-04-20 | [13:30 PT] | Deep Dive | [AsyncIterator](https://hackmd.io/pfgy92PZQV-Zr_c00-aMnQ?both) | +| 2023-04-27 | [09:00 PT] | Open Discussion | Add your topics to [this doc](https://hackmd.io/p7q-egk-TRKissJetk2zRw) | + +### March 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-03-02 | [09:00 PT] | Sprint Planning | – | +| 2023-03-09 | [11:00 PT] | Reading Club | [Mental experiments with `io_uring`](https://vorner.github.io/2019/11/03/io-uring-mental-experiments.html) | +| 2022-03-13 | [08:30 PT] | Triage | – | +| 2023-03-16 | [09:00 PT] | Deep Dive | Async vision update | +| 2023-03-23 | [11:00 PT] | Open Discussion | Add your topics to [this doc](https://hackmd.io/ddpCJa44SXmhNhFrYLh1EA) | +| 2022-03-27 | [08:30 PT] | Triage | – | +| 2023-03-30 | [09:00 PT] | Deep Dive | AFIT case studies | + +### February 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-02-02 | [09:00 PT] | Sprint Planning | – | +| 2023-02-09 | [09:00 PT] | Reading Club | [Notes on structured concurrency, or: Go statement considered harmful](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/) | +| 2022-02-13 | [08:30 PT] | Triage | – +| 2023-02-16 | [09:00 PT] | Deep Dive | Send bounds and AFIT ([notes](https://hackmd.io/F4gXjoNMQwek5gVKWfmg4Q)) | +| 2023-02-23 | [09:00 PT] | Deep Dive | Async vision ([notes](https://hackmd.io/UOoD7w7MQ6CvoC66DdX_Rg?view)) | +| 2022-02-27 | [08:30 PT] | Triage | – + +### January 2023 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2023-01-12 | [09:00 PT] | Sprint Planning | – +| 2023-01-19 | [09:00 PT] | Deep Dive | [futures-concurrency](https://docs.rs/futures-concurrency/latest/futures_concurrency/) ([notes](https://hackmd.io/28MpaMEUT8S6yDX9tciApw?view)) +| 2023-01-26 | [09:00 PT] | Reading Club | [Context reactor hook](https://jblog.andbit.net/2022/12/28/context-reactor-hook/) ([notes](https://hackmd.io/8NCF0R-NTXmvRzx8selRVg?view)) + +### December 2022 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2022-12-01 | [09:00 PT] | Sprint Planning | – +| 2022-12-05 | [08:30 PT] | Triage | – +| 2022-12-08 | [09:00 PT] | Deep Dive | [Async main and test](https://hackmd.io/@vincenzopalazzo-rust/SJkBzhJOo) +| 2022-12-19 | [08:30 PT] | Triage | – + +### November 2022 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2022-11-03 | [09:00 PT] | (Canceled) | – +| 2022-11-07 | [08:30 PT] | Triage | – +| 2022-11-10 | [09:00 PT] | Sprint Planning | – +| 2022-11-17 | [09:00 PT] | Reading Club | [Trio: Async concurrency for mere mortals](https://www.youtube.com/watch?v=oLkfnc_UMcE) ([notes](https://hackmd.io/Lep6OZHFRXulsVFcgT7sdA?view)) +| 2022-11-21 | [08:30 PT] | Triage | – +| 2022-11-24 | [09:00 PT] | (Canceled) | – + + +### October 2022 + +| Date | Time | Meeting Type | Topic | Notes | +|------------|------------|-----------------|-------|-------| +| 2022-10-06 | [09:00 PT] | Sprint Planning | – +| 2022-10-10 | [08:30 PT] | Triage | – +| 2022-10-13 | [09:00 PT] | Reading Club | [How nextest uses tokio (and generally why async rust)](https://sunshowers.io/posts/nextest-and-tokio-1/) | [notes](https://hackmd.io/yYSKcetFSwuH-n8KAOYkxQ) +| 2022-10-20 | [09:00 PT] | Reading Club | [Language feature: in-place construction](https://y86-dev.github.io/blog/safe-pinned-initialization/in-place.html) | [notes](https://hackmd.io/XfeniruATrq-gY5qOQ32yg?view) +| 2022-10-24 | [08:30 PT] | Triage | – +| 2022-10-27 | [09:00 PT] | Reading Club | [RFC 3318, Field Projection](https://github.com/y86-dev/rfcs/blob/field-projection/text/3318-field-projection.md) | [notes](https://hackmd.io/79TICQb0SPWCIcz02IiWaA?view) + +### September 2022 + +| Date | Time | Meeting Type | Topic | +|------------|------------|-----------------|-------| +| 2022-09-01 | [09:00 PT] | Reading Club | [A look back at asynchronous Rust](https://tomaka.medium.com/a-look-back-at-asynchronous-rust-d54d63934a1c) ([notes](https://hackmd.io/RRVC9tDVQZSKgs9JNbo5LQ)) +| 2022-09-08 | [09:00 PT] | Sprint Planning | [Sprint](https://hackmd.io/gPgXC4fsTZOgOnd-Bwhoag?view#2022-09-08---2022-10-06) +| 2022-09-12 | [08:30 PT] | Triage | – +| 2022-09-15 | [09:00 PT] | Reading Club | [Futures concurrency 3](https://blog.yoshuawuyts.com/futures-concurrency-3/) ([notes](https://hackmd.io/8gvVE3yaTUGw7IjWqsbuwA)) +| 2022-09-22 | [09:00 PT] | Reading Club | [Async/await in Swift](https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md) ([notes](https://hackmd.io/DG4APdWoS6iYsTMgf_n_Kg)) +| 2022-09-26 | [08:30 PT] | Triage | – +| 2022-09-29 | [09:00 PT] | Deep Dive | Async I/O Traits ([notes](https://hackmd.io/VtzIRH2pTZGBJMln2xBEGQ)) + +### Even earlier + +| Reading Club Document | Date and Zulip thread or notes link | +| --- | --- | +| [Async cancellation 1](https://blog.yoshuawuyts.com/async-cancellation-1/) | [2022-03-07] | +| [Async overloading](https://blog.yoshuawuyts.com/async-overloading/) | [2022-03-21] +| [RPITIDT](https://rust-lang.github.io/async-fundamentals-initiative/explainer/async_fn_in_dyn_trait.html) | [2022-04-04] +| [Kotlin Coroutines Introduction](https://play.kotlinlang.org/hands-on/Introduction%20to%20Coroutines%20and%20Channels/06_StructuredConcurrency) | [2022-04-18] +| [dyn* doesn't need to be special](https://dev.to/cad97/dyn-doesnt-need-to-be-special-3ldm) | 2022-05-18 ([notes](https://hackmd.io/-Jqg46vmRCiwSNLXFsv70Q?view)) | +| [Async destructors, async genericity, and completion futures](https://sabrinajewson.org/blog/async-drop) | 2022-06-23 and 2022-06-30 (video) ([notes](https://hackmd.io/DBC2RvHXT_GQqdyRRI8geQ?view)) | +| [Safe Pin projections through view types](https://blog.yoshuawuyts.com/safe-pin-projections-through-view-types/) | 2022-07-28 (video) ([notes](https://hackmd.io/FPBnaaAHTYmO0QPWJY3dEw?both)) | +| [Panics vs cancellation, part 1](https://smallcultfollowing.com/babysteps//blog/2022/01/27/panics-vs-cancellation-part-1/) | 2022-08-18 [notes](https://hackmd.io/Ux6nxmMMRuat6v4O1ci1ng) | + +[2022-03-07]: https://zulip-archive.rust-lang.org/stream/187312-wg-async/topic/async.20reading.20club.202022-03-07.html +[2022-03-21]: https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/async.20reading.20club.202022-03-07 +[2022-04-04]: https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/async.20reading.20club.202022-04-04 +[2022-04-18]: https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/async.20reading.20club.202022-04-18 + + +[reading club]: https://hackmd.io/6kSbmyggT6eAy5uvdB6srA?both +[triage meeting]: ./triage.md +[08:30 PT]: https://dateful.com/time-zone-converter?t=0830&tz2=PST-PDT-Pacific-Time +[09:00 PT]: https://dateful.com/time-zone-converter?t=09&tz2=PST-PDT-Pacific-Time +[10:00 PT]: https://dateful.com/time-zone-converter?t=10&tz2=PST-PDT-Pacific-Time +[11:00 PT]: https://dateful.com/time-zone-converter?t=11&tz2=PST-PDT-Pacific-Time +[13:30 PT]: https://dateful.com/time-zone-converter?t=1330&tz2=PST-PDT-Pacific-Time +[#wg-async]: https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async diff --git a/src/triage.md b/src/triage.md index b1a9c9bb..c1fde44b 100644 --- a/src/triage.md +++ b/src/triage.md @@ -2,8 +2,11 @@ ## When, where -The weekly triage meeting is held on [Zulip] at 13:00 US Eastern time on Fridays ([google calendar event for meeting](https://calendar.google.com/event?action=TEMPLATE&tmeid=M2VhYjRjczZxanE5ODcwbzR1bnZsNTV0MGFfMjAyMTAyMjZUMTgwMDAwWiA2dTVycnRjZTZscnR2MDdwZmkzZGFtZ2p1c0Bn&tmsrc=6u5rrtce6lrtv07pfi3damgjus%40group.calendar.google.com&scp=ALL)). +The weekly triage meeting is held on [Zulip] at [11:30 US Eastern time on every other Monday][everytimezone]. +For the date of the next triage meeting, see the [meetings page]. +[meetings page]: ./meetings.md +[everytimezone]: https://everytimezone.com/s/c3abbec9 [Zulip]: ./welcome.md#zulip ## So you want to fix a bug? @@ -25,7 +28,7 @@ It is used to drive the triage process. ## Triage process -In our weekly triage meetings, we take new issues assigned [`A-async-await`] and categorize them. +In our weekly triage meetings, we take new issues assigned [`A-async-await`] and categorize them. The process is: - Review the [project board], from right to left: @@ -44,16 +47,15 @@ The process is: If an issue is a good candidate for mentoring, mark `E-needs-mentor` and try to find a mentor. -Mentors assigned to issues should write up mentoring instructions. -**Often, this is just a couple lines pointing to the relevant code.** +Mentors assigned to issues should write up mentoring instructions. +**Often, this is just a couple lines pointing to the relevant code.** Mentorship doesn't require intimate knowledge of the compiler, just some familiarity and a willingness to look around for the right code. -After writing instructions, mentors should un-assign themselves, add `E-mentor`, and remove `E-needs-mentor`. -On the project board, if a mentor is assigned to an issue, it should go to the **Claimed** column until mentoring instructions are provided. +After writing instructions, mentors should un-assign themselves, add `E-mentor`, and remove `E-needs-mentor`. +On the project board, if a mentor is assigned to an issue, it should go to the **Claimed** column until mentoring instructions are provided. After that, it should go to **To do** until someone has volunteered to work on it. [`A-async-await`]: https://github.com/rust-lang/rust/labels/A-async-await [uncategorized issues]: https://github.com/search?q=org%3Arust-lang+is%3Aissue+label%3AA-async-await+is%3Aopen+-label%3AAsyncAwait-Triaged&type=Issues -[`P-high`]: https://github.com/search?q=org%3Arust-lang+is%3Aissue+label%3AAsyncAwait-Triaged+label%3AP-high+is%3Aopen&type=Issues [`P-medium`]: https://github.com/search?q=org%3Arust-lang+is%3Aissue+label%3AAsyncAwait-Triaged+label%3AP-medium+is%3Aopen&type=Issues [`P-low`]: https://github.com/search?q=org%3Arust-lang+is%3Aissue+label%3AAsyncAwait-Triaged+label%3AP-low+is%3Aopen&type=Issues diff --git a/src/vision.md b/src/vision.md index bcf88491..48a8ce7f 100644 --- a/src/vision.md +++ b/src/vision.md @@ -17,6 +17,10 @@ We believe Rust can become one of the most popular choices for building distribu This document is a collaborative effort to build a shared vision for Async Rust. **Our goal is to engage the entire community in a collective act of the imagination:** how can we make the end-to-end experience of using Async I/O not only a pragmatic choice, but a *joyful* one? +## 🚧 Under construction! Help needed! 🚧 + +The first version of this document is not yet complete, but it's getting very close! We are in the process of finalizing the set of ["status quo"](./vision/status_quo.md) and ["shiny future"](./vision/shiny_future.md) stories and the details of the [proposed roadmap](./vision/roadmap.md). The current content however is believed to be relatively final, at this point we are elaborating and improving it. + ## Where we are and where we are going The "vision document" starts with a [cast of characters][cc]. Each character is tied to a particular Rust value (e.g., performance, productivity, etc) determined by their background; this background also informs the expectations they bring when using Rust. [Grace], for example, wants to keep the same level of performance she currently get with C, but with the productivity benefits of memory safety. [Alan], meanwhile, is hoping Rust will give him higher performance without losing the safety and ergonomics that he enjoys with garbage collected languages. @@ -32,7 +36,3 @@ The vision is not just idle speculation. It is the central document that we use ## Involving the whole community The async vision document provides a forum where the Async Rust community can plan a great overall experience for Async Rust users. Async Rust was intentionally designed not to have a "one size fits all" mindset, and we don't want to change that. Our goal is to build a shared vision for the end-to-end experience while retaining the loosely coupled, exploration-oriented ecosystem we have built. - -## 🚧 Under construction! Help needed! 🚧 - -This document is not yet complete! We are actively working on it as part of the working group, and we would like your help! Check out the [How to vision doc](./vision/how_to_vision.md) page for more details. diff --git a/src/vision/awards.md b/src/vision/awards.md deleted file mode 100644 index afb7fc62..00000000 --- a/src/vision/awards.md +++ /dev/null @@ -1 +0,0 @@ -# Awards for "status quo" and "shiny future" diff --git a/src/vision/characters/alan.md b/src/vision/characters/alan.md index f1ccb793..00009257 100644 --- a/src/vision/characters/alan.md +++ b/src/vision/characters/alan.md @@ -14,6 +14,10 @@ Alan works at a Java shop. They run a number of network services built in Java, Alan is developing networking programs in Kotlin. He loves Kotlin for its expressive syntax and clean integration with Java. Still, he sometimes encounters problems running his services due to garbage collection latencies or overall memory usage. He's heard that Rust can be fun to use too, and is curious to try it out. +### Variant D: Go + +Alan develops a distributed database in Go, enjoying its simplicity and first-class treatment of concurrency. He's successfully built a transactional database that handles over 100K QPS. Intrigued by Rust's promise of "fearless concurrency", Alan tries Rust for more efficient use of memory and CPU. He's curious what classes of errors Rust async prevents and how Rust guarantees its safety without sacrificing the speed. + ## 🤔 Frequently Asked Questions ### What does Alan want most from Async Rust? diff --git a/src/vision/how_it_feels.md b/src/vision/how_it_feels.md new file mode 100644 index 00000000..f27a02af --- /dev/null +++ b/src/vision/how_it_feels.md @@ -0,0 +1,35 @@ +# How using async Rust ought to feel (and why it doesn't today) + +This section is, in many ways, the most important. It aims to identify the way it should feel to use Async Rust. + +## Consistent: "just add async/await" + +Async Rust should be a small delta atop Sync Rust. People who are familiar with sync Rust should be able to leverage what they know to make adopting Async Rust straightforward. Porting a sync code base to async should be relatively smooth: just add async/await, adopt the async variants of the various libraries, and you're done. + +## Reliable: "if it compiles, it works" + +One of the great things about Rust is the feeling of "if it compiles, it works". This is what allows you to do a giant refactoring and find that the code runs on the first try. It is what lets you deploy code that uses parallelism or other fancy features without exhausting fuzz testing and worry about every possible corner case. + +## Empowering: "complex stuff feels easy" + +Rust's great strength is taking formerly complex, wizard-like things and making them easy to do. In the case of async, that means letting people use the latest and greatest stuff, like io-uring. It also means enabling parallelism and complex scheduling patterns very easily. + +## Performant: "ran well right out of the box" + +Rust code tends to perform "quite well" right out of the box. You don't have to give up the "nice things" in the language, like closures or high-level APIs, in order to get good performance and tight memory usage. In fact, those high-level APIs often perform as well or better than what you would get if you wrote the code yourself. + +## Productive: "great crates for every need, just mix and match" + +Being able to leverage a large ecosystem of top-notch crates is a key part of what makes Rust (and most any modern language) productive. When using async Rust, you should be able to search crates.io and find crates that cover all kinds of things you might want to do. You should be able to add those crates to your `Cargo.toml` and readily connect them to one another without surprising hiccups. + +## Transparent and tunable: "it's easy to diagnose deadlocks and performance bottlenecks" + +Using Rust means most things work and perform well by default, but of course it can't prevent all problems. When you do find bugs, you need to be able to easily track what happened and figure out how to fix it. When your performance is subpar, you need to be able to peek under the covers and understand what's going on so that you can tune it up. In synchronous Rust, this means integrating with but also improving on existing tooling like debuggers and profilers. In asynchronous Rust, though, there's an extra hurdle, because the terms that users are thinking in (asynchronous tasks etc) exist within the runtime, but are not the same terms that synchronous debuggers and profilers expose. There is a need for more customized tooling to help users debug problems without having to map between the async concept and the underlying implementation. + +## Control: "I can do all the weird things" + +Part of what's great about Rust is that it lets you get into explore all the corner cases. Want to target the kernel? Develop embedded systems using async networking without any operating system? Run on WebAssembly? No problem, we can do that. + +## Interoperable: "integrating with C++, node.js, etc is easy" + +Much like C, Rust aims to be a "lingua franca", something you can integrate into your existing systems on a piecemeal basis. In synchronous Rust, this means that functions can "speak" the C ABI and Rust structures can be compiled with C-compatible layouts, and that we use native system functionality like the default memory allocator or the native threading APIs. In _asynchronous_ Rust, it means that we are able to integrate into other systems, like C++ futures, Grand Central Dispatch, or JavaScript promises. diff --git a/src/vision/how_to_comment.md b/src/vision/how_to_comment.md deleted file mode 100644 index 29ee7277..00000000 --- a/src/vision/how_to_comment.md +++ /dev/null @@ -1 +0,0 @@ -# How to comment diff --git a/src/vision/how_to_vision.md b/src/vision/how_to_vision.md index 21af8641..07a0b830 100644 --- a/src/vision/how_to_vision.md +++ b/src/vision/how_to_vision.md @@ -4,57 +4,50 @@ | When | What | | --- | --- | -| ✅ **Now** till 2021-04-30 | Improve the [sample projects][hvp] | -| ✅ **Now** till 2021-04-30 | Propose new ["status quo" stories][hvsq] or [comment] on existing PRs | -| 🛑 Starting 2021-04-02 | Propose new ["shiny future" stories][hvsf] or [comment] on existing PRs | -| 🛑 Starting 2021-04-30 | Vote for the [awards] on the status quo and shiny future stories! | +| 🛑 **Coming soon** | Participate in discussions and development towards [roadmap] goals | +| 🛑 **Coming soon** | Take ownership of "help wanted" goals from the [roadmap] | +| ⚠️ **Winding down** | Propose new ["status quo" stories][hvsq] or [comment] on existing PRs | +| ⚠️ **Winding down** | Propose new ["shiny future" stories][hvsf] or [comment] on existing PRs | +| 🛑 Coming soon | Vote for the [awards] on the status quo and shiny future stories! | -## The big picture +## Making the vision real -The process we are using to write the vision doc encourages active collaboration and "positive sum" thinking. It starts with a brainstorming period, during which we aim to collect as many "status quo" and "shiny future" stories as we can. +We are currently working towards implementing the async vision described in the [shiny future] section. On the [roadmap] page, you can get an overview of the major goals that are part of implementing that future and how we have divided up the work. Each of the goals also has several initiatives, and those initiatives have upcoming milestones. If you'd like to participate in an initiative, you can find the appropriate Zulip stream and see if they are looking for help! -This brainstorming period runs for six weeks, until the end of April. For the first two weeks (until 2021-04-02), we are collecting ["status quo" stories][hvsq] only. After that, we will accept both ["status quo"][hvsq] and ["shiny future" stories][hvsf] until the end of the brainstorming period. Finally, to cap off the brainstorming period, we will select winners for [awards] like "Most Humorous Story" or "Most Supportive Contributor". +### Goal and initiative owners -Once the brainstorming period is complete, the working group leads will begin work on assembling the various stories and shiny futures into a coherent draft. This draft will be reviewed by the community and the Rust teams and adjusted based on feedback. +Each top-level goal and initiative in the [roadmap] has an **owner**. The owner of the top-level goal manages the goal overall, while the owner of an initiative manages the "nitty gritty" design work (for example, preparing the [evaluation](./how_to_vision/evaluations.md), authoring any RFCs required, or supervising the implementation). You can learn more about the [responsibilities of owners](./how_to_vision/owners.md) in this page. If you have questions about whether you can help out with a goal or an initiative, the owner is probably the one to talk to. -### Brainstorming +[responsibilities of owners]: ./how_to_vision/owners.md -The brainstorming period runs until 2021-04-30: +### Help wanted goals -* Folks open "status quo" and (starting 2021-04-02) "shiny future" story PRs against the [wg-async-foundations repo][repo]. - * [Templates and instructions for status quo stories can be found here.][hvsq] - * You can also browse the [open "status quo" issues] for ideas! - * [Templates and instructions for shiny future stories can be found here.][hvsf] -* We collectively [comment] on these PRs, helping to improve them and make them more complete. -* Unless they contain factual inaccuracies, the aim is to merge **all** PRs opened in the brainstorming period. - * We also expect people to sometimes extend other stories with additional details or FAQs. -* At the end of the brainstorming period, we will vote and give [awards] for things like "most amusing". (We'd like suggestions on the best categories!) +Some of the top-level goals are marked with ✋, which means "help wanted". Those goals are looking for an owner. If you think you might be interested, you can read about the [responsibilities of owners] and contact the [wg leads]. -#### The more the merrier! +### Stakeholders -**During this brainstorming period, we want to focus on getting as many ideas as we can.** Having multiple "shiny futures" that address the same problem is a feature, not a bug, as it will let us mix-and-match later to try and find the best overall plan. Comments and questions will be used as a [tool for improving understanding or sharpening proposals.][comment] Presenting alternative ideas is done by [writing an alternative story][alt]. +While we always encourage feedback from the broader public, many of our initiatives also have identified sets of [stakeholders]. These are people who are specially consulted as part of the process to give feedback on the design and implementation. They can be representatives from major projects in the ecosystem, production users, or other sorts of experts. -[alt]: https://nikomatsakis.github.io/wg-async-foundations/vision/how_to_vision/comment.html#you-might-just-want-to-write-your-own-story +[stakeholders]: ./how_to_vision/stakeholders.md -#### Reviewing contributions +## Living document -To merge a story or project PR, any member of the working group can open a topic on Zulip and propose it be merged. Ideally there will be no outstanding concerns. If a second member of the working group approves, the PR can then be merged. +Although many of the pieces are complete, the vision doc is a living document and it will never be done. During the brainstorming period, we had a [lot of stories submitted](./submitted_stories.md) and we are now in the process of "harmonizing" those into a small set of [status quo] and [shiny future] narratives, each based around a representative [project] and the same set of [characters]. If you'd like to help out with that, contact the [wg leads]. -Reviewers should ensure that new stories and projects are added to the `SUMMARY.md` file either before merging or directly afterwards. +We also plan to regularly revisit the vision once we've made significant progress on implementation or if new information has come to light. -### Harmonizing +### Submitting status quo and shiny future story PRs -At this point, the [wg leads] will write the draft vision document, drawing on the status quo and shiny future stories that were submitted. -Like an RFC, this draft vision doc will be opened for comment and improved based on the resulting feedback. -When the [wg leads] feel it is ready, it will be taken to the [lang] and [libs] teams for approval (and other Rust teams as appropriate). +Although the brainstorming period has ended, we are still open to new PRs, particularly if they cover space that has not been well covered: -[lang]: https://www.rust-lang.org/governance/teams/lang -[libs]: https://www.rust-lang.org/governance/teams/library +* [Templates and instructions for status quo stories can be found here.][hvsq] +* [Templates and instructions for shiny future stories can be found here.][hvsf] + +### Wait, did somebody say awards? + +Yes! We are planning on giving [awards] in various categories for folks who write [status quo](./how_to_vision/status_quo.md) and [shiny future](./how_to_vision/shiny_future.md) PRs. The precise categories are TBD. Check out the [awards] page for more details. -### Living document -This meant to be a **living document**. We plan to revisit it regularly to track our progress and update it based on what we've learned in the meantime. Note that the shiny future stories in particular are going to involve a fair bit of uncertainty, so we expect them to change as we go. - [hvsq]: ./how_to_vision/status_quo.md [hvsf]: ./how_to_vision/shiny_future.md [Vote]: ./how_to_vision/awards.md @@ -62,10 +55,10 @@ This meant to be a **living document**. We plan to revisit it regularly to track [comment]: ./how_to_vision/comment.md [awards]: ./how_to_vision/awards.md [wg leads]: ../welcome.md#leads -[hvp]: ./how_to_vision/projects.md -[repo]: https://github.com/rust-lang/wg-async-foundations -[open "status quo" issues]: https://github.com/rust-lang/wg-async-foundations/labels/status-quo-story-ideas - -## Wait, did somebody say awards? - -Yes! We are planning to give [awards] in various categories for folks who write [status quo](./how_to_vision/status_quo.md) and [shiny future](./how_to_vision/shiny_future.md) PRs. The precise categories are TBD. Check out the [awards] page for more details. +[repo]: https://github.com/rust-lang/wg-async +[open "status quo" issues]: https://github.com/rust-lang/wg-async/labels/status-quo-story-ideas +[roadmap]: ./roadmap.md +[status quo]: ./status_quo.md +[shiny future]: ./shiny_future.md +[project]: ./project.md +[characters]: ./characters.md diff --git a/src/vision/how_to_vision/evaluations.md b/src/vision/how_to_vision/evaluations.md new file mode 100644 index 00000000..1e5837b4 --- /dev/null +++ b/src/vision/how_to_vision/evaluations.md @@ -0,0 +1,29 @@ +# Writing an evaluation + +When an initiative involves a complex design task, the [initiative owner] begins by writing an **evaluation**. The evaluation documents the various design options and their tradeoffs, and also includes a recommendation. Evaluations are posted publicly and presented to the relevant Rust teams, which will discuss with the [owners] and [stakeholders] ultimately make a choice on how to proceed. + +The current draft for each evaluation will be maintained in some git repository, often a dedicated repository for the initiative. The repository will also list the [stakeholders] associated with that particular effort. + +## Getting feedback + +Developing an evaluation consists of first preparing an initial draft by surveying initial work and then taking the following steps (repeat until satisfied): + +* Review draft in meetings with stakeholders + * These meetings can be a small, productive group of people + * Often better to have multiple stakeholders together so people can brainstorm together, but 1:1 may be useful too +* Present the draft to the teams and take feedback +* Review issues raised on the repo (see below) +* Adjust draft in response to the above comments + +## Issues on the repo + +In addition to the active outreach to stakeholders, anyone can submit feedback by opening issues on the repositories storing the draft evaluations. These reposies will have issue categories with templates that categorize the feedback and provide some structure. For example: + +* Experience report +* Proposal feedback +* Crazy new idea + +[initiative owner]: ./owners.md +[owners]: ./owners.md +[stakeholders]: ./stakeholders.md + diff --git a/src/vision/how_to_vision/owners.md b/src/vision/how_to_vision/owners.md new file mode 100644 index 00000000..016b665f --- /dev/null +++ b/src/vision/how_to_vision/owners.md @@ -0,0 +1,64 @@ +# Owning a goal or initiative + +This page describes the roles and responsibilities associated with being the **owner** of an item on the [roadmap](../roadmap.md). Roadmap items fall into two categories, top-level goals and initiatives. In both cases, being an owner means that you are responsible for ensuring that the item gets done, but the details of owning a top-level goal are different from owning an initiative. + +## Summary + +Goal owners are responsible for splitting their area into a set of **initiatives**. These can be active or on hold. + +They are also responsible for ensuring that for each initiative: + +- An owner is assigned +- A landing page exists +- Milestones are defined on the landing page +- Stakeholders are identified and looped in at the proper stages + +Finally, they are expected to attend sprint meetings. + +## Sprint meetings + +We are organizing the working group in **two week sprints**. This means that every two weeks we have a sprint planning meeting. **All goal owners are expected to attend!** Initiative owners or other contributors are welcome as well. + +The purpose of the sprint planning meeting is to check-in on the progress towards the milestones for each initiative and to see if they need to be adjusted. It's also a chance to raise interesting questions or get advice about tricky things or unexpected problems, as well as to celebrate our progress. + +## Owning a top-level goal + +As the owner of a **top-level goal** your role is to figure out overall plan for how that goal will be achieved and to track progress. This means breaking up the goal into different initiatives, finding owners for those initiatives (which can be you!), and helping those owners to plan milestones. You are also generally responsible for staying on top of the state of things and updating other owners as to new or interesting developments. + +## Owning an initiative + +Our definition of [initiative] is precisely the same as that used by the Rust lang team: it corresponds to a some active effort with a clear goal or deliverable(s). As the owner of an initiative, your role is to ensure that the work gets done (Which doesn't necessarily mean you do it yourself, it may be that you instead coordinate with volunteers or other implementors). You also guide the design of the deliverables within the initiative. + +As in the lang team process, the role of the owner is not to make the final decision (that belongs to the relevant rust team(s)), but to develop the "menu" of design choices, elaborate the tradeoffs involved, and make recommendations. For particularly complex designs, these evaluations will take the form of [evaluation documents] and are developed in collaboration with a defined set of [stakeholders]. + +[initiative]: https://lang-team.rust-lang.org/initiatives.html +[initiative owners]: https://lang-team.rust-lang.org/initiatives/process/roles/owner.html +[evaluation documents]: ./evaluations.md + +### Making a landing page + +Each initiative should have a landing page, linked to from the [roadmap]. This can be a page on this website or a dedicated repo. + +For in-progress initiatives the landing page should include, or have pointers to: + +- Goals and impact of the initiative +- Milestones +- Design notes and documentation +- Links to any organizing tools, such as a project board +- The initiative owner +- The current set of [stakeholders] and the area(s) they represent +- Notes on how to get involved +- For landing pages not on this website, a link back to the overall [roadmap] + +For making a dedicated repo, it's recommended to use this [initiative template][template] as a starting point. + +[roadmap]: ../roadmap.md +[template]: https://github.com/rust-lang/initiative-template + +### Planning initiative milestones + +When you own an initiative, you should work with the owner of the top-level goal and others to plan out a series of **milestones** around the initiative. These milestones correspond to the various steps you need to take to complete the initiative. + +Milestones are not fixed and they frequently change as you progress. They usually start out quite vague, such as "author an RFC", and then get more precise as you learn more about what is required: "figure out the design for X", "implement feature Y". We update the status and set of milestones for each sprint status meeting. + +[stakeholders]: ./stakeholders.md \ No newline at end of file diff --git a/src/vision/how_to_vision/projects.md b/src/vision/how_to_vision/projects.md deleted file mode 100644 index 6efcbe67..00000000 --- a/src/vision/how_to_vision/projects.md +++ /dev/null @@ -1,19 +0,0 @@ -# ❓ How to vision: Projects - -[pjd]: ../projects.md -[wg leads]: ../welcome.md#leads -[template]: ../status_quo/template.md -[summary]: ../../SUMMARY.md - -## How to open a PR - -If you'd like to add a new project, please [open a PR using this template][template] and adding a new file into [the `projects` directory][pjd]. Do not add your file to [`SUMMARY.md`][summary], that will create conflicts. We'll add it after merging. - -We are pretty happy to add new projects, although we would prefer only to add a new project if it has some characteristic that is distinct from the other projects we've got so far and which is important to a 'status quo' or 'shiny future' story. - -## FAQs to answer in your PR - -In your PR, make sure to include the following FAQs: - -* What makes this project different from most others? -* Are there existing crates that are similar to this project? diff --git a/src/vision/how_to_vision/shiny_future.md b/src/vision/how_to_vision/shiny_future.md index e23e73af..a4b9ebeb 100644 --- a/src/vision/how_to_vision/shiny_future.md +++ b/src/vision/how_to_vision/shiny_future.md @@ -1,8 +1,6 @@ # ❓ How to vision: "Shiny future" stories -## 🛑 Not time for this yet 🛑 - -We're not ready for this yet! See the [how to vision](../how_to_vision.md) page for details on the phasing. +We want all Async Rust users and their hopes and dreams for what Async Rust should be in the future to be reflected in the async vision doc, so please help us by writing 'shiny future' stories about what you would like async Rust to look like! **Remember: we are in a brainstorming period.** Please feel free to leave comments in an effort to help someone improve their PRs, but if you would prefer a different approach, you are better off writing your own story. (In fact, you should write your own story even if you like their approach but just have a few alternatives that are worth thinking over.) [character]: ../characters.md [comment]: ./comment.md @@ -18,7 +16,7 @@ We're not ready for this yet! See the [how to vision](../how_to_vision.md) page Just want to get started? Here are quick instructions to get you going: * **To write your own story:** - * Create a PR based on the ["shiny future" template][template]. + * Create a PR based on the ["shiny future" template][template]. * Do not add your file to [`SUMMARY.md`] -- that will create conflicts, we'll do it manually after merging. ## How to open a PR @@ -54,7 +52,7 @@ Every shiny future PR includes a FAQ. This FAQ should always include answers to There are also some optional questions: * What are the incremental steps towards realizing this shiny future? - * Talk about the actual work we will do. You can link to [design docs](../design_docs.md) or even add new ones, as appropriate. + * Talk about the actual work we will do. You can link to [design docs](../../design_docs.md) or even add new ones, as appropriate. * You don't have to have the whole path figured out yet! * Does realizing this future require cooperation between many projects? * For example, if you are describing an interface in libstd that runtimes will have to implement, talk about that. @@ -73,30 +71,43 @@ The goal is that, at the end of the review process, the status quo story has a l ## 🤔 Frequently Asked Questions -### **What is the process to propose a shiny future story?** +### What is the process to propose a shiny future story? * Just open a PR [using this template][template]. * Do not add your file to [`SUMMARY.md`], that will create conflicts. We'll do it after merging. -### **What character should I use for my shiny future story?** +### What character should I use for my shiny future story? * Usually you would use the same character from the status quo story you are retelling. * If for some reason you chose a different character, add a FAQ to explain why. -### **What do I do if there is no status quo story for my shiny future?** +### What do I do if there is no status quo story for my shiny future? [Write the status quo story first!](./status_quo.md) -### **How much detail should I give? How specific should I be?** +#### What happens when there are multiple "shiny future" stories about the same thing? + +During this brainstorming period, we want to focus on getting as many ideas as we can. Having multiple "shiny futures" that address the same problem is a feature, not a bug, as it will let us mix-and-match later to try and find the best overall plan. + +### How much detail should I give? How specific should I be? * Detailed is generally better, but only if those details are helpful for understanding the morals of your story. * Specific is generally better, since an abstract story doesn't feel as real. -### **What do I do when I get to details that I don't know yet?** +#### What is the "scope" of a shiny future story? Can I tell shiny future stories that involve ecosystem projects? + +All the stories in the vision doc are meant to cover the full "end to end" experience of using async Rust. That means that sometimes they will take about things that are really part of projects that are outside of the Rust org. For example, we might write a shiny future that involves how the standard library has published standard traits for core concepts and those concepts have been adopted by libraries throughout the ecosystem. There is a FAQ that asks you to talk about what kinds of coordinate between projects will be required to realize this vision. + +### What do I do when I get to details that I don't know yet? Take your best guess and add a FAQ explaining which details are still up in the air. -### **What do I do if I don't know that my idea is technically feasible?** -You don't have to know how your idea will work yet. You can add FAQs to try and clarify what parts you do know and what parts still need to be figured out. +### Do we have to know exactly how we will achieve the "shiny future"? + +You don't have to know how your idea will work yet. We will eventually have to figure out the precise designs, but at this point we're more interested in talking about the experience we aim to create. That said, if you do have plans for how to achieve your shiny future, you can also include [design docs] in the PR, or add FAQ that specify what you have in mind (and perhaps what you have to figure out still). -### **What do I do if somebody leaves a comment about how my idea will work and I don't know the answer?** +### What do I do if somebody leaves a comment about how my idea will work and I don't know the answer? Add it to the FAQ! -[template]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/shiny_future/template.md -[sfd]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/shiny_future -[`SUMMARY.md`]: https://github.com/rust-lang/wg-async-foundations/blob/master/src/SUMMARY.md +### What if we write a "shiny future" story but it turns out to be impossible to implement? + +Glad you asked! The vision document is a living document, and we intend to revisit it regularly. This is important because it turns out that predicting the future is hard. We fully expect that some aspects of the "shiny future" stories we write are going to be wrong, sometimes very wrong. We will be regularly returning to the vision document to check how things are going and adjust our trajectory appropriately. + +[template]: https://github.com/rust-lang/wg-async/tree/master/src/vision/shiny_future/template.md +[sfd]: https://github.com/rust-lang/wg-async/tree/master/src/vision/shiny_future +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md diff --git a/src/vision/how_to_vision/stakeholders.md b/src/vision/how_to_vision/stakeholders.md new file mode 100644 index 00000000..62222997 --- /dev/null +++ b/src/vision/how_to_vision/stakeholders.md @@ -0,0 +1,31 @@ +# Stakeholders + +Many initiatives in the [roadmap] have an associated set of **stakeholders**. The role of a stakeholder is as follows: + +* They are consulted by the owner over the course of working on the initiative. +* They do not have veto power; that belongs to the team. +* When they do raise concerns, those concerns should either be addressed in the design or discussed explicitly in the FAQ. + +Stakeholders can be: + +* Domain experts (perhaps from other languages) +* Representatives from major libraries +* Production users + +Stakeholders can be selected in coordination with the async working group leads. Potential new stakeholders can also get in touch with the owner. + +## Feedback on the design + +One role for stakeholders is to give feedback on the design as it progresses. Stakeholders are thus consulted in course of preparing evaluation docs or RFCs. + +## Experimenting with the implementation + +Another role for stakeholders is evaluating the implemenation. This is partiularly important for production users. Stakeholders might, for example, agree to port their code to use the nightly version of the feature and adapt it as the design evolves. + +## Goals of the stakeholder program + +The goal of the stakeholder program is to make Rust's design process even more inclusive. We have observed that existing mechanisms like the RFC process or issue threads are often not a very good fit for certain categories of users, such as production users or the maintainers of large libraries, as they are not able to keep up with the discussion. As a result, they don't participate, and we wind up depriving ourselves of valuable feedback. The stakeholder program looks to supplement those mechanisms with direct contact. + +Another goal is to get more testing: one problem we have observed is that features are often developed and deployed on nightly, but production users don't really want to try them out until they hit stable! We would like to get some commitment from people to give things a try so that we have a better chance of finding problems before stabilization. + +We want to emphasize that we welcome design feedback from **all Rust users**, regardless of whether you are a named stakeholder or not. If you're using async Rust, or have read through the designs and have a question or idea for improvement, please feel free to open an issue on the appropriate repository. diff --git a/src/vision/how_to_vision/status_quo.md b/src/vision/how_to_vision/status_quo.md index 8f11e449..2cc1a268 100644 --- a/src/vision/how_to_vision/status_quo.md +++ b/src/vision/how_to_vision/status_quo.md @@ -1,6 +1,6 @@ # ❓ How to vision: "Status quo" stories -We want to make sure all Async Rust users and their experiences are reflected in the async vision doc, so please help us by writing 'status quo' stories about your experiences or the experiences of others! Remember, **status quo stories are not "real", but neither are they fiction.** They are constructed from the real experiences of people using Async Rust (often multiple people). +We want to make sure all Async Rust users and their experiences are reflected in the async vision doc, so please help us by writing 'status quo' stories about your experiences or the experiences of others! Remember, **status quo stories are not "real", but neither are they fiction.** They are constructed from the real experiences of people using Async Rust (often multiple people). [sq]: ../status_quo.md [character]: ../characters.md @@ -11,29 +11,29 @@ We want to make sure all Async Rust users and their experiences are reflected in Just want to get started? Here are quick instructions to get you going: * **To write your own story:** - * Create a PR based on the ["status quo" template][template]. + * Create a PR based on the ["status quo" template][template]. * Do not add your file to [`SUMMARY.md`] -- that will create conflicts, we'll do it manually after merging. * **To get feedback on a story idea, or look for someone else to write it:** - * Open up a ["status quo" story issue][issue] on the [wg-async-foundations repository]. + * Open up a ["status quo" story issue][issue] on the [wg-async repository]. * **To find ideas of what to write, or to share your experiences:** * Search the [open issues tagged as status-quo-story-idea][oi]. * Remember to [comment supportively][comment]. ## Optional: open an issue to discuss your story or find others with similar experiences -If you have a story idea but you don't have the time to write about it, or if you would like to know whether other folks have encountered the same sorts of problems, you can [open up a "status quo" story issue][issue] on the [wg-async-foundations repository]. Alternatively, if you're looking for a story to write, you can browse the [open issues tagged as status-quo-story-idea][oi] and see if anything catches your eye. If you see people describing problems you have hit, or have questions about the experiences people are sharing, then please leave a comment -- but remember to [comment supportively][comment]. (You can also come to [Zulip] to discuss.) +If you have a story idea but you don't have the time to write about it, or if you would like to know whether other folks have encountered the same sorts of problems, you can [open up a "status quo" story issue][issue] on the [wg-async repository]. Alternatively, if you're looking for a story to write, you can browse the [open issues tagged as status-quo-story-idea][oi] and see if anything catches your eye. If you see people describing problems you have hit, or have questions about the experiences people are sharing, then please leave a comment -- but remember to [comment supportively][comment]. (You can also come to [Zulip] to discuss.) -[sqsi]: https://github.com/rust-lang/wg-async-foundations/labels/status-quo-story-ideas +[sqsi]: https://github.com/rust-lang/wg-async/labels/status-quo-story-ideas [Zulip]: ../../welcome.md#Zulip -[issue]: https://github.com/rust-lang/wg-async-foundations/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= -[oi]: https://github.com/rust-lang/wg-async-foundations/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas -[wg-async-foundations repository]: https://github.com/rust-lang/wg-async-foundations/ +[issue]: https://github.com/rust-lang/wg-async/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= +[oi]: https://github.com/rust-lang/wg-async/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas +[wg-async repository]: https://github.com/rust-lang/wg-async/ ## How to open a PR If you have an idea you'd like to write about, please [open a PR using this template][template] and adding a new file into [the `status_quo` directory][sqd]. Do not add your file to [`SUMMARY.md`] -- that will create conflicts, we'll do it manually after merging. -## Goals of a status quo PR +## Goals of a status quo PR When writing a [status quo][sq] story, your goal is to present what you see as a major challenge for Async Rust. You want to draw upon people's experiences (sometimes multiple people) to show all the aspects of the problem in an engaging and entertaining way. @@ -89,6 +89,6 @@ It doesn't have to be perfect. Pick the one that seems like the closest fit. If The more specific you can get, the better. If you can link to tweets or blog posts, that's ideal. You can also add notes into the [conversations] folder and link to those. Of course, you should be sure people are ok with that. [conversations]: ../../conversations.md -[template]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/status_quo/template.md -[sqd]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/status_quo -[`SUMMARY.md`]: https://github.com/rust-lang/wg-async-foundations/blob/master/src/SUMMARY.md +[template]: https://github.com/rust-lang/wg-async/tree/master/src/vision/status_quo/template.md +[sqd]: https://github.com/rust-lang/wg-async/tree/master/src/vision/status_quo +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md diff --git a/src/vision/projects/template.md b/src/vision/projects/template.md index 76189820..2c4224dd 100644 --- a/src/vision/projects/template.md +++ b/src/vision/projects/template.md @@ -2,7 +2,7 @@ *This is a template for adding new projects. See the [instructions] for more details on how to add new project!* -[instructions]: ../how_to_vision/projects.md +[instructions]: ../projects.md ## What is this? diff --git a/src/vision/roadmap.md b/src/vision/roadmap.md index 64ad6d5e..f777dd04 100644 --- a/src/vision/roadmap.md +++ b/src/vision/roadmap.md @@ -1,26 +1,117 @@ -# 📅 The roadmap: what we're doing in 2021 +# Roadmap -This page describes the current plans for 2021. -It is updated on a monthly basis. +What follows is a list of *high-level goals*, like "async fn everywhere", that capture some part of the improved user experience. Each goal has associated *initiatives*, which are particular streams of work within that goal. Each goal and each initiative have an associated owner -- in some cases multiple owners -- who are the people responsible for ensuring that the goal/initiative is making progress. If you click on a goal/initiative, you will get a high-level description of its *impact*. That is, how the experience of using async Rust is going to change as a result of this work. -### 🛑 Not time for this yet 🛑 +We categorize the goals and initiatives into four states: -We're not really ready to work on this section yet. We're still focused on writing out the [status quo](./status_quo.md). What you see here are really just placeholders to give you the idea of what this section might look like. +| State | Meaning | +| --- | --- | +| ✅ | Done. | +| 🦀 | In progress: Work is ongoing! | +| ✋ | Help wanted: Seeking an [owner] to pursue this! Talk to the [wg leads] if you are interested. | +| 💤 | Paused: we are waiting to work on this until some other stuff gets done. | -## Key +Some goals and initiatives have further "how to help" instructions for those wanting to contribute. +These are marked by the 🛠️ symbol. -| Emoji | Meaning | -| --- | --- | -| 🥬 | "Healthy" -- on track with the plan as described in the doc | -| ✏️ | "Planning" -- Still figuring out the plan | -| 🤒 | "Worried" -- things are looking a bit tricky, plans aren't working out | -| 🏖️ | "On vacation" -- taking a break right now | -| ⚰️ | We gave up on this idea =) | +[owner]: ./how_to_vision/owners.md + +## Impact and milesetones + +Clicking on active initiatives also shows a list of *milestones*. These milestones (things like "write an [evaluation doc]") indicate the planned work ahead of us. We meet every 2 weeks to assess our progress on these milestones and to update the list as needed. -## Roadmap items +[evaluation doc]: ./roadmap/stages.html#evaluation +[stabilize]: https://lang-team.rust-lang.org/initiatives/process/stages/stabilized.html +[feature complete]: https://lang-team.rust-lang.org/initiatives/process/stages/feature_complete.html -| Plan | Owner | Status | Last updated +## Overview + +| Deliverable | State | Progress | [Owner] | | --- | --- | --- | --- | -| [Async functions in traits] | nikomatsakis | 🥬 | 2021-02 | +| 🔻 [Async fn everywhere] | 🦀 | ▰▰▱▱▱▱ | [tmandry] | +|   ↳ [Type Alias Impl Trait] | 🦀 | ▰▰▰▰▰▱ | [oli-obk] | +|   ↳ [Generic Associated Types] | 🦀 | ▰▰▰▰▰▱ | [jackh726] | +|   ↳ [Fundamentals] | 🦀 | ▰▰▱▱▱▱ | [tmandry] | +|   ↳ [Boxable async functions] | 💤 | ▰▱▱▱▱▱ | | +|   ↳ [Async main and tests] | 🦀 | ▰▰▱▱▱▱ | [vincenzopalazzo] | +| 🔻 [Scoped spawn and reliable cancellation] | 💤 | ▰▱▱▱▱▱ | | +|   ↳ [Capability] | 💤 | ▰▱▱▱▱▱ | | +|   ↳ [Scope API] | 💤 | ▰▱▱▱▱▱ | | +| 🔻 [Async iteration] | 💤 | ▰▰▱▱▱▱ | | +|   ↳ [Async iteration trait] | 💤 | ▰▰▰▱▱▱ | | +|   ↳ [Generator syntax] | 💤 | ▰▰▱▱▱▱ | | +| 🔻 [Portable across runtimes] | 🦀 | ▰▰▱▱▱▱ | [nrc] | +|   ↳ [Read/write traits] | 🦀 | ▰▰▱▱▱▱ | | +|   ↳ [Timer traits] | 💤 | ▰▱▱▱▱▱ | | +|   ↳ [Spawn traits] | 💤 | ▰▱▱▱▱▱ | | +|   ↳ [Runtime trait] | 💤 | ▰▱▱▱▱▱ | | +| 🔻 [Polish] [[🛠️][how-to-help-polish]] | 🦀 | ▰▰▰▱▱▱ | [eholk] | +|   ↳ [Error messages] | 💤 | ▰▰▰▱▱▱ | | +|   ↳ [Must not suspend lint] | 🦀 | ▰▰▰▰▱▱ | | +|   ↳ [Blocking function lint] | 💤 | ▰▰▱▱▱▱ | | +|   ↳ [Lint against large copies] | 💤 | ▰▰▱▱▱▱ | | +|   ↳ [Cleaner async stacktraces] | 💤 | ▰▱▱▱▱▱ | | +|   ↳ [Precise generator captures] | 🦀 | ▰▱▱▱▱▱ | | +|   ↳ [Sync and async behave the same] | 🦀 | ▰▱▱▱▱▱ | | +| 🔻 [Tooling] | 🦀 | ▰▰▱▱▱▱ | [pnkfelix] | +|   ↳ [Tokio console] | 🦀 | ▰▰▰▰▱▱ | [eliza weisman] | +|   ↳ [Crashdump debugging] | 🦀 | ▰▰▱▱▱▱ | [michaelwoerister] | +| 🔻 [Documentation] | 🦀 | ▰▰▱▱▱▱ | | +|   ↳ [Async book] | 💤 | ▰▰▱▱▱▱ | | +| 🔻 [Testing] | 💤 | ▰▱▱▱▱▱ | | +|   ↳ tbd | 💤 | ▰▱▱▱▱▱ | +| 🔻 [Threadsafe portability] | 💤 | ▰▱▱▱▱▱ | | +|   ↳ tbd | 💤 | ▰▱▱▱▱▱ | +| 🔻 [Keyword generics] | 🦀 | ▰▱▱▱▱▱ | [yoshuawuyts] | +|   ↳ [Async overloading] | 🦀 | ▰▱▱▱▱▱ | [yoshuawuyts] | + +[Async fn everywhere]: ./roadmap/async_fn.md +[fundamentals]: https://rust-lang.github.io/async-fundamentals-initiative/ +[Async closures]: https://rust-lang.github.io/async-fundamentals-initiative/design-discussions/async_closures.html +[Boxable async functions]: ./roadmap/async_fn/boxable.md +[Async main and tests]: https://vincenzopalazzo.github.io/async-main-and-tests-initiative/index.html +[Scoped spawn and reliable cancellation]: ./roadmap/scopes.md +[Capability]: ./roadmap/scopes/capability.md +[Scope API]: ./roadmap/scopes/scope_api.md +[Async iteration]: ./roadmap/async_iter.md +[Async iteration trait]: ./roadmap/async_iter/traits.md +[Generator syntax]: ./roadmap/async_iter/generators.md +[Portable across runtimes]: https://github.com/nrc/portable-interoperable +[Read/write traits]: https://github.com/nrc/portable-interoperable/blob/master/io-traits/README.md +[Timer traits]: ./roadmap/portable/timers.md +[Spawn traits]: ./roadmap/portable/spawn.md +[Runtime trait]: ./roadmap/portable/runtime.md +[polish]: ./roadmap/polish.md +[how-to-help-polish]: ./roadmap/polish.md#-how-to-help +[Error messages]: ./roadmap/polish/error_messages.md +[Blocking function lint]: ./roadmap/polish/lint_blocking_fns.md +[Must not suspend lint]: ./roadmap/polish/lint_must_not_suspend.md +[Cleaner async stacktraces]: ./roadmap/polish/stacktraces.md +[Lint against large copies]: ./roadmap/polish/lint_large_copies.md +[Tooling]: ./roadmap/tooling.md +[Tokio console]: https://github.com/tokio-rs/console +[Crashdump debugging]: https://github.com/rust-lang/async-crashdump-debugging-initiative +[Documentation]: ./roadmap/documentation.md +[Async book]: ./roadmap/documentation/async_book.md +[Testing]: ./roadmap/testing.md +[Threadsafe portability]: ./roadmap/threadsafe_portability.md +[Async overloading]: ./roadmap/async_overloading.md +[Generic Associated Types]: https://github.com/nikomatsakis/generic-associated-types-initiative/ +[Type Alias Impl Trait]: https://github.com/nikomatsakis/impl-trait-initiative/ +[Precise generator captures]: ./roadmap/polish/precise_generator_captures.md +[Sync and async behave the same]: ./roadmap/polish/sync_and_async.md +[Keyword generics]: https://rust-lang.github.io/keyword-generics-initiative/ + +[nikomatsakis]: https://github.com/nikomatsakis +[tmandry]: https://github.com/tmandry +[michaelwoerister]: https://github.com/michaelwoerister +[eholk]: https://github.com/eholk +[pnkfelix]: https://github.com/pnkfelix +[eliza weisman]: https://github.com/hawkw +[jackh726]: https://github.com/jackh726 +[oli-obk]: https://github.com/oli-obk +[yoshuawuyts]: https://github.com/yoshuawuyts +[nrc]: https://github.com/nrc +[vincenzopalazzo]: https://github.com/vincenzopalazzo -[Async functions in traits]: ../design_docs/async_fn_in_traits.md +[wg leads]: ../welcome.md#leads diff --git a/src/vision/roadmap/async_fn.md b/src/vision/roadmap/async_fn.md new file mode 100644 index 00000000..a5258f45 --- /dev/null +++ b/src/vision/roadmap/async_fn.md @@ -0,0 +1,8 @@ +# Async fn everywhere + +## Impact + +* To a first-order approximation, any place that you can write some sort of Rust function or closure, you should be able to make it asynchronous: + * [in traits and closures, including the Drop trait](https://rust-lang.github.io/async-fundamentals-initiative/) + * in [main and tests](./async_fn/async_main_and_tests.md) +* You should be able to [easily create futures that heap allocate their storage](./async_fn/boxable.md), both for performance tuning and for scenarios like recursive functions diff --git a/src/vision/roadmap/async_fn/async_main_and_tests.md b/src/vision/roadmap/async_fn/async_main_and_tests.md new file mode 100644 index 00000000..7522742f --- /dev/null +++ b/src/vision/roadmap/async_fn/async_main_and_tests.md @@ -0,0 +1,13 @@ +# Async main and tests + +## Impact + +* Able to write `#[test]` that easily use async functions. +* In the case of portable libraries, end users are able to re-run test suites with distinct runtimes. + +## Milestones + +> Able to write `async fn main` and `#[test] async fn` just like you would in synchronous code. + +This initiative is **on hold** while we investigate mechanisms for [portability across runtimes](../portable.md). + diff --git a/src/vision/roadmap/async_fn/boxable.md b/src/vision/roadmap/async_fn/boxable.md new file mode 100644 index 00000000..2dd39f4b --- /dev/null +++ b/src/vision/roadmap/async_fn/boxable.md @@ -0,0 +1,51 @@ +# Boxable async fn + +## Impact + +* Able to easily cause some async functions, blocks, or closures to allocate their stack space lazilly when called (by 'boxing' it) + * Combined with profiler or other tooling support, this can help to tune the size of futures +* Boxed async blocks allows particular *portions* of a function to be boxed, e.g. cold paths + +## Milestones + +| Milestone | State | Key participants | +| --- | --- | --- | +| Author [evaluation doc] | 💤 | | +| [Feature complete] implementation | 💤 | | + +[evaluation doc]: ./roadmap/stages.html#evaluation +[stabilize]: https://lang-team.rust-lang.org/initiatives/process/stages/stabilized.html +[feature complete]: https://lang-team.rust-lang.org/initiatives/process/stages/feature_complete.html + +## Design notes + +Example might be to use a decorator: + +```rust +#[boxed] +async fn foo() { } +``` + +This does not have to desugar to `-> Box>`; it can instead desugar to `Box`, or perhaps a nominal type to permit recursion. + +Another approach is the `box` keyword: + +```rust +box async fn foo() { } +``` + +We can apply the keyword modifier to async blocks and closures: + +```rust +fn foo() -> BoxFuture { + box async { ... } +} +``` + +```rust +async fn stuff(s: impl AsyncIterator) { + s.map(box async |x| { ... }) +} +``` + +This is useful for breaking up future types to make them more shallow. diff --git a/src/vision/roadmap/async_iter.md b/src/vision/roadmap/async_iter.md new file mode 100644 index 00000000..c0fcb83b --- /dev/null +++ b/src/vision/roadmap/async_iter.md @@ -0,0 +1,6 @@ +# Flexible async iteration + +## Impact + +* Able to create and compose iterators that await async results +* Able to create complex parallel or concurrent schedules that work reliably diff --git a/src/vision/roadmap/async_iter/generators.md b/src/vision/roadmap/async_iter/generators.md new file mode 100644 index 00000000..dcdfec63 --- /dev/null +++ b/src/vision/roadmap/async_iter/generators.md @@ -0,0 +1,6 @@ +# Generators + +## Impact + +* Able to write iterators (and async iterators) with ease, comparable to languages like Python or JavaScript +* Able to extend the resulting iterators with "optimization" traits like `ExactSizeIterator` for maximum efficiency \ No newline at end of file diff --git a/src/vision/roadmap/async_iter/traits.md b/src/vision/roadmap/async_iter/traits.md new file mode 100644 index 00000000..408289de --- /dev/null +++ b/src/vision/roadmap/async_iter/traits.md @@ -0,0 +1,58 @@ +# Async iteration + +## Impact + +* Able to write code that takes "something iterable" +* Able to use combinators similar to synchronous `Iterator` +* Able to construct complex, parallel schedules that [can refer to borrow data](../borrowed_data_and_cancellation.md) + +## Requires + +* [inline async functions](../async_fn/inline_async_fn.md), but it is possible to prototype and experiment without that +* [borrowed data and cancellation](../borrowed_data_and_cancellation.md), but it is possible to prototype and experiment without that + +## Design notes + +The async iterator trait can leverage [inline async functions](../async_fn_everywhere/inline_async_fn.md): + +```rust +#[repr(inline_async)] +trait AsyncIterator { + type Item; + + async fn next(&mut self) -> Self::Item; +} +``` + +Note the name change from `Stream` to `AsyncIterator`. + +One implication of this change is that pinning is no longer necessary when driving an async iterator. For example, one could now write an async iterator that recursively walks through a set of URLs like so (presuming `std::async_iter::from_fn` and [async closures](https://rust-lang.github.io/async-fundamentals-initiative/design-discussions/async_closures.html)): + +```rust +fn explore(start_url: Url) -> impl AsyncIterator { + let mut urls = vec![start_url]; + std::async_iter::from_fn(async move || { + if let Some(url) = urls.pop() { + let mut successor_urls = fetch_successor_urls(url).await; + urls.extend(successor_urls); + Some(url) + } else { + None + } + }) +} +``` + +### Parallel async iteration + +We should have combinators like `buffered` that enable *parallel* async iteration, similar to the parallel iterators offered by [rayon]. The core operation here is `for_each` (which processes each item in the iterator): + +```rust +trait ParAsyncIter { + type Item; + + async fn for_each(&mut self, op: impl AsyncFn(Self::Item)); +} +``` + +The `buffered` combinator would be implemented by creating an internal scope and spawning tasks into it as needed. \ No newline at end of file diff --git a/src/vision/roadmap/async_overloading.md b/src/vision/roadmap/async_overloading.md new file mode 100644 index 00000000..502b95d7 --- /dev/null +++ b/src/vision/roadmap/async_overloading.md @@ -0,0 +1,47 @@ +# Async overloading + +## Impact + +* By default, function definitions can be compiled into either sync or async mode +* Able to overload a function with two variants, one for sync and one for async + +## Design notes + +This is a highly speculative deliverable. However, it would be great if one were able to write code that is neither sync nor sync, but potentially *either*. Further, one should be able to provide *specialized* variants that perform the same task but in slightly different ways; this would be particularly useful for primitives like TCP streams. + +### Monomorphize + +The way to think of this is that every function has an implicit generic parameter indicating its *scheduler mode*. When one writes `fn foo()`, that is like creating a generic impl: + +```rust +impl Fn<(), SM> for Foo +where + SM: SchedulerMode, +{ + ... +} +``` + +When one writes `async fn` or `sync fn`, those are like providing specific impls: + +```rust +impl Fn<(), AsyncSchedulerMode> for Foo { + ... +} + +impl Fn<(), SchedulerMode> for Foo { + ... +} +``` + +Further, by default, when you call a function, you invoke it in the same scheduler mode as the caller. + +### Implications for elsewhere + +* If we had this feature, then having distinct modules like `use std::io` and `use std::async_io` would not be necessary. +* Further, we would want to design our traits and so forth to have a "common subset" of functions that differ only in the presence or absence of the keyword `async`. + +### Related work + +* [SE-0296: Allow overloads that differ only in async](https://github.com/apple/swift-evolution/pull/1392) +* [Async Overloading (Yoshua Wuyts, 2021)](https://blog.yoshuawuyts.com/async-overloading/) diff --git a/src/vision/roadmap/documentation.md b/src/vision/roadmap/documentation.md new file mode 100644 index 00000000..fac3a406 --- /dev/null +++ b/src/vision/roadmap/documentation.md @@ -0,0 +1,9 @@ +# Documentation + +## Impact + +* Quality, easily findable documentation to help folks get started with async Rust + +## Requires + +* [Async book](./documentation/async_book.md) diff --git a/src/vision/roadmap/documentation/async_book.md b/src/vision/roadmap/documentation/async_book.md new file mode 100644 index 00000000..5848c95a --- /dev/null +++ b/src/vision/roadmap/documentation/async_book.md @@ -0,0 +1,6 @@ +# Async book + +## Impact + +* Centralized documentation explainined how Async Rust works +* Docs explain how to get started, identify common patterns, and cover concepts that are common to all or most runtimes diff --git a/src/vision/roadmap/polish.md b/src/vision/roadmap/polish.md new file mode 100644 index 00000000..3f0d0f85 --- /dev/null +++ b/src/vision/roadmap/polish.md @@ -0,0 +1,70 @@ +# Polish + +## Impact + +* Users can predict and understand why the compiler raises error messages. Errors are aligned with an experienced user's intuition about how Rust works. +* Error messages identify common misconceptions, suggest solutions, and are generally on par with sync Rust. + * Errors not only show that there is a problem, they help the user to fix it and to learn more about Rust (possibly directing the user to other documentation). + * The compiler may suggest crates from the ecosystem to help solve problems when appropriate. +* Lints guide the user away from common errors and help them both to get started with async Rust and to maintain async Rust programs over time. +* Rust's async implementation is high quality and reflects an attention to detail. + * No internal compiler errors + * Compiler analysis and code generation passes are precise and not unnecessarily conservative. + * Integration with low-level tooling and the like is high-quality. + * The generated code from the compiler is high quality and performant. + +## 🛠️ How to Help + +The goal of a highly polished async experience in Rust has many details and touches many aspects of the project, including both the async area in particular and the Rust project in general. +This means there are lots of ways to get involved! + +The weekly [triage meeting] primarily focuses on polish issues, so that is a great place to get to know people already working on the project and find out what people are actively working on. +We meet over Zulip, so feel free to just lurk, or chime in if you want to. +See the [triage meeting] page for details about when the meeting happens and how to join. + +Even outside of regularly scheduled meetings, you are welcome to hang out in the Async Working Group's [Zulip stream]. +There are usually a few people active there who are happy to discuss async-related topics. + +If you are looking for a specific area to help, there are several places where we track work. + +* The [Initiatives](#initiatives) list down below. +* The Async Work Group [Project Board]. The "On Deck" column is a good place to start looking. +* Issues on the [wg-async repo]. These tend to relate to project organization and longer term objectives. +* Issues on the [Rust repo]. Specifically, issues tagged [AsyncAwait-Polish], [A-async-await]. Issues that are also tagged with E-mentor will have mentoring instructions, which are usually pointers to specific points in the code where changes will be needed to fix the issue. + +Finally, a great way to contribute is to point out any rough edges you come across with writing async Rust. +This can be done either through issues on the [Rust repo], or by starting a topic on our [Zulip stream]. +Examples of rough edges that we are interested in include confusing error messages or places where Rust behaved in a way you found surprising or counter-intuitive. +Knowing about these issues helps to ensure we are fixing the right things. + +[A-async-await]: https://github.com/rust-lang/rust/labels/A-async-await +[AsyncAwait-Polish]: https://github.com/rust-lang/rust/labels/AsyncAwait-Polish +[Project Board]: https://github.com/orgs/rust-lang/projects/2 +[Rust repo]: https://github.com/rust-lang/rust/issues +[Triage meeting]: ../../triage.md +[wg-async repo]: https://github.com/rust-lang/wg-async/issues +[Zulip stream]: https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async + +## Initiatives + +| Initiative | State | Key participants | +| --- | --- | --- | +| [Error messages] | 💤 | | +| Lint: [Must not suspend] | 🦀 | [Gus Wynn] | +| Lint: [Blocking in async context] | 💤 | | +| Lint: [Large copies], large generators | 💤 | | +| [Cleaner async stacktraces] | 💤 | | +| [Precise generator captures] | 🦀 | [eholk] | +| [Sync and async behave the same] | 💤 | | + +[eholk]: https://github.com/eholk/ +[Lang team]: https://www.rust-lang.org/governance/teams/lang +[Blocking in async context]: ./polish/lint_blocking_fns.md +[Large copies]: ./polish/lint_large_copies.md +[Must not suspend]: ./polish/lint_must_not_suspend.md +[RFC]: https://rust-lang.github.io/rfcs/3014-must-not-suspend-lint.html +[Precise generator captures]: ./polish/precise_generator_captures.md +[Gus Wynn]: https://github.com/guswynn +[Error messages]: ./polish/error_messages.md +[Cleaner async stacktraces]: ./polish/stacktraces.md +[Sync and async behave the same]: ./polish/sync_and_async.md diff --git a/src/vision/roadmap/polish/error_messages.md b/src/vision/roadmap/polish/error_messages.md new file mode 100644 index 00000000..bae8cd5b --- /dev/null +++ b/src/vision/roadmap/polish/error_messages.md @@ -0,0 +1,11 @@ +# Error messages for most confusing scenarios + +## Impact + +* Errors not only show that there is a problem, they help the user to fix it and to learn more about Rust (possibly directing the user to other documentation). + +## Design notes + +Of course there are an infinite number of improvements one could make. The point of this deliverable is to target the *most common* situations and confusions people see in practice. The final list is still being enumerated: + +* Confusing error: Immutable reference to future is not a future [rust-lang/rust#87211](https://github.com/rust-lang/rust/issues/87211) diff --git a/src/vision/roadmap/polish/lint_blocking_fns.md b/src/vision/roadmap/polish/lint_blocking_fns.md new file mode 100644 index 00000000..6c4dbb89 --- /dev/null +++ b/src/vision/roadmap/polish/lint_blocking_fns.md @@ -0,0 +1,12 @@ +# Lint blocking fns + +## Impact + +* Identify calls to blocking functions from within async functions and guide the user to an async replacement. + +## Milestones + +| Milestone | Status | Key Participants | +| --- | --- | --- | +| RFC proposed and accepted | 💤 | | +| Implemented | 💤 | | diff --git a/src/vision/roadmap/polish/lint_large_copies.md b/src/vision/roadmap/polish/lint_large_copies.md new file mode 100644 index 00000000..cbd5106e --- /dev/null +++ b/src/vision/roadmap/polish/lint_large_copies.md @@ -0,0 +1,18 @@ +# Lint large copies + +## Impact + +* Identify when large types are being copied and issue a warning. This is particularly useful for large futures, but applies to other Rust types as well. + +## Milestones + +| Milestone | Status | Key Participants | +| --- | --- | --- | +| [Lang team] initiative proposal | 💤 | | +| Implemented | 💤 | | + +## Design notes + +This is already implemented in experimental form. We would also need easy and effective ways to reduce the size of a future, though, such as [deliv_boxable](../async_fn/boxable.md). + +[Lang team]: https://www.rust-lang.org/governance/teams/lang diff --git a/src/vision/roadmap/polish/lint_must_not_suspend.md b/src/vision/roadmap/polish/lint_must_not_suspend.md new file mode 100644 index 00000000..905f1184 --- /dev/null +++ b/src/vision/roadmap/polish/lint_must_not_suspend.md @@ -0,0 +1,19 @@ +# Lint must not suspend + +## Impact + +* Warnings when values which ought not to be live over an await are, well, live over an `await`. + * Example: lock guards. + +## Milestones + +| Milestone | Status | Key Participants | +| --- | --- | --- | +| Implemented the [RFC] | ✅ | [Gus Wynn] | +| [Improve drop range tracking] | 🦀 | [Eric Holk] | +| Stabilize the lint | 💤 |[Gus Wynn] | + +[RFC]: https://rust-lang.github.io/rfcs/3014-must-not-suspend-lint.html +[Improve drop range tracking]: https://github.com/rust-lang/rust/pull/91032 +[Gus Wynn]: https://github.com/guswynn +[Eric Holk]: https://github.com/eholk diff --git a/src/vision/roadmap/polish/precise_generator_captures.md b/src/vision/roadmap/polish/precise_generator_captures.md new file mode 100644 index 00000000..6db3d5ad --- /dev/null +++ b/src/vision/roadmap/polish/precise_generator_captures.md @@ -0,0 +1,19 @@ +# Precise Generator Captures + +## Impact + +* Users can predict and understand why the compiler raises error messages. Errors are aligned with an experienced user's intuition about how Rust works. +* Compiler analysis and code generation passes are precise and not unnecessarily conservative. + +## Milestones + +| Milestone | Status | Key Participants | +| --- | --- | --- | +| Prototyped | 🦀 | [eholk] | +| Documented in Rust Reference | 🦀 | [eholk] | +| [Lang team] initiative proposal | 💤 | [eholk] | +| [Lang team] signoff | 💤 | [Lang team] | +| Stabilized | 💤 | [eholk] | + +[eholk]: https://github.com/eholk/ +[Lang team]: https://www.rust-lang.org/governance/teams/lang diff --git a/src/vision/roadmap/polish/stacktraces.md b/src/vision/roadmap/polish/stacktraces.md new file mode 100644 index 00000000..1f4f09c4 --- /dev/null +++ b/src/vision/roadmap/polish/stacktraces.md @@ -0,0 +1,10 @@ +# Stacktraces + +## Impact + +* Async stacktraces contain only the information that people need to figure out what has happened, and are free of extraneous or runtime-internal details +* Users are able to recover the full, unabridged stacktrace if needed + +See the [design notes] for details about the current state of async stack traces and proposals for how to improve them. + +[design notes]: ../../../design_docs/async_stack_traces.md diff --git a/src/vision/roadmap/polish/sync_and_async.md b/src/vision/roadmap/polish/sync_and_async.md new file mode 100644 index 00000000..a4c1080a --- /dev/null +++ b/src/vision/roadmap/polish/sync_and_async.md @@ -0,0 +1,115 @@ +# Sync and async behave the same + +## Impact + +**Async code should not be surprising.** In general, if you surround a block of +synchronous code with `async` or mark a sync `fn` as `async`, nothing +unexpected should happen. + +* The code should evaluate to the same value after awaiting. +* Any compilation errors should be essentially the same, modulo details around implicit futures in the return type. + +## Milestones + +| Milestone | Status | Key Participants | +| --- | --- | --- | +| Define "behave the same" | 💤 | [yoshuawuyts] | +| Create testing to ensure same behavior | 💤 | [yoshuawuyts] | + +[yoshuawuyts]: https://github.com/yoshuawuyts + +## Details + +Ideally, there should not be a lot of work needed specifically to achieve this goal. +Instead, the primary aim is to define principles that can inform the design in other places. +That said, automated testing to verify we have achieved these principles may take significant effort. + +There are several ways we can look at what it means to behave the same. +One way is from a language and semantics standpoint, while another is from a library and ecosystem standpoint. +We will look at each of these in more detail, and then lay out some ideas for automated testing. + +### Language and Semantics + +Roughly what we want here is that code that differs only in its syncness should do the same thing. +Of course, this is not strictly possible because a sync and async program are fundamentally different programs. +We still want something approximating this. +Below are several principles that try to make this more precise. +For each one, we are talking about a synchronous and asynchronous version of a piece of code where the synchronous version is basically the async version with all the `async` and `.await` keywords removed. +Or conversely, we can view the async version as the sync version where all `fn`s have been replaced with `async fn` and all calls have `.await` added. +Note that this assumes there are no manually implemented futures. +This is an intentionally restrictive subset to focus on the core language semantics. +In the Library and Ecosystem section, we will discuss replacing standard library functionality with async equivalents to make this comparison more interesting. + +1. **Equality**: if the sync version and the async version both produce a value, then the values are the same. +2. **Effects**: the same set of observable effects happen in both the sync and async version, and the effects happen in the same order, at least where order is specified. Effects here includes things such as writing to a file (although realistically the async version should use the async version of the File I/O API), observable memory reads or writes. +3. **Termination**: either both the sync and async version terminate (or can be polled to completion in the async case), or both do not terminate. Note that this is a special case of **Effects**. +4. **Panic**: the sync version panics if and only if the async version panics. Note that this is a special case of **Effects**. +5. **Types***: if the sync version of a function returns type `T` then the async version returns type `Future` and vice-versa. Functions or closures passed as parameters would undergo a similar transformation. +6. **Compilation***: either both the sync and async version compile successfully, or they both produce equivalent compiler errors on the same line. + +The first four principles are probably not terrible hard to achieve. +The last two, marked with an asterisk, may not be completely possible or even desirable in all cases. + +For types, there is a fundamental difference in the async code because `.await` points expose types that would be purely internal in the sync version. +One impact of this is that the auto traits may not be the same between the two. +We might be able to get this property in one direction though. +For example, adding a `.await` might make the future not `Send`, but removing a `.await` will probably not remove any auto traits. +See the following code for more detail: + +```rust +fn sync_foo() { + let t = NonSend { ... }; + bar(); // `sync_foo` is `Send` with or without this line. +} + +async fn async_foo() { + let t = NonSend { ... }; + bar().await; // With this line, the future returned by `async_foo` is `!Send` + // because NonSend is `!Send` and is alive across the `.await` + // point. Without this line, the future returned by `async_foo` + // is `Send`. +} +``` + +The key difference between the sync version and the async version here is that the suspension introduced by the `.await` point reveals internal details of `async_foo` that are not observable in the `sync_foo` case. + +Compilation is closely related to the types goal because if async causes the types to change then this could introduce or remove compilation errors. +Additionally, we will probably have some async-only diagnostics, such as the [`must_not_suspend` lint][must_not_suspend]. + +### Library and Ecosystem + +At a high level, the library and ecosystem goals are about having comparable capabilities available in libraries for both sync and async code. +For example, mutexes in an async context need integration with the runtime, so the standard synchronous mutex is not generally suitable for async code, although there are cases where a sync mutex makes sense [[1]], [[2]]. +For this reason, most async runtimes provide some form of `AsyncMutex`. + +[1]: https://ryhl.io/blog/async-what-is-blocking/ +[2]: https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/ch20.html#the-group-table-synchronous-mutexes + +Note that one way to achieve a comparable sync and async library ecosystem may be through [Async Overloading]. + +One way to approach this is to generalize the mostly mechanical transformation we described above to also include translating library calls, and then define what properties we would want to be preserved during the translation. +We would assume for synchronous blocking APIs, such as File I/O, the `Read` and `Write` traits, etc., we have corresponding async File I/O APIs, `AsyncRead` and `AsyncWrite` traits, etc. +The [async-std] project showed that most of the Rust standard library can be pretty directly translated into async code, other than cases where there were missing language features such as [async drop], [async traits], and [async closures]. + + +[async-std]: https://async.rs/ +[async-traits]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap.html +[async drop]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html +[async-closures]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_closures.html + + +🛠️ This area is still underdeveloped, so if you would like to help this would be a great place to pitch in! + +#### Open, Related Questions + +- Should `async::iter::once` take `Future` or `T`? + - Similarly for `async::iter::empty` + - And `async::iter::repeat` (one future to completion and yield return value repeatedly) + - `async::iter::repeat_with` would almost certainly want to take an async closure + +### Automated Testing + +🛠️ This area is still underdeveloped, so if you would like to help this would be a great place to pitch in! + +[Async Overloading]: ../async_overloading.md +[must_not_suspend]: ./lint_must_not_suspend.md diff --git a/src/vision/roadmap/portable.md b/src/vision/roadmap/portable.md new file mode 100644 index 00000000..86d1905a --- /dev/null +++ b/src/vision/roadmap/portable.md @@ -0,0 +1,9 @@ +# Portable across runtimes, easy to switch + +## Impact + +* Able to grab libraries from crates.io and mix-and-match them with confidence, no matter what runtime or other libraries you are using +* Able to easily author libraries that can be combined with other libraries and are independent of runtime +* Able to easily change applications between runtimes to explore new possibilities +* Able to easily author new runtimes that try out a new execution strategy or for some new environment and have them interoperate with most extant libraries, without the need to change those libraries +* Able to find runtimes that fit a wide variety of scenarios and use patterns diff --git a/src/vision/roadmap/portable/read_write.md b/src/vision/roadmap/portable/read_write.md new file mode 100644 index 00000000..9b44a1b5 --- /dev/null +++ b/src/vision/roadmap/portable/read_write.md @@ -0,0 +1,164 @@ +# Async read/write + +## Impact + +* Able to abstract over "something readable" and "something writeable" +* Able to use these traits with `dyn Trait` +* Able to easily write wrappers that "instrument" other readables/writeables +* Able to author wrappers like SSL, where reading may require reading *and* writing on the underlying data stream + +## Design notes + +### Challenge: Permitting simultaneous reads/writes + +The obvious version of the existing `AsyncRead` and `AsyncWrite` traits would be: + +```rust +#[repr(inline_async)] +trait AsyncRead { + async fn read(&mut self, buf: &mut [u8]) -> std::io::Result; +} + +#[repr(inline_async)] +trait AsyncWrite { + async fn write(&mut self, buf: &[u8]) -> std::io::Result; +} +``` + +This form doesn't permit one to simultaneously be reading and writing. Moreover, SSL requires changing modes, so that e.g. performing a read may require writing to the underlying socket, and vice versa. (Link?) + +Note also that using `std::io::Result` would make the traits unusable in `#[no_std]` (this is also the case with the regular `Read` and `Write` traits), which might preclude embedded uses of these traits. These fundamental traits could all be added to `alloc` (but not `core`, because `std::io::Error` depends on `Box`). + +### Variant A: Readiness + +One possibility is the design that [CarlLerche proposed](https://gist.github.com/carllerche/5d7037bd55dac1cb72891529a4ff1540), which separates "readiness" from the actual (non-async) methods to acquire the data: + +````rust +pub struct Interest(...); +pub struct Ready(...); + +impl Interest { + pub const READ = ...; + pub const WRITE = ...; +} + +#[repr(inline)] +pub trait AsyncIo { + /// Wait for any of the requested input, returns the actual readiness. + /// + /// # Examples + /// + /// ``` + /// async fn main() -> Result<(), Box> { + /// let stream = TcpStream::connect("127.0.0.1:8080").await?; + /// + /// loop { + /// let ready = stream.ready(Interest::READABLE | Interest::WRITABLE).await?; + /// + /// if ready.is_readable() { + /// let mut data = vec![0; 1024]; + /// // Try to read data, this may still fail with `WouldBlock` + /// // if the readiness event is a false positive. + /// match stream.try_read(&mut data) { + /// Ok(n) => { + /// println!("read {} bytes", n); + /// } + /// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + /// continue; + /// } + /// Err(e) => { + /// return Err(e.into()); + /// } + /// } + /// + /// } + /// + /// if ready.is_writable() { + /// // Try to write data, this may still fail with `WouldBlock` + /// // if the readiness event is a false positive. + /// match stream.try_write(b"hello world") { + /// Ok(n) => { + /// println!("write {} bytes", n); + /// } + /// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { + /// continue + /// } + /// Err(e) => { + /// return Err(e.into()); + /// } + /// } + /// } + /// } + /// } + /// ``` + async fn ready(&mut self, interest: Interest) -> io::Result; +} + +pub trait AsyncRead: AsyncIo { + fn try_read(&mut self, buf: &mut ReadBuf<'_>) -> io::Result<()>; +} + +pub trait AsyncWrite: AsyncIo { + fn try_write(&mut self, buf: &[u8]) -> io::Result; +} +```` + +This allows users to: + +- Take `T: AsyncRead`, `T: AsyncWrite`, or `T: AsyncRead + AsyncWrite` + +Note that it is always possible to ask whether writes are "ready", even for a read-only source; the answer will just be "no" (or perhaps an error). + +#### Can we convert all existing code to this form? + +The `try_read` and `try_write` methods are basically identical to the existing "poll" methods. So the real question is what it takes to implement the `ready` async function. Note that tokio internally already adopts a model very similar to this on many types (though there is no trait for it). + +It seems like the torture case to validate this is openssl. + +### Variant B: Some form of split + +Another alternative is to have read/write traits and a way to "split" a single object into separate read/write traits: + +```rust +#[repr(inline_async)] +trait AsyncRead { + async fn read(&mut self, buf: &mut [u8]) -> std::io::Result; +} + +#[repr(inline_async)] +trait AsyncWrite { + async fn write(&mut self, buf: &[u8]) -> std::io::Result; +} + +#[repr(inline_async)] +trait AsyncBidirectional: AsyncRead + AsyncWrite { + async fn split(&mut self) -> (impl AsyncRead + '_, impl AsyncWrite + '_) +} +``` + +The challenge here is to figure out exactly how that definition should look. The version I gave above includes the possibility that the resulting readers/writers have access to the fields of `self`. + +### Variant C: Extend traits to permit expressing that functions can both execute + +Ranging further out into unknowns, it is possible to imagine extending traits with a way to declare that two `&mut self` methods could both be invoked concurrently. This would be generally useful but would be a fundamental extension to the trait system for which we don't really have any existing design. There is a further complication that the `read` and `write` methods are in distinct traits (`AsyncRead` and `AsyncWrite`, respectively) and hence cannot + +```rust +#[repr(inline_async)] +trait AsyncRead { + async fn read(&mut self, buf: &mut [u8]) -> std::io::Result; + async fn write(&mut self, buf: &[u8]) -> std::io::Result; +} + +#[repr(inline_async)] +trait AsyncWrite { +} + +#[repr(inline_async)] +trait AsyncBidirectional: AsyncRead + AsyncWrite { + async fn split(&mut self) -> (impl AsyncRead + '_, impl AsyncWrite + '_) +} +``` + +### Variant D: Implement the `AsyncRead` and `AsyncWrite` traits for `&T` + +In `std`, [there are `Read` and `Write` impls for `&File`](https://doc.rust-lang.org/std/fs/struct.File.html#impl-Read-1), and [the async-std runtime has followed suit](https://docs.rs/async-std/1.9.0/async_std/fs/struct.File.html#impl-Read-1). This means that you can express "can do both `AsyncRead + AsyncWrite`" as `AsyncRead + AsyncWrite + Copy`, more or less, or other similar tricks. However, it's not possible to do this for _any type_. Worth exploring. diff --git a/src/vision/roadmap/portable/runtime.md b/src/vision/roadmap/portable/runtime.md new file mode 100644 index 00000000..3329b279 --- /dev/null +++ b/src/vision/roadmap/portable/runtime.md @@ -0,0 +1,94 @@ +# Runtime + +## Impact + +* Able to write simple, non-generic async Rust code that performs common operations like opening TCP sockets, sending UDP packets, accessing files, sleeping, and spawning tasks, but which is not specific to a particular runtime. +* Able to retarget code that relies on these APIs across different runtimes with no effort. + +## Design notes + +When writing sync code, it is possible to simply _access_ I/O and other facilities without needing to thread generics around: + +```rust +fn load_socket_addr() -> Result> { + Ok(std::fs::read_to_string("address.txt")?.parse()?) +} +``` + +This code will work no matter what operating system you run it on. + +Similarly, if you don't mind hard-coding your runtime, one can use `tokio` or `async_std` in a similar fashion + +```rust +// Pick one: +// +// use tokio as my_runtime; +// use async_std as my_runtime; + +async fn load_socket_addr() -> Result> { + Ok(my_runtime::fs::read_to_string("address.txt").await?.parse()?) +} +``` + +Given suitable traits in the stdlib, it would be possible to write generic code that feels similar: + +```rust +async fn load_socket_addr() -> Result> { + Ok(F::read_to_string("address.txt").await?.parse()?) +} +``` + +Alternatively, that might be done with `dyn` trait: + +```rust +async fn load_socket_addr(fs: &dyn AsyncFs)) -> Result> { + Ok(F::read_to_string("address.txt").await?.parse()?) +} +``` + +Either approach is significantly more annoying, both as the author of the library and for folks who invoke your library. + +### Preferred experience + +The ideal would be that you can write an async function that is "as easy" to use as a non-async one, and have it be portable across runtimes: + +```rust +async fn load_socket_addr() -> Result> { + Ok(std::async_fs::read_to_string("address.txt").await?.parse()?) +} +``` + +### But how to achieve it? + +The basic idea is to extract out a "core API" of things that a runtime must provide and to make those functions available as part of the `Context` that `Async` values are invoked with. To avoid the need for generics and monomorphization, this would have to be based purely on `dyn` values. This interface ought to be compatible with no-std runtimes as well, which imposes some challenges. + +## Frequently asked questions + +### What about async overloading? + +Good question! The [async overloading](../async_overloading.md) feature may be another, better route to this same goal. At minimum it implies that `std::async_fs` etc might not be the right names (although those modules could be deprecated and merged going forward). + +It definitely suggests that the names and signatures of all functions, methods, and types should be kept very strictly analogous. In particular, sync APIs should be a subset of async APIs. + +### What about cap-std? + +It's interesting to observe that the `dyn` approach is feeling very close to [cap-std](https://blog.sunfishcode.online/introducing-cap-std/). That might be worth taking into consideration. Some targets, like wasm, may well prefer if we took a more "capability oriented" approach. + +### What about spawning and scopes? + +Given that spawning should occur through scopes, it may be that we don't need a `std::async_thread::spawn` API so much as standards for scopes. + +### What about evolving the API? + +We will want to be able to start with a small API and grow it. How is that possible, given that the *implementation* of the API lives in external runtimes? + +### What methods are needed? + +We need to cover the things that exist in the sync stdlib + +* spawn, spawn-blocking +* timers (sleep) +* TCP streams, UDP sockets +* file I/O +* channels and other primitives + * mutexes? diff --git a/src/vision/roadmap/portable/spawn.md b/src/vision/roadmap/portable/spawn.md new file mode 100644 index 00000000..f424994b --- /dev/null +++ b/src/vision/roadmap/portable/spawn.md @@ -0,0 +1,6 @@ +# Async spawn, spawn-blocking + +## Impact + +* Able to write libraries or applications that use a trait to spawn async or blocking tasks without referring to a particular runtime +* Able to use the trait in a dyn-safe fashion \ No newline at end of file diff --git a/src/vision/roadmap/portable/timers.md b/src/vision/roadmap/portable/timers.md new file mode 100644 index 00000000..e4088110 --- /dev/null +++ b/src/vision/roadmap/portable/timers.md @@ -0,0 +1,6 @@ +# Async timer + +## Impact + +* Able to write libraries or applications that use a trait to create a timer without referring to a particular runtime +* Able to use the trait in a dyn-safe fashion \ No newline at end of file diff --git a/src/vision/roadmap/scopes.md b/src/vision/roadmap/scopes.md new file mode 100644 index 00000000..6623aa9f --- /dev/null +++ b/src/vision/roadmap/scopes.md @@ -0,0 +1,127 @@ +# Scopes + +## Impact + +* Able to spawn parallel tasks or blocking work that accesses borrowed data +* Easily create expressive scheduler patterns that make use of borrowed data using high-level combinators and APIs +* When data is no longer needed, able to cancel work and have it reliably and promptly terminate, including any subtasks or other bits of work it may have created +* Cancellation does not leave work "half-finished", but reliably cleans up program state +* Able to use DMA, io-uring, etc to write directly into output buffers, and to recover in the case of cancellation + +## Requires + +* [capability](./scopes/capability.md) +* [APIs](./scopes/api.md) + +## Design notes + +Async functions are commonly written with borrowed references as arguments: + +```rust +async fn do_something(db: &Db) { ... } +``` + +but important utilities like `spawn` and `spawn_blocking` require `'static` tasks. Building on non-cancelable traits, we can implement a "scope" API that allows one to introduce an async scope. This scope API should permit one to spawn tasks into a scope, but have various kinds of scopes (e.g., synchronous execution, parallel execution, and so forth). It should ultimately reside in the standard library and hook into different runtimes for scheduling. This will take some experimentation! + +```rust +async fn foo(db: &Database) { + let result = std::async_thread::scope(|s| { + let job1 = s.spawn(async || { + async_thing(db) + }); + let job2 = s.spawn_blocking(|| { + sync_thing(db) + }); + + (job1.await, job2.await) + }).await; +} +``` + +### Side-stepping the nested await problem + +One goal of scopes is to avoid the "nested await" problem, as described in [Barbara battles buffered streams (BBBS)][bbbs]. The idea is like this: the standard combinators which run work "in the background" and which give access to intermediate results from that work should schedule that work into a scope.[^hard] This would typically be done by using an "interior iterator" pattern, but it could also be done by taking a scope parameter. Some examples from today's APIs are `FuturesUnordered` and `Stream::buffered`. + +[^hard]: This is not a hard rule. But invoking poll manually is best regarded as a risky thing to be managed with care -- not only because of the formal safety guarantees, but because of the possibility for "nested await"-style failures. + +[bbbs]: https://rust-lang.github.io/wg-async/vision/status_quo/barbara_battles_buffered_streams.html +[`buffered`]: https://docs.rs/futures/0.3.15/futures/prelude/stream/trait.StreamExt.html#method.buffered + +In the case of [BBBS], the problem arises because of `buffered`, which spawns off concurrent work to process multiple connections. Under this system, the implementation of `buffered` would create an internal scope for spawn its tasks into that scope, side-stepping the problem. One could imagine also offering a variant of `buffered` like `buffered_in` that takes a scope parameter, permitting the user to choose the scope of those spawned tasks: + +```rust +async fn do_work(database: &Database) { + std::async_thread::scope(|s| { + let work = do_select(database, FIND_WORK_QUERY).await?; + std::async_iter::from_iter(work) + .map(|item| do_select(database, work_from_item(item))) + .buffered_in(5, scope) + .for_each(|work_item| process_work_item(database, work_item)) + .await; + }).await; +} +``` + +### Concurrency without scopes: Join, select, race, and friends + +It is possible to introduce concurrency in ways that both (a) do not require scopes and (b) avoid the "nested await" problem. Any combinator which takes multiple `Async` instances and polls them to completion (or cancels them) before it itself returns is ok. This includes: + +- `join`, because the `join(a, b)` doesn't complete until both `a` and `b` have completed; +- `select`, because selecting will cancel the alternatives that are not chosen; +- `race`, which is a variant of select. + +This is important because embedded systems often avoid allocators, and the scope API implicitly requires allocation (one can spawn an unbounded number of tasks). + +### Cancellation + +In today's Rust, any async function can be synchronously cancelled at any await point: the code simply stops executing, and destructors are run for any extant variables. This leads to a lot of bugs. (TODO: link to stories) + +Under systems like [Swift's proposed structured concurrency model](https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md), or with APIs like [.NET's CancellationToken](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=net-5.0), cancellation is "voluntary". What this means is that when a task is cancelled, a flag is set; the task can query this flag but is not otherwise affected. Under structured concurrency systems, this flag is propagated to all chidren (and transitively to their children). + +[preemption]: https://tokio.rs/blog/2020-04-preemption + +Voluntary cancellation is a requirement for [scoped access](./scoped.md). If there are parallel tasks executing within a scope, and the scope itself is canceled, those parallel tasks must be joined and halted before the memory for the scope can be freed. + +One downside of such a system is that cancellation _may not_ take effect. We can make it more likely to work by integrating the cancellation flag into the standard library methods, similar to how tokio encourages ["voluntary preemption"][preemption]. This means that file reads and things will start to report errors (`Err(TaskCanceled)`) once the task has been canceled. This has the advantage that it exercises existing error paths and permits recovery. + +### Cancellation and `select` + +The `select` macro chooses from N futures and returns the first one that matches. Today, the others are immediately canceled. This behavior doesn't play especially well with voluntary cancellation. There are a few options here: + +- We could make `select` signal cancellation for each of the things it is selecting over and then wait for them to finish. +- We could also make `select` continue to take `Future` (not `Async`) values, which effectively makes `Future` a "cancel-safe" trait (or perhaps we introduce a `CancelSafe` marker trait that extends `Async`). + - This would mean that typical `async fn` could not be given to select, though we might allow people to mark `async fn` as "cancel-safe", in which case they would implement `Future`. They would also not have access to ordinary async fn, though. + - Effectively, the current `Future` trait becomes the "cancel-safe" form of `Async`. This is a bit odd, since it has other distinctions, like using `Pin`, so it might be preferable to use a 'marker trait'. + - Of course, users could spawn a task that calls the function and give the handle to select. + +## Frequently asked questions + +### Could there be a convenient way to access the current scope? + +If we wanted to integrate the idea of scopes more deeply, we could have some way to get access to the current scope and reference its lifetime. Lots of unknowns to work out here, though. For example, suppose you have a function that creates a scope and invokes a closure within. Do we have a way to indicate to the closure that `'scope` in that closure may be different? + +It starts to feel like simply passing "scope" values may be simpler, and perhaps we need a way to automate the threading of state instead. Another advantage of passing a `scope` explicitly is that it is clear when parallel tasks may be launched. + +### How does cancellation work in other settings? + +Many other languages use a shard flag to observe when cancellation has been requested. + +In some languages, there is also an immediate callback that is invoked when cancellation is requested which permits you to take immediate action. [Swift proposal E0304](https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md#cancellation-handlers), for example, includes "cancellation handlers" that are run immediately. + +* [Kotlin cancellation](https://kotlinlang.org/docs/cancellation-and-timeouts.html): + * You can invoke `cancel` on launched jobs (spawned tasks). + * Cancelling sets a flag that the job can check for. + * Builtin functions check for the flag and throw an exception if it is set. + * If you need a builtin function to run post-cancellation, you can [run the code in a "non-cancelable" context](https://kotlinlang.org/docs/cancellation-and-timeouts.html#run-non-cancellable-block). + +### What is the relationship between AsyncDrop and cancellation? + +In async Rust today, one signals cancellation of a future by (synchronously) dropping it. This _forces_ the future to stop executing, and drops the values that are on the stack. Experience has shown that this is someting users have a lot of trouble doing correctly, particularly at fine granularities (see e.g. [Alan builds a cache](../submitted_stories/status_quo/alan_builds_a_cache.md) or [Barbara gets burned by select](../submitted_stories/status_quo/barbara_gets_burned_by_select.md)). + +Given `AsyncDrop`, we could adopt a similar convention, where canceling an `Async` is done by (asynchronously) dropping it. This would presumably amend the unsafe contract of the `Async` trait so that the value must be polled to completion _or_ async-dropped. To avoid the footguns we see today, a typical future could simply continue execution from its `AsyncDrop` method (but disregard the result). It might however set an internal flag to true or otherwise allow the user to find out that it has been canceled. It's not clear, though, precisely what value is being added by `AsyncDrop` in this scenario versus the `Async` simply not implementing `AsyncDrop` -- perhaps though it serves as an elegant way to give both an immediate "cancellation" callback and an opportunity to continue. + +An alternative is to use a cancellation token of some kind, so that _scopes_ can be canceled and that cancelation can be observed. The main reason to have that token or observation mechanism be "built-in" to some degree is so that it can be observed and used to drive "voluntary cancellation" from I/O routines and the like. Under that model, `AsyncDrop` would be intended more for values (like database handles) that have cleanup to be done, much like `Drop` today, and less as a way to signal cancellation. + +## Related Work + +- [Async Cancellation I (Yoshua Wuyts, 2021)](https://blog.yoshuawuyts.com/async-cancellation-1/) diff --git a/src/vision/roadmap/scopes/capability.md b/src/vision/roadmap/scopes/capability.md new file mode 100644 index 00000000..fc0c6ef1 --- /dev/null +++ b/src/vision/roadmap/scopes/capability.md @@ -0,0 +1,24 @@ +# Capability + +## Impact + +* The ability to create async tasks that can be safely given access to borrowed data, similar to crossbeam or rayon scopes +* There are potentially multiple routes with which this can be accomplished + +## Design notes + +Today's `Future` trait lacks one fundamental capability compared to synchronous code: there is no (known?) way to "block" your caller and be sure that the caller will not continue executing until you agree. In synchronous code, you can use a closure and a destructor to achieve this, which is the technique used for things like `rayon::scope` and crossbeam's scoped threads. In async code, because the `Future` trait has a safe poll function, it is always possible to poll it part way and then `mem::forget` (or otherwise leak) the value; this means that one cannot have parallel threads executing and using those references. + +Async functions are commonly written with borrowed references as arguments: + +```rust +async fn do_something(db: &Db) { ... } +``` + +but important utilities like `spawn` and `spawn_blocking` require `'static` tasks. Without "unfogettable" traits, the only way to circumvent this is with mechanisms like `FuturesUnordered`, which is then subject to footguns as described in [Barbara battles buffered streams](https://rust-lang.github.io/wg-async/vision/status_quo/barbara_battles_buffered_streams.html). + +There are two main approaches under consideration to address this issue: + +* [Introducing a new trait for futures, Async](./capability/variant_async_trait.md) +* [Introducing a new "default" trait, Leak](./capability/variant_leak.md) that can be used to prevent values from leaking + * If we say that scopes cannot be leaked, and the scope defines `AsyncDrop`, then we can (presumably) be sure that its destructor will run. diff --git a/src/vision/roadmap/scopes/capability/variant_async_trait.md b/src/vision/roadmap/scopes/capability/variant_async_trait.md new file mode 100644 index 00000000..4d14a800 --- /dev/null +++ b/src/vision/roadmap/scopes/capability/variant_async_trait.md @@ -0,0 +1,86 @@ +# Variant: Async trait + +As proposed in https://github.com/Matthias247/rfcs/pull/1, one way to solve this is to introduce a new future trait with an unsafe poll method: + +```rust +trait Async { + type Output; + + /// # Unsafe conditions + /// + /// * Once polled, cannot be moved + /// * Once polled, destructor must execute before memory is deallocated + /// * Once polled, must be polled to completion + /// + /// FIXME: Have to specify how to manage panic. + unsafe fn poll( + &mut self, + context: &mut core::task::Context<'_>, + ) -> core::task::Poll; +} +``` + +This would then require "bridging impls" to convert the (now likely deprecated, or at least repurposed) Future trait: + +```rust +impl Async for F { .. } // impl A +``` + +which in turn creates an interesting question, since if we wish to have a single combinator that is usable with either trait, specialization would be required: + +```rust +impl Future for Combinator { .. } // impl B +impl Async for Combinator { .. } // impl C + +// Coherence error: Given some type `F1: Future`, +// two ways to show that `Combinator: Async`. +``` + +## Bridging + +Introduce "bridge impls" like the following: + +```rust +impl Async for F where F: Future { + +} +``` + +Newer runtimes will be built atop the `Async` trait, but older code will still work with them, since everything that implements `Future` implements `Async`. + +#### Combinators + +One tricky case has to do with bridging combinators. If you have a combinator like `Join`: + +```rust +struct Join { ... } + +impl Future for Join +where + A: Future, + B: Future, +{ } +``` + +This combinator cannot then be used with `Async` values. You cannot (today) add a second impl like the following for coherence reasons: + +```rust +impl Async for Join +where + A: Async, + B: Async, +{ } +``` + +The problem is that this second impl creates multiple routes to implement `Async` for `Join` where `A` and `B` are futures. These routes are of course equivalent, but the compiler doesn't know that. + +### Solution A: Don't solve it + +We might simply introduce new combinators for the `Async` trait. Particularly given the move to [scoped threads](./scoped.md) it is likely that the set of combinators would want to change anyhow. + +### Solution B: Specialization + +Specialization can be used to resolve this, and it would be a great feature for Rust overall. However, specialization has a number of challenges to overcome. Some related articles: + +- [Maximally minimal specialization](https://smallcultfollowing.com/babysteps/blog/2018/02/09/maximally-minimal-specialization-always-applicable-impls/) +- [Supporting blanket impls in specialization](https://smallcultfollowing.com/babysteps/blog/2016/10/24/supporting-blanket-impls-in-specialization/) diff --git a/src/vision/roadmap/scopes/capability/variant_leak.md b/src/vision/roadmap/scopes/capability/variant_leak.md new file mode 100644 index 00000000..c6c341a1 --- /dev/null +++ b/src/vision/roadmap/scopes/capability/variant_leak.md @@ -0,0 +1,3 @@ +# Variant: Leak trait + +(Requires elaboration) diff --git a/src/vision/roadmap/scopes/scope_api.md b/src/vision/roadmap/scopes/scope_api.md new file mode 100644 index 00000000..f63c793d --- /dev/null +++ b/src/vision/roadmap/scopes/scope_api.md @@ -0,0 +1,7 @@ +# API + +## Impact + +* Able to create hierarchical scopes, easily spawn async & blocking tasks within those scopes, and propagate cancellation. +* Able to select any runtime to back the API + diff --git a/src/vision/roadmap/testing.md b/src/vision/roadmap/testing.md new file mode 100644 index 00000000..147973de --- /dev/null +++ b/src/vision/roadmap/testing.md @@ -0,0 +1,13 @@ +# Testing + +## Impact + +* Async applications need the ability to write tests that let them simulate and mock the outside world +* Ability to test edge cases: + * Long latencies + * Dropped connections + * Funky schedules + +## Design notes + +At the moment, this is an "experimentation" area, but it represents a common need without well-established, widely used solutions. \ No newline at end of file diff --git a/src/vision/roadmap/threadsafe_portability.md b/src/vision/roadmap/threadsafe_portability.md new file mode 100644 index 00000000..3162e281 --- /dev/null +++ b/src/vision/roadmap/threadsafe_portability.md @@ -0,0 +1,6 @@ +# Threadsafe portability + +## Impact + +* Able to write code that can be easily made `Send` or not `Send` + * The resulting code is able to switch between helper types, like `Rc` and `Arc`, appropriately. \ No newline at end of file diff --git a/src/vision/roadmap/tooling.md b/src/vision/roadmap/tooling.md new file mode 100644 index 00000000..77cb6096 --- /dev/null +++ b/src/vision/roadmap/tooling.md @@ -0,0 +1,22 @@ +# Tooling + +## Impact + +* Tooling that gives insight into the state of async runtimes + * How many tasks are running and what is their state + * What are tasks blocked on and why? + * Where is memory allocated? + * Where is CPU time spent? + * Flamegraph of where things spend their time + * Perf-style profile of where things spend their time +* Tooling should also allow you to limit profiles to a particular request or to requests that meet particular criteria (e.g., coming from a particular source) +* Tooling should detect common hazards and identify them, suggesting fixes + * Tasks that clone a `Waker` but don't trigger it + * Tasks that don't respond to a request to cancellation for a long time + * Outlier tasks that sleep for a very long time without being awoken +* Tooling should permit "always on" profiling that can be used in production +* Tooling can provide profile-based feedback: + * Where to "heap-allocate" futures + * Poll functions that execute for a long time without yielding + * Imbalanced workloads across cores +* Tooling can be either customized or integrated into existing tools like perf, gdb, lldb, etc, as appropriate \ No newline at end of file diff --git a/src/vision/roadmap/tooling/console.md b/src/vision/roadmap/tooling/console.md new file mode 100644 index 00000000..39327338 --- /dev/null +++ b/src/vision/roadmap/tooling/console.md @@ -0,0 +1,11 @@ +# Console + +https://github.com/tokio-rs/console + +* Able to get live information about the state of the runtime and async tasks from a running program. + * How many tasks are running and what is their state +* Detect common hazards and identify them +* Requires opt-in at program source level + * https://hackmd.io/yjtVRDPjQJq05p_8L0-5UA +* Incurs some amount of overhead + * Todo: quantify overhead incurred diff --git a/src/vision/roadmap/tooling/crashdump.md b/src/vision/roadmap/tooling/crashdump.md new file mode 100644 index 00000000..91bbd073 --- /dev/null +++ b/src/vision/roadmap/tooling/crashdump.md @@ -0,0 +1,3 @@ +# Crashdump + +* Able to get information about the state of the runtime and async tasks from crashdumps. \ No newline at end of file diff --git a/src/vision/shiny_future.md b/src/vision/shiny_future.md index 05fa467e..ead77fb4 100644 --- a/src/vision/shiny_future.md +++ b/src/vision/shiny_future.md @@ -1,21 +1,48 @@ -# ✨ Shiny future: Where we want to get to +# ✨ Shiny future -### 🛑 Not time for this yet 🛑 +This page represents a complete vision for where we want async to go. This vision is what we believe to be the best way to achieve the [experiences](./how_it_feels.md) that we want async to provide. -We're not really ready to work on this section yet. We're still focused on writing out the [status quo](./status_quo.md). +## Work in progress -## What it this +Note that while a lot of the steps needed are fairly clear, several of them also have [significant unknowns or points of controversy](./unresolved_questions.md). We have attempted to highlight those and expect to be working through those points as we go. -The "shiny future" is here to tell you what we are trying to build over the next 2 to 3 years. That is, it presents our "best guess" as to what will look like a few years from now. When describing specific features, it also embeds links to [design notes] that describe the constraints and general plans around that feature. +## Certainty levels -[design notes]: ../design_notes.md +- 🌈 -- Implemented and stable +- 🌞 -- Everything is looking good +- 🌤️ -- Still some stuff to figure out, but unlikely to see major changes in the design +- 🌥️ -- Got one or two solid leads, but still have to figure out if it will work +- 🌧️ -- No clear path yet, this may not even be a good idea -### Think big -- too big, if you have to +## Key aspects of the future -You'll notice that the ideas in this document are **maximalist and ambitious**. They stake out an opinionated position on how the ergonomics of Async I/O should feel. This position may not, in truth, be attainable, and for sure there will be changes along the way. Sometimes the realities of how computers actually work may prevent us from doing all that we'd like to. That's ok. This is a dream and a goal. +* 🌤️ If you know sync Rust, getting started in Async Rust is straightforward ([more](roadmap/async_fn.md)]) + * 🌤️ Mostly, you change `fn` to `async fn`, add some calls to await, and change over to other parts of the stdlib, though supporting `dyn Trait` requires making some choices, particularly in a no-std environment + * 🌤️ It still has that "if it compiles, it generally works, and it runs pretty darn fast" feeling + * 🌤️ Destructors and cleanup also work the same way as in sync Rust, thanks to `Drop` to `AsyncDrop` + * 🌤️ No need to write poll functions or to interact with pin except in quite specialized scenarios +* 🌤️ High-quality documentation and tutorials helps you to get started and learn the ropes + * 🌤️ The docs also identify common patterns for structuring your async programs and their advantages and disadvantages +* 🌥️ Tooling and debugger integration gives insight into the behavior of your program + * 🌥️ Easy to get a snapshot of overall acitivity (e.g. to find out what tasks or exist or why a task is blocked) + * 🌥️ Easy to see aggregate performance trends over time (e.g., number of active connections, waiting connections, etc) + * 🌥️ Easy to profile things in terms of your async tasks (e.g., to get a flamegraph of a specific connection) +* 🌥️ Variety of high-quality runtimes available in cargo, and it's easy to change between them: + * 🌧️ When you use things from the standard library, they work across runtimes automatically + * 🌥️ There are standardized, foundational traits for common operations like I/O, spawning tasks, timers +* 🌥️ Hierarchical scopes allow you to easily spawn parallel and concurrent tasks + * 🌥️ These can reference borrowed data, enabling easy parallel processing of async iterators (think "async rayon") +* 🌥️ Cancellation works well and without surprises + * 🌥️ When cancellation is requested, it propagates to subtasks within a scope + * 🌧️ I/O operations and the like begin to fail, so that cancellation is automatic and flows through familiar error paths + * 🌥️ If desired, you can "opt-in" to synchronous cancellation, in which case any await becomes a cancellation point. This allows your `async fn` to be used with `select` without spawning a task. -We fully expect that the designs and stories described in this document will change as we work towards realizing them. When there are areas of particular uncertainty, we use the Frequently Asked Questions and the design docs to call them out. +## Learn more -## Where are the stories? +Check out... -We haven't written these yet! \ No newline at end of file +* [The user's manual of the future](./shiny_future/users_manual.md) + +## Where did all the stories go? + +The full set of "submitted" shiny future stories [have been moved here](./submitted_stories/shiny_future.md). diff --git a/src/vision/shiny_future/template.md b/src/vision/shiny_future/template.md deleted file mode 100644 index 79ee7e18..00000000 --- a/src/vision/shiny_future/template.md +++ /dev/null @@ -1,62 +0,0 @@ -# ✨ Shiny future stories: template - -*This is a template for adding new "shiny future" stories. To propose a new shiny future PR, do the following:* - -* *Create a new file in the [`shiny_future`] directory named something like `Alan_loves_foo.md` or `Grace_does_bar_and_its_great.md`, and start from [the raw source from this template]. You can replace all the italicized stuff. :)* -* *Do not add a link to your story to the [`SUMMARY.md`] file; we'll do it after merging, otherwise there will be too many conflicts.* - -*For more detailed instructions, see the [How To Vision: Shiny Future] page!* - -[How To Vision: Shiny Future]: ../how_to_vision/shiny_future.md -[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async-foundations/master/src/vision/shiny_future/template.md -[`shiny_future`]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/shiny_future -[`SUMMARY.md`]: https://github.com/rust-lang/wg-async-foundations/blob/master/src/SUMMARY.md - -### 🛑 Not time for this yet 🛑 - -We are not yet accepting shiny future story ideas! - -## The story - -*Write your story here! Feel free to add subsections, citations, links, code examples, whatever you think is best.* - -## 🤔 Frequently Asked Questions - -### **What status quo story or stories are you retelling?** -*Link to the status quo stories here. If there isn't a story that you're retelling, [write it](../how_to_vision/status_quo.md)!* - -### **What is [Alan] most excited about in this future? Is he disappointed by anything?** -*Think about Alan's top priority (performance) and the expectations he brings (ease of use, tooling, etc). How do they fare in this future?* - -### **What is [Grace] most excited about in this future? Is she disappointed by anything?** -*Think about Grace's top priority (memory safety) and the expectations she brings (still able to use all the tricks she knows and loves). How do they fare in this future?* - -### **What is [Niklaus] most excited about in this future? Is he disappointed by anything?** -*Think about Niklaus's top priority (accessibility) and the expectations he brings (strong community that will support him). How do they fare in this future?* - -### **What is [Barbara] most excited about in this future? Is she disappointed by anything?** -*Think about Barbara's top priority (productivity, maintenance over time) and the expectations she brings (fits well with Rust). How do they fare in this future?* - -### **If this is an alternative to another shiny future, which one, and what motivated you to write an alternative?** (Optional) -* *Cite the other story. Be specific, but focus on what you like about your version, not what you dislike about the other.* -* *If this is not an alternative, you can skip this one.* - -### **What [projects] benefit the most from this future?** - -### **Are there any [projects] that are hindered by this future?** - -### **What are the incremental steps towards realizing this shiny future?** (Optional) -* *Talk about the actual work we will do. You can link to [design docs](../design_docs.md) or even add new ones, as appropriate.* -* *You don't have to have the whole path figured out yet!* - -### **Does realizing this future require cooperation between many projects?** (Optional) -*For example, if you are describing an interface in libstd that runtimes will have to implement, talk about that.* - -[character]: ../characters.md -[comment]: ./comment.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[projects]: ../projects.md diff --git a/src/vision/shiny_future/users_manual.md b/src/vision/shiny_future/users_manual.md new file mode 100644 index 00000000..20aefd63 --- /dev/null +++ b/src/vision/shiny_future/users_manual.md @@ -0,0 +1,356 @@ +# User's Manual of the Future + +![I always dreamed of seeing the future](https://media.giphy.com/media/ZhESFK96NxbuO1yDgy/giphy.gif) + +This text is written from the perspective of async Rust's "shiny future". It describes the async Rust that future users will experience. Embedded within are links of the form "deliv_xxx" that connect to the specific deliverables that are being described. + +*Note:* Not everything in the future is great. Search for "Caveat" and you'll find a few notes of problems that we don't expect to fix. + +## Introduction: Async I/O as a user + +### What is async I/O? + +These days, most Rust code that interacts with the network or does high-performance I/O is Async I/O. Async I/O is, in some sense, an implementation detail. It is a set of language extensions that make it easy to run many asynchronous tasks using only a small number of underlying *operating system threads*. This means that you can scale up to a very large number of tasks using only a small amount of resources. To be frank, for many applications, async I/O is overkill. However, there are some for which it is absolutely essential, and that's why most of the high quality libraries are using asynchronous interfaces. Fortunately, async Rust is quite easy to use, so even if you don't really *need* the power right now, that's not a problem. + +### Choosing a runtime + +When you use sync Rust, operations like I/O and so forth are taken care of by your operating system (or your libc implementation, in any case). When you use *async* Rust, though, the mapping between asynchronous tasks is performed by a *library*, called a runtime. One of Rust's key distinguishing features is that it doesn't bake in the choice of a runtime. This means that people are free to develop libaries which use a variety of different strategies to schedule tasks, I/O, and so forth. The choice of runtime can in some cases make a big difference to your overall performance, or what kind of environments you can run in. + +If this seems overwhelming, don't worry. Rust makes it easy to experiment with runtimes and try different ones ([deliv_portable]). Here is a list of some of the popular runtimes, and the sorts of applications where they are suitable: + +* General purpose, good for just about anything: tokio, async-std +* High-performance file I/O, thread-per-core architecture: glommio +* Focused on reliability: bastion +* Embedded environments: embassy + +If you are not sure what's best for you, we recommend picking any of the general purpose runtimes. + +### Async fn: where it all starts + +Getting started with async Rust is easy. Most anywhere that you write `fn` in Rust, you can now write `async fn` (exception: extern blocks), starting with the main function: + +```rust +#[tokio::main] // or async_std::main, glommio::main, etc +async fn main() { + println!("Hello, world!"); // <-- expect a warning here +} +``` + +You can see that we decorated `main` with a `#[tokio::main]` attribute. This is how we select the runtime we will use: most runtimes emit a similar decorator, so you could change this to `#[async_std::main]`, `#[glommio::main]`, or `#[embassy::main]` and all the examples and code we talk about in this document would work just the same. ([deliv_portable]) + +Whichever runtime you choose, if you actually try to compile this, you're going to see that you get a warning ([deliv_lint_blocking_fn]): + +``` + println!("Hello, world!"); + ^^^^^^^ synchronous I/O in async fn +``` + +This is because macros like `println!` expand to *blocking* operations, that take control of the underlying thread and don't allow the scheduler to continue. You need to use the async equivalent ([deliv_portable_stdlib]), then `await` the result: + +```rust +async fn main() { + async_println!("Hello, world!").await; +} +``` + +When you `await` on something, you are pausing execution and waiting for it to complete before you continue. *Under the hood*, an await corresponds to giving up the current thread of control so that the runtime can do something else instead while you wait (e.g., process another task). + +### Documentation and common patterns + +This document is a survey of some of the major aspects of writing async functions. If you'd like a deeper introduction, the async book both explains how to get started in async but also common patterns, mistakes to avoid, and some of the details of the various runtimes you can choose from. ([deliv_documentation]) + +### Async iterators + +So far, using `async` seems like mostly more work to accomplish the same thing, since you have to add `await` keywords everywhere. But async functions are like synchronous functions with superpowers: they have the ability to easily compose complex schedules of parallel and concurrent workloads. This is particularly true when you start messing around with asynchronous iterators. + +Consider this example. Imagine that you have a bunch of networking requests coming in. For each one, you have to do a bit of lightweight preparation, and then some heavyweight processing. This processing can take up a lot of RAM, and takes a while, so you can only process one request at a time, but you would like to do up to 5 instances of that lightweight preparation in parallel while you wait, so that things are all queued up and ready to go. You want a schedule like this, in other words: + +``` + ┌───────────────┐ + │ Preparation 1 │ ─────┐ + └───────────────┘ │ + │ + ┌───────────────┐ │ ┌───────────────┐ + │ Preparation 2 │ ─────┼────►│ Process item │ ─────► + └───────────────┘ │ └───────────────┘ + │ + ... │ + │ + ┌───────────────┐ │ + │ Preparation 5 │ ─────┘ + └───────────────┘ +``` + +You can create that quite easily: + +```rust +async fn do_work(database: &Database) { + let work = do_select(database, FIND_WORK_QUERY)?; + stream::iter(work) + .map(async |item| preparation(database, item).await) + .buffered(5) + .for_each(async |work_item| process_work_item(database, work_item).await) + .await; +} +``` + +The `buffered` combinator on async iterators creates a schedule that does up to 5 items in parallel, but still produces one item at a time as the result. Thus `for_each` executes on only one item at a time. + +How does all this work? The basic `AsyncIterator` trait ([deliv_async_iter]) looks quite similar to the standard `Iterator` trait, except that it has an `async fn` (this fn also has a `#[repr]` annotation; you can ignore it for now, but we discuss it later). + +```rust +trait AsyncIter { + type Item; + + #[repr(inline)] + async fn next(&mut self) -> Self::Item; +} +``` + +However, when you use combinators like `buffered` that introduce parallelism, you are now using a *parallel* async iterator ([deliv_async_iter]), similar to the parallel iterators offered by [rayon]. The core operation here is `for_each` (which processes each item in the iterator): + +```rust +trait ParAsyncIter { + type Item; + + async fn for_each(&mut self, op: impl AsyncFn(Self::Item)); +} +``` + +*Editor's note:* There's a subtle difference between `for_each` here and Rayon's `for_each`. It might actually be nice to rework Rayon's approach too. Detail hammering still required! + +### Scopes + +Parallel async iterators are implemented atop of something called *scopes* ([deliv_scope_api]). Scopes are a way of structuring your async tasks into a hierarchy. In this hierarchy, every parent task waits for its children to complete before it itself is complete. Scopes are also connected to cancellation: when you mark a parent task as cancelled, it propagates that cancellation down to its children as well (but still waits for them to finish up) ([deliv_cancellation]). + +Scopes allow you to spawn parallel tasks that access borrowed data ([deliv_borrowed_data]). For example, you could rewrite the parallel iterator above using scopes. For simplicity, we'll ignore the "up to 5 items being prepared" and just spawn a task for all items at once: + +```rust +async fn do_work(database: &Database) { + std::async_thread::scope(async |s| { + // Channel to send prepared items over to the + // task that processes them one at a time: + let mut (tx, rx) = std::async_sync::mpsc::channel(); + + // Spawn a task to spawn tasks: + s.spawn(async move || { + let work = do_select(database, FIND_WORK_QUERY)?; + work.for_each(|item| { + // Spawn a task processing each item and then + // sending it on the channel: + s.spawn(async |item| { + let prepared_item = preparation(database, item).await + tx.send(prepared_item).await; + }); + }); + }); + + // Spawn a task to spawn tasks: + s.spawn(async move || { + while let Some(item) = rx.next().await { + process_item(item).await; + } + }); + }); +} +``` + +### Cancellation + +Cancelling a task is a common operation in async code. Often this is because of a dropped connection, but it could also be because of non-error conditions, such as waiting for the first of two requests to complete and taking whichever finished first. ([deliv_cancellation]) + +*Editor's note:* Clearly, this needs to be elaborated. Topics: + +* Ambient cancellation flag vs explicit passing +* Connecting to I/O operations so they produce errors +* Opt-in synchronous cancellation, select + +### Async read and write traits + +The `AsyncRead` and `AsyncWrite` traits are the most common way to do I/O. They are the async equivalent of the `std::io::Read` and `std::io::Write` traits. They are used in a similar way. [deliv_async_read_write] + +*Editor's note:* This requires elaboration. The challenge is that the best design for these traits is unclear. + +### Async fns in traits, overview + +Async functions work in traits, too ([deliv_async_fundamentals]): + +```rust +trait HttpRequest { + async fn request(&self, url: &Url) -> HttpResponse; +} +``` + +#### Desugaring async fn in traits into `impl Trait` and generic associated types + +Async functions actually desugar into functions that return an `impl Future`. When you use an async function in a trait ([deliv_impl_trait_in_trait]), that is desugared into a (generic) associated type in the trait ([deliv_gats]) whose value is inferred by the compiler ([deliv_tait]): + +```rust +trait SomeTrait { + async fn foo(&mut self); +} + +// becomes: + +trait SomeTrait { + fn foo<(&mut self) -> impl Future + '_; +} + +// becomes something like: +// +// Editor's note: The name of the associated type is under debate; +// it may or may not be something user can name, though they should +// have *some* syntax for referring to it. + +trait SomeTrait { + type Foo<'me>: Future + '_ + where + Self: 'me; + + async fn foo(&mut self) -> Self::Foo<'_>; +} +``` + +What this means is that the future type `SomeTrait::Foo` is going to be a generated type returned by the compiler that is speciic to that future. + +#### Caveat: Gritty details around dyn Trait and no-std + +However, there is a catch here. When a trait contains `async fn`, using `dyn` types (e.g., `dyn HttpRequest`, for the trait above) can get a bit complicated. ([deliv_dyn_async_trait]) By default, we assume that folks using `dyn HttpRequest` are doing so in a multithreaded, standard environment. This means that, by default: + +* A reference like `&T` can only be cast to `&dyn HttpRequest` if all the `async fn` in its impl are `Send` + * Note that you can still write impls whose `async fn` are not send, but you cannot use them with `dyn` (again, by default). +* Async calls that go through a `dyn HttpRequest` will allocate a `Box` to store their data + * This is usually fine, but in particularly tight loops can be a performance hazard. + * Note that this *only applies* when you use `dyn HttpRequest`; most tight loops tend to use generics like `T: HttpRequest` anyway, and here there is no issue. + +These assumptions don't work for everyone, so there are some knobs you can turn: + +* You can request that the futures not be assumed to be Send. +* You can change the "smart pointer" type used to allocate data; for example, instead of `Box`, a choice like `Stack<32>` would stack allocate up to 32 bytes (compilation errors will result if more than 32 bytes are required), and `SmallBox<32>` would stack allocate up to 32 bytes but heap allocate after that. ([deliv_dyn_async_trait]) +* You can use 'inline' async functions, though these are not always suitable. (These are covered under "Diving into the details".) + +The way that all of this is implemented is that users can define their own impls of the form `impl Trait for dyn Trait` ([deliv_dyn_trait]). This permits us to supply a number of derives that can be used to implement the above options. + +## Tooling + +There are a number of powerful development tools available for debugging, profiling, and tuning your Async Rust applications ([deliv_tooling]). These tools allow you to easily view the current tasks in your application, find out what they are blocked on, and do profiling to see where they spend their time. + +Async Rust includes profiling tools that are sufficiently lightweight that you can run them in your production runs, giving very accurate data about what is really happening in your system. They also allow you to process the data in a number of ways, such as viewing profiles per request, or for requests coming from a specific source. + +The tools also include "hazard detection" that uncovers potential bugs or performance problems that you may not have noticed. For example, they can identify functions that run too long with any form of await or yield, which can lead to "hogging" the CPU and preventing other tasks from running. + +Finally, the tools can make suggestions to help you to tune your async code performance. They can identify code that ought to be outlined into separate functions, for example, or instances where the size of futures can be reduced through judicious use of heap allocation ([deliv_boxable]). These edits come in the form of suggestions, much like the compiler, which can be automatically applied with `cargo fix`. + +### Bridging the sync and async worlds + +One of the challenges of async programming is how to embed *synchronous* snippets of code. A synchronous snippet is anything that may occupy the thread for a long period of time without executing an await. This might be because it is a very long-running long loop, or it may be because of it invokes blocking primitives (like synchronous I/O). For efficiency, the async runtimes are setup to assume that this doesn't happen. This means that it is your responsibility to mark any piece of synchronous code with a call to `blocking`. This is a signal to the runtime that the code may block, and it allows the runtime to execute the code on another thread or take other forms of action: + +```rust +std::async::blocking(|| ...).await; +``` + +Note that `blocking` is an async function. Interally, it is built on the scope method `spawn_blocking`, which spawns out a task into an inner scope ([deliv_scope_api]): + +```rust +async fn blocking(f: impl FnOnce() -> R) -> R { + scope(|s| s.spawn_blocking(f).await).await +} +``` + +#### Caveat: Beware the async sandwich + +One challenge with integrating sync and async code is called the "async sandwich". This occurs when you have async code that calls into sync code which in turn wishes to invoke async code: + +* an `async fn` A that calls .. +* a synchronous `fn` B that wishes to block on .. +* an `async fn` C doing some I/O + +The problem here is that, for this to work, the `async fn` A really needs to call the synchronous function with `blocking`, but that may not be apparent, and A may not be in your control (that is, you may be authoring B and/or C, and not be able to modify A). This is a difficult situation without a great answer. Some runtimes offer methods that can help in this situation, but deadlocks may result. + +We hope to address this with 'overloaded async' functions, but more work is needed to flesh out that design ([deliv_async_overloading]). + +## Diving into the details + +The previous topics covered the "high-level view" of async. This section dives a bit more into some of the details of how things work. + +### "Inline" async functions + +Inline async functions ([deliv_inline_async_fn]) are an optimization that is useful for traits where the trait represents the *primary purpose* of the type that implements it; typically such traits are implemented by dedicated types that exist just for that purpose. Examples include: + +* The read and write traits. +* Async iterators. +* Async functions. + +Inline async functions are also crucial to `AsyncDrop` ([deliv_async_drop]), discussed below. + +Inline async functions are declared within a trait body. They indicate that all intermediate state for the function is stored within the struct itself: + +```rust +trait AsyncIter { + type Item; + + #[repr(inline)] + async fn next(&mut self) -> Self::Item; +} +``` + +This implies some limitations, but it has some benefits as well. For example, traits that contain only inline async functions are purely `dyn` safe without any overhead or limitations. + +### Boxable heap allocation + +One of the challening parts of writing a system that juggles many concurrent requests is deciding how much stack to allocate. Pthread-based systems solve this problem by reserving a very large portion of memory for the stack, but this doesn't scale up well when you have very large numbers of requests. A better alternative is to start with a small stack and grow dynamically: but that can be tricky to do without hitting potential performance hazards. + +Rust occupies an interesting spot in the design space. For simple Rust futures, we will allocate *exactly as much stack space as is needed*. This is done by analyzing the future and seeing what possible calls it may make. + +Sometimes, though, this analysis is not possible. For example, a recursive function can use infinite stack. In cases like this, you can annotate your async function to indicate that its stack space should be allocated on the heap where it is called ([deliv_boxable]): + +```rust +box async fn foo() { .. } +``` + +These annotations are also useful for tuning performance. The tooling ([deliv_tooling]) can be used to suggest inserting `box` keywords on cold code paths, thus avoiding allocating stack space that is rarely used. + +### Async drop + +Cleaning up resources in async code is done using destructors, just as in synchronous Rust. Simply implement the `AsyncDrop` trait ([deliv_async_drop]) instead of `Drop`, and you're good to go: + +```rust +impl AsyncDrop for MyType { + async fn drop(&mut self) { + ... + } +} +``` + +Just as in synchronous Rust, you are advised to keep destructors limited in their effects. + +#### Caveat: Synchronous drop + +One thing to be aware of when you implement `AsyncDrop` is that, because any Rust value can be dropped at any point, the type system will allow your type to be dropped synchronously as well. We do however have a lint that detects the most common cases and gives you a warning, so this is rare in practice. + +**Note:** If a type that implements `AsyncDrop` *but not `Drop`* is dropped synchronously, the program will abort! + +#### Caveat: Implicit await points + +One other thing to be aware of is that async drop will trigger implicit awaits each time a value is dropped (e.g., when a block is exited). This is rarely an issue. + +[deliv_portable]: ../roadmap/portable.md +[deliv_lint_blocking_fn]: ../roadmap/polish/lint_blocking_fns.md +[deliv_portable_stdlib]: ../roadmap/portable/stdlib.md +[deliv_documentation]: ../roadmap/documentation.md +[deliv_async_iter]: ../roadmap/async_iter.md +[deliv_cancellation]: ../roadmap/borrowed_data_and_cancellation.md +[deliv_borrowed_data]: ../roadmap/borrowed_data_and_cancellation.md +[deliv_async_read_write]: ../../design_docs/async_read_write.md +[deliv_impl_trait_in_trait]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/impl_trait_in_traits.html +[deliv_gats]: https://github.com/rust-lang/generic-associated-types-initiative +[deliv_tait]: https://github.com/rust-lang/rust/issues/63063 +[deliv_dyn_async_trait]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/dyn_async_trait.html +[deliv_dyn_trait]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/dyn_trait.html +[deliv_scope_api]: ../roadmap/scopes/scope_api.md +[deliv_inline_async_fn]: ../roadmap/async_fn/inline_async_fn.md +[deliv_async_drop]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html +[deliv_async_fundamentals]: https://rust-lang.github.io/async-fundamentals-initiative/ +[deliv_async_overloading]: ../roadmap/async_overloading.md +[deliv_tooling]: ../roadmap/tooling.md +[deliv_boxable]: ../roadmap/async_fn/boxable.md diff --git a/src/vision/status_quo.md b/src/vision/status_quo.md index 9f8e051b..6a54d06b 100644 --- a/src/vision/status_quo.md +++ b/src/vision/status_quo.md @@ -1,33 +1,5 @@ -# 😱 Status quo stories +# 😱 Status quo -## 🚧 Under construction! Help needed! 🚧 +## Where did all the stories go? -We are still in the process of drafting the vision document. The stories you see on this page are examples meant to give a feeling for how a status quo story looks; you can expect them to change. We encourage you to propose your own by opening a PR -- see the ["How to vision"][htv] page for instructions and details. - -[htv]: ./how_to_vision.md - -## What is this - -The "status quo" stories document the experience of using Async Rust today. Each story narrates the challenges encountered by [one of our characters][cc] as they try (and typically fail in dramatic fashion) to achieve their goals. - -[cc]: ./characters.md - -Writing the "status quo" stories helps us to compensate for the [curse of knowledge][cok]: the folks working on Async Rust tend to be experts in Async Rust. We've gotten used to the workarounds required to be productive, and we know the little tips and tricks that can get you out of a jam. The stories help us gauge the cumulative impact all the paper cuts can have on someone still learning their way around. This gives us the data we need to prioritize. - -[cok]: https://en.wikipedia.org/wiki/Curse_of_knowledge - -### Based on a true story - -These stories may not be true, but they are not fiction. They are based on real-life experiences of actual people. Each story contains a "Frequently Asked Questions" section referencing sources used to create the story. In some cases, it may link to notes or summaries in the [conversations] section, though that is not required. The "Frequently Asked Questions" section also contains a summary of what the "morals" of the story are (i.e., what are the key takeaways), along with answers to questions that people have raised along the way. - -[conversations]: ../conversations.md - -### The stories provide data we use to prioritize, not a prioritization itself - -**Just because a user story is represented here doesn't mean we're going to be able to fix it right now.** Some of these user stories will indicate more severe problems than others. As we consider the stories, we'll select some subset to try and address; that choice is reflected in the [roadmap]. - -[roadmap]: ./roadmap.md - -## Where are the stories? - -Check the sidebar with the table of contents bar! +The full set of "submitted" status quo stories [have been moved here](./submitted_stories/status_quo.md). This area will be used for a "combined" status quo story which has not yet been written! diff --git a/src/vision/submitted_stories.md b/src/vision/submitted_stories.md new file mode 100644 index 00000000..ef98f4fb --- /dev/null +++ b/src/vision/submitted_stories.md @@ -0,0 +1,5 @@ +# 💝 Appendix: Submitted stories + +This appendix contains the full list of [status quo](./submitted_stories/status_quo.md) and [shiny future](./submitted_stories/shiny_future.md) stories that were submitted by users as part of the vision doc construction. The lessons and ideas from these stories have been incorporated into the current [roadmap]. + +[roadmap]: ./roadmap.md \ No newline at end of file diff --git a/src/vision/submitted_stories/shiny_future.md b/src/vision/submitted_stories/shiny_future.md new file mode 100644 index 00000000..c204d30f --- /dev/null +++ b/src/vision/submitted_stories/shiny_future.md @@ -0,0 +1,26 @@ +# ✨ Shiny future: Where we want to get to + +## 🚧 Under construction! Help needed! 🚧 + +We are still in the process of drafting the vision document. The stories you see on this page are examples meant to give a feeling for how a shiny future story looks; you can expect them to change. See the ["How to vision"][htv] page for instructions and details. + +[htv]: ../how_to_vision.md + +## What it this + +The "shiny future" is here to tell you what we are trying to build over the next 2 to 3 years. That is, it presents our "best guess" as to what will look like a few years from now. When describing specific features, it also embeds links to [design notes] that describe the constraints and general plans around that feature. + +🧐 You may also enjoy reading the [blog post] announcing the brainstorming effort. + +[design notes]: ../design_notes.md +[blog post]: https://blog.rust-lang.org/2021/04/14/async-vision-doc-shiny-future.html + +### Think big -- too big, if you have to + +You'll notice that the ideas in this document are **maximalist and ambitious**. They stake out an opinionated position on how the ergonomics of Async I/O should feel. This position may not, in truth, be attainable, and for sure there will be changes along the way. Sometimes the realities of how computers actually work may prevent us from doing all that we'd like to. That's ok. This is a dream and a goal. + +We fully expect that the designs and stories described in this document will change as we work towards realizing them. When there are areas of particular uncertainty, we use the Frequently Asked Questions and the design docs to call them out. + +## Where are the stories? + +We haven't written these yet! diff --git a/src/vision/submitted_stories/shiny_future/alan_learns_async_on_his_own.md b/src/vision/submitted_stories/shiny_future/alan_learns_async_on_his_own.md new file mode 100644 index 00000000..f9256b53 --- /dev/null +++ b/src/vision/submitted_stories/shiny_future/alan_learns_async_on_his_own.md @@ -0,0 +1,448 @@ +# ✨ Shiny future stories: Alan learns async on his own + +## 🚧 Warning: Draft status 🚧 + +This is a draft "shiny future" story submitted as part of the +brainstorming period. It is derived from what actual Rust users wish +async Rust should be, and is meant to deal with some of the challenges +that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the +FAQ, feel free to open a PR making edits (but keep in mind that, as +peoples needs and desires for async Rust may differ greatly, shiny +future stories [cannot be wrong]. At worst they are only useful for a +small set of people or their problems might be better solved with +alternative solutions). Alternatively, you may wish to [add your own +shiny vision story][htvsq]! + +## The story + +[Alan] is trying to pick up Rust, and wants to build a command-line web +scraper since it's a project he's recently written in Go. The program +takes a URL, and recursively downloads all URLs named in all fetched +pages. + +Alan goes to crates.io and searches for "http client", and finds a +library called `reqwest`. He opens its documentation, and sees that the +library has him choose between an "async" and a "blocking" client. +Confused, Alan types in "rust async" in his favorite search engine, and +finds the [Rust async book]. On the very first page there's a summary of +where async is useful and where it's not, as well as some of the +downsides of each approach. Alan sees that for "make a single web +request", async is not generally necessary, whereas for "making many +network requests concurrently" async is recommended. Since Alan expects +his crawler to make many requests, he decides he probably wants +async for this application. + +The async book tells Alan that he should mark his `main` function as +`async fn`, so he does. He then follows the `reqwest` async examples, +and is able to successfully make his crawler download a single web page. +Next, he wants to parse each page to extract additional URLs to fetch. +So, he finds a library that can parse HTML, `quick-xml`. He sets up his +application with a `HashSet` to store all the yet-to-be-parsed URLs, and +then writes a loop that pulls out a URL from the set, issues a HTTP +request, awaits the response bytes, and passes them to `quick-xml`. Alan +first tried to give the `http::Response` directly to +`quick_xml::Reader::from_reader`, but the compiler told him: + +```text +error: This type does not implement `Read`, which is required by `Reader::from_reader`. + + let page = Reader::from_reader(request.await?); + ^^^^^^^^^^^^^^ + + help: The type does implement `AsyncRead`, but the method does not support asynchronous inputs. +suggestion: Use a method that supports asynchronous readers or read the data to a `Vec` first, + and then pass that to `Reader::from_reader` instead (`Vec` implements `Read`). +``` + +Alan has his program iterate over all the links on the fetched page, and +add any URLs he finds to the `HashSet`, before he then goes around the +loop again. He is pretty satisfied -- the program seems to work well. +However, it's fairly slow, as it only fetches one page at a time. Alan +looks in the async book he discovered earlier, and sees a chapter titled +"Doing many things at once". The chapter tells Alan that he has three +options: + + - use _select_ to wait for the first of many futures to complete; + - use _join_ to wait on many futures to all complete; and + - use _spawn_ to run a future in the background. + +Alan figures that his program should keep many requests in flight at the +same time, and then parse each one as it finishes, so he goes for the +select approach. He writes: + +```rust +let mut requests = Select::new(); +requests.insert(client.get(start_url).send()); +while !requests.is_empty() { + let response = requests.await; + // Use quick-xml to extract urls from response. + // For each url: + if seen_urls.insert(url.clone()) { + requests.insert(client.get(url).send()); + } +} +``` + +This works, and Alan is delighted. But it seems to work a bit _too_ well +-- his crawler is so fast that it starts getting rate-limited by the +servers he runs it against. So, Alan decides to make his crawler a bit +less aggressive, and adds a call to `std::thread::sleep` after he parses +each page. He compiles his application again, and sees a new warning +from the compiler: + +```text +warning: blocking call in asynchronous code + + std::thread::sleep(Duration::from_secs(1)); + ^^^^^^^^^^^^^^^^^^ + + help: If the thread is put to sleep, other asynchronous code running + on the same thread does not get to run either. +suggestion: Use the asynchronous std::future::sleep method instead of std::thread::sleep in async code. + reading: See the "Blocking in async code" chapter in the Rust async book for more details. +``` + +Alan is happy that the compiler told him about this problem up front, +rather than his downloads being held up during the entire sleep period! +He does as the compiler instructs, and replaces `thread::sleep` with its +asynchronous alternative and an `await`. He then runs his code again, +and the warning is gone, and everything seems to work correctly. + +While looking at his code in his editor, however, Alan notices a little +yellow squiggly line next to his `while` loop. Hovering over it, he sees +a warning from a tool called "Clippy", that says: + +```text +warning: + + while !requests.is_empty() { + ^^^^^^^^^^^^^^^^^^^^^^^^^^ this loop + + let response = requests.await; + ^^^^^^^^^^^^^^ awaits one future from a `Select` + + + std::future::sleep(Duration::from_secs(1)).await; + ^^^^^^^^^^^^^^^^^^ and then pauses, which prevents progress on the `Select` + + + help: Futures do nothing when they're not being awaited, + so while the task is asleep, the `Select` cannot make progress. +suggestion: Consider spawning the futures in the `Select` so they can run in the background. + reading: See the "Doing many things at once" chapter in the Rust async book for more details. +``` + +Alan first searches for "rust clippy" on his search engine of choice, +and learns that it is a linter for Rust that checks for common mistakes +and cases where code can be more idiomatic. He makes a mental note to +always run Clippy from now on. + +Alan recognizes the recommended chapter title from before, and sure +enough, when he looks back on the page that made him choose select, he +sees a box explaining that, as the warning suggests, a `Select` only +makes progress on the asynchronous tasks it contains when it is being +awaited. The same box also suggests to _spawn_ the tasks before placing +them in the `Select` to have them continue to run even after the +`Select` has yielded an item. + +So, Alan modifies his code to spawn each request: + +```rust +// For each url: +if seen_urls.insert(url.clone()) { + requests.insert(std::future::spawn(async { + client.get(url).send().await + })); +} +``` + +But now his code doesn't compile any more: + +```text +error: borrow of `client` does not live long enough: + + let client = request::Client::new(); + ^^^^^^ client is created here + + requests.insert(std::future::spawn(async { + ^^^^^^^^^^^^^^^^^^ spawn requires F: 'static + + client.get(url).send().await + ^^^^^^ this borrow of client makes the `async` block have lifetime 'a + + } + ^ the lifetime 'a ends here when `client` is dropped. + + help: An async block that needs access to local variables cannot be spawned, + since spawned tasks may run past the end of the current function. +suggestion: Consider using `async move` to move `client` if it isn't needed elsewhere, + or keep `client` around forever by using `Arc` for reference-counting, + and then `clone` it before passing it into each call to `spawn`. + reading: See the "Spawning and 'static" chapter in the Rust async book for more details. +``` + +> Author note: the recommendation `Arc` above should be inferred from +> the `Send` bound on `spawn`. If such a bound isn't present, we should +> recommend `Rc` instead. Ideally we would also tailor the suggestion to +> whether changing `async` to `async move` would _actually_ make the +> code compile. + +Alan is amazed at how comprehensive the compiler errors are, and is glad +to see a reference to the async book, which he now realizes he should +probably just make time to read start-to-finish, as it covers everything +he's running into. Alan first tries to change `async` to `async move` as +the compiler suggests, but the compiler then tells him that `client` may +be used again in the next iteration of the loop, which makes Alan +facepalm. Instead, he does as the compiler tells him, and puts the +`client` in an `Arc` and `clone`s that `Arc` for each `spawn`. + +At this point, the code looks a little messy, so Alan decides to open +the referenced chapter in the async book as well. It suggests that +while the pattern he's used is a good fallback, it's often possible to +_construct_ the future outside the spawn, and then `await` it inside the +spawn. Alan gives that a try by removing the `Arc` again and writing: + +```rust +let fut = client.get(url).send(); +requests.insert(std::future::spawn(async move { + fut.await +})); +``` + +> Author note: how would the compiler tell Alan about this +> transformation rather than him having to discover it in the book? + +This works, and Alan is happy! Doubly-so when he notices the yellow +Clippy squiggles telling him that the `async move { fut.await }` can be +simplified to just `fut`. + +Alan runs his crawler again, and this time it doesn't run afoul of any +rate limiting. However, Alan notices that it's still just parsing one +page's HTML at a time, and wonders if he can parallelize that part too. +He figures that since each spawned future runs in the background, he can +just do the XML parsing in there too! So, he refactors the code for +going from a URL to a list of URLs into its own `async fn urls`, and +then writes: + +```rust +async fn urls(client: &Client, url: Url) -> Vec { /* .. */ } + +let mut requests = Select::new(); +requests.insert(spawn(urls(&client, start_url))); +while !requests.is_empty() { + let urls = requests.await; + for url in urls { + if seen_urls.insert(url.clone()) { + requests.insert(spawn(urls(&client, url))); + } + } + sleep(Duration::from_secs(1)).await; +} +``` + +However, to Alan's surprise, this no longer compiles, and is back to the +old `'static` error: + +```text +error: borrow of `client` does not live long enough: + + let client = request::Client::new(); + ^^^^^^ client is created here + + requests.insert(spawn(urls(&client, start_url))); + ^^^^^ spawn requires F: 'static + + requests.insert(spawn(urls(&client, start_url))); + ^^^^^^^ but the provided argument is tied to the lifetime of this borrow + + } + ^ which ends here when `client` is dropped. + + help: When you call an `async fn`, it does nothing until it is first awaited. + For that reason, the `Future` that it returns borrows all of the `async fn`'s arguments. +suggestion: If possible, write the `async fn` (`urls`) as a regular `fn() -> impl Future` that + first uses any arguments that aren't needed after the first `await`, and then + returns an `async move {}` with the remainder of the function body. + + Otherwise, consider making the arguments reference-counted with `Arc` so that the async + function's return value does not borrow anything from its caller. + reading: See the "Spawning and 'static" chapter in the Rust async book for more details. +``` + +With the compiler's helpful explanation, Alan realizes that this is +another instance of the same problem he had earlier, and changes his +`async fn` to: + +```rust +fn urls(client: &Client, url: Url) -> impl Future> { + let fut = client.get(url).send(); + async move { + let response = fut.await; + // Use quick-xml to extract URLs to return. + } +} +``` + +At which point the code once again compiles, and runs faster than ever +before! However, when Alan runs his crawler against a website with +particularly large pages, he notices a new warning in his terminal when +the crawler is running: + +```text +******************** [ Scheduling Delay Detected ] ********************* +The asynchronous runtime has detected that asynchronous tasks are +occasionally prevented from running due to a long-running synchronous +operation holding up the executing thread. + +In particular, the task defined at src/lib.rs:88 can make progress, but +the executor thread that would run it hasn't executed a new asynchronous +task in a while. It was last seen executing at src/lib.rs:96. + +This warning suggests that your program is running a long-running or +blocking operation somewhere inside of an `async fn`, which prevents +that thread from making progress on concurrent asynchronous tasks. In +the worst instance, this can lead to deadlocks if the blocking code +blocks waiting on some asynchronous task that itself cannot make +progress until the thread continues running asynchronous tasks. + +You can find more details about this error in the "Blocking in async +code" chapter of the Rust async book. + +This warning is only displayed in debug mode. +************************************************************************ +``` + +Looking at the indicated lines, Alan sees that line 88 is: + +```rust +requests.insert(spawn(urls(&client, url))); +``` + +And line 96 is the `loop` around: + +```rust +match html_reader.read_event(&mut buf) { + // ... +} +``` + +Alan thinks he understands what the warning is trying to tell him, but +he's not quite sure what he should do to fix it. So he goes to the +indicated chapter in the async book, which says: + +> If you have to run a long-running synchronous operation, or issue a +> blocking system call, you risk holding up the execution of +> asynchronous tasks that the current thread is responsible for +> managing until the long-running operation completes. You have many +> options for mitigating the impact of such synchronous code, each with +> its own set of trade-offs. + +It then suggests: + + - Try to make the synchronous code asynchronous if possible. This could + even just consist of inserting occasional voluntary scheduling points + into long-running loops using `std::future::yield().await` to allow + the thread to continue to make progress on asynchronous tasks. + - Run the synchronous code in a dedicated thread using + `spawn_blocking` and simply `await` the resulting `JoinHandle` in the + asynchronous code. + - Inform the runtime that the current thread (with `block_in_place`) + that it should give away all of its background tasks to other runtime + threads (if applicable), and only then execute the synchronous code. + +The document goes into more detail about the implications of each +choice, but Alan likes the first option the best for this use-case, and +augments his HTML reading loop to occasionally call +`std::future::yield().await`. The runtime warning goes away. + +[Rust async book]: https://rust-lang.github.io/async-book/ + +## 🤔 Frequently Asked Questions + +### What status quo stories are you retelling? + + - [Alan tries to debug a hang](../status_quo/alan_tries_to_debug_a_hang.html) + - [Barbara anguishes over HTTP](../status_quo/barbara_anguishes_over_http.html) + - [Barbara bridges sync and async in perf.rust-lang.org](../status_quo/barbara_bridges_sync_and_async.html) + - [Barbara compares some C++ code](../status_quo/barbara_compares_some_cpp_code.html) + - [Barbara makes their first foray into async](../status_quo/barbara_makes_their_first_steps_into_async.html) + - [Niklaus wants to share knowledge](../status_quo/niklaus_wants_to_share_knowledge.html) + +### What are the key attributes of this shiny future? + + - Not every use-case requires async, and users should be told early on + that that's the case, and enough to make the decision themselves! + - Compiler errors and warnings should recognize _specific_ common + mistakes and recommend good general patterns for solutions. + - Warnings and errors should refer users to more comprehensive + documentation for in-depth explanations and best practices. + - A shared terminology (`AsyncRead`) and standard locations for key + primitives (`sleep`, `spawn`, `Select`) is needed to be able to + provide truly helpful, actionable error messages. + - Async Rust has some very particular problem patterns which are + important to handle correctly. Misleading error messages like "add + `'static` to your `&mut`" or "add `move`" can really throw developers + for a loop by sending them down the wrong rabbit hole. + - Detecting known cases of blocking (even if imperfect) could help + users significantly in avoiding foot-guns. Some cases are: + using `std::thread::sleep`, loops without `.await` in them (or where + all the `.await`s are on `poll_fn` futures), calling methods that + transitively call `block_on`. + +### What is the "most shiny" about this future? + +The ability to detect issues that _would_ be performance problems at +runtime at compile-time. + +### What are some of the potential pitfalls about this future? + +Detecting blocking is tricky, and likely subject to both false-positives +and false-negatives. Users _hate_ false-positive warnings, so we'll have +to be careful about when we give warnings based on what _might_ happen +at runtime. + +### Did anything surprise you when writing this story? Did the story go any place unexpected? + +I wasn't expecting it to end up this long and detailed! + +I also wasn't expecting to have to get into the fact that `async fn`s +capture their arguments, but got there very quickly by just walking +through what I imagine Alan's thought process and development would be +like. + +### What are some variations of this story that you considered, or that you think might be fun to write? Have any variations of this story already been written? + + - How does Alan realize the difference between `Select` (really + `FuturesUnordered`) and `select!` (where the branches are known + statically)? + - Another common pain-point is forgetting to pin futures when using + constructs like `select!`. Can the compiler detect this and suggest + `std::task::pin!` (and can we have that in `std` please)? + - Tools that allow the user to introspect the program state at runtime + and detect things like blocking that way are great, but don't help + newcomers too much. They won't know about the tools, or what to look + for. + - How can we detect and warn about async code that transitively ends up + calling `block_on`? + - This story didn't get into taking a `Mutex` and holding it across an + `.await`, and the associated problems. Nor how a user finds other, + better design patterns to deal with that situation. + - A story where Alan uses the docs to decide he _shouldn't_ use async + would be nice. Including if he then needs to use some library that is + itself `async` -- how does he bridge that gap? And perhaps one where + he then later changes his mind and has to move from sync to async. + - [Barbara plays with async](../status_quo/barbara_plays_with_async.html) + could also use a similar-style "shining future" story. + +### What are some of the things we'll have to figure out to realize this future? What projects besides Rust itself are involved, if any? (Optional) + + - Detecting the async "color" of functions to warn about crossing. + - Detecting long-running code in runtimes. + - Standardizing enough core terminology and mechanisms that the + compiler can both detect specific problems and propose actionable + solutions + +[Alan]: ../../characters/alan.md +[htvsq]: ../shiny_future.md diff --git a/src/vision/submitted_stories/shiny_future/alan_switches_runtimes.md b/src/vision/submitted_stories/shiny_future/alan_switches_runtimes.md new file mode 100644 index 00000000..b944127d --- /dev/null +++ b/src/vision/submitted_stories/shiny_future/alan_switches_runtimes.md @@ -0,0 +1,150 @@ +# ✨ Shiny future stories: Alan switches runtimes + +[How To Vision: Shiny Future]: ../shiny_future.md +[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async/master/src/vision/shiny_future/template.md +[`shiny_future`]: https://github.com/rust-lang/wg-async/tree/master/src/vision/shiny_future +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md + +## 🚧 Warning: Draft status 🚧 + +This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]! + +## The story + +Since his [early adventures](./alans_trust_in_the_compiler_is_rewarded.md) with Async I/O went so well, Alan has been looking for a way to learn more. He finds a job working in Rust. One of the projects he works on is [DistriData]. Looking at their code, he sees an annotation he has never seen before: + +```rust +#[humboldt::main] +async fn main() -> Result<(), Box> { + let result = std::async_thread::spawn(async move { + do_something() + }); +} +``` + +He asks Barbara, one of his coworkers, "What is this `humboldt::main` annotation? What's `humboldt`?" She answers by explaining to him that Rust's support for async I/O is actually based around an underlying runtime. "Rust gives you a pretty decent runtime by default," she says, "but it's not tuned for our workloads. We wrote our own runtime, which we call `humboldt`." + +Alan asks, "What happens with the various std APIs? For example, I see we are calling `std::async_thread::spawn` -- when I used that before, it spawned tasks into the default runtime. What happens now?" + +Barbara explains that the "async" APIs in std generally execute relative to the current runtime that is in use. "When you call `std::async_thread::spawn`, it will spawn a task onto the current runtime. It's the same with the routines in `std::async_io` and so forth. The `humboldt::main` annotation actually just creates a synchronous `main` function that initializes the `humboldt` runtime and launches the first future. When you just write an `async fn main` without any annotation, the compiler synthesizes the same `main` function with the default runtime." + + + +## Learning more about Humboldt + +Alan sees that some of the networking code that is being used in their application is creating network connections using humboldt APIs: + +```rust +use humboldt::network; +``` + +He asks Barbara, "Why don't we use the `std::async_io` APIs for that?" She explains that Humboldt makes use of some custom kernel extensions that, naturally enough, aren't part of the std library. "TCP is for rubes," she says, "we are using TTCP -- Turbo TCP." Her mind wanders briefly to [Turbo Pascal] and she has a brief moment of yearning for the days when computers had a "Turbo" button that changed them from 8 MHz to 12 MHz. She snaps back into the present day. "Anyway, the `std::async_io` APIs just call into humboldt's APIs via various traits. But we can code directly against `humboldt` when we want to access the extra capabilities it offers. That *does* make it harder to change to another runtime later, though." + +[Turbo Pascal]: https://en.wikipedia.org/wiki/Turbo_Pascal + +## Integrating into other event loops + +Later on, Alan is working on a visualizer front-end that integrates with [DistriData] to give more details about their workloads. To do it, he needs to integrate with Cocoa APIs and he wants to run certain tasks on [Grand Central Dispatch]. He approaches Barbara and asks, "If everything is running on `humboldt`, is there a way for me to run some things on another event loop? How does that work?" + +[Grand Central Dispatch]: https://en.wikipedia.org/wiki/Grand_Central_Dispatch + +Barbara explains, "That's easy. You just have to use the `gcd` wrapper crate -- you can find it on `crates.io`. It implements the runtime traits for `gcd` and it has a `spawn` method. Once you spawn your task onto `gcd`, everything you run within `gcd` will be running in that context." + +Alan says, "And so, if I want to get things running on `humboldt` again, I spawn a task back on `humboldt`?" + +"Exactly," says Barbara. "Humboldt has a global event loop, so you can do that by just doing `humboldt::spawn`. You can also just use the `humboldt::io` APIs directly. They will always use the Humboldt I/O threads, rather than using the current runtime." + +Alan winds up with some code that looks like this: + +```rust +async fn do_something_on_humboldt() { + gcd::spawn(async move { + let foo = do_something_on_gcd(); + + let bar = humboldt::spawn(async move { + do_a_little_bit_of_stuff_on_humboldt(); + }); + + combine(foo.await, bar.await); + }); +} +``` + +## 🤔 Frequently Asked Questions + +### What status quo story or stories are you retelling? + +Good question! I'm not entirely sure! I have to go looking and think about it. Maybe we'll have to write some more. + +### What are the key points you were trying to convey with this status quo story? + +* There is some way to seamlessly change to a different default runtime to use for `async fn main`. +* There is no global runtime, just the current runtime. +* When you are using this different runtime, you can write code that is hard-coded to it and which exposes additional capabilities. +* You can integrate multiple runtimes relatively easily, and the std APIs work with whichever is the current runtime. + +### How do you imagine the std APIs and so forth know the current runtime? + +I was imagining that we would add fields to the `Context<'_>` struct that is supplied to each `async fn` when it runs. Users don't have direct access to this struct, but the compiler does. If the std APIs return futures, they would gain access to it that way as well. If not, we'd have to create some other mechanism. + +### What happens for runtimes that don't support all the features that std supports? + +That feels like a portability question. See the (yet to be written) sequel story, "Alan runs some things on WebAssembly". =) + +### **What is [Alan] most excited about in this future? Is he disappointed by anything?** + +Alan is excited about how easy it is to get async programs up and running, and he finds that they perform pretty well once he does so, so he's happy. + +### **What is [Grace] most excited about in this future? Is she disappointed by anything?** + +Grace is concerned with memory safety and being able to deploy her tricks she knows from other languages. Memory safety works fine here. In terms of tricks she knows and loves, she's happy that she can easily switch to another runtime. The default runtime is good and works well for most things, but for the [`DistriData`] project, they really need something tailored just for them. She is also happy she can use the extended APIs offered by `humboldt`. + +### **What is [Niklaus] most excited about in this future? Is he disappointed by anything?** + +Niklaus finds it async Rust quite accessible, for the same reasons cited as in ["Alan's Trust in the Rust Compiler is Rewarded"]. + +["Alan's Trust in the Rust Compiler is Rewarded"]: ./alans_trust_in_the_compiler_is_rewarded.md + +### **What is [Barbara] most excited about in this future? Is she disappointed by anything?** + +Depending on the technical details, Barbara may be a bit disappointed by the details of how std interfaces with the runtimes, as that may introduce some amount of overhead. This may not matter in practice, but it could also lead to library authors avoiding the std APIs in favor of writing generics or other mechanisms that are "zero overhead". + +### **What [projects] benefit the most from this future?** + +Projects like [DistriData] really benefit from being able to customize their runtime. + +### **Are there any [projects] that are hindered by this future?** + +We have to pay careful attention to embedded projects like [MonsterMesh]. Some of the most obvious ways to implement this future would lean on `dyn` types and perhaps boxing, and that would rule out some embedded projects. Embedded runtimes like [embassy] are also the most different in their overall design and they would have the hardest time fitting into the std APIs (of course, many embedded projects are already no-std, but many of them make use of some subset of the std capabilities through the facade). In general, traits and generic functions in std could lead to larger code size, as well. + +[embassy]: https://github.com/akiles/embassy + +### **What are the incremental steps towards realizing this shiny future?** + +There are a few steps required to realize this future: + +* We have to determine the core mechanism that is used for std types to interface with the current scheduler. + * Is it based on dynamic dispatch? Delayed linking? Some other tricks we have yet to invent? + * Depending on the details, language changes may be required. +* We have to hammer out the set of traits or other interfaces used to define the parts of a runtime (see below for some of the considerations). + * We can start with easier cases and proceed to more difficult ones, however. + +### **Does realizing this future require cooperation between many projects?** + +Yes. We will need to collaborate to define traits that std can use to interface with each runtime, and the runtimes will need to implement those traits. This is going to be non-trivial, because we want to preserve the ability for independent runtimes to experiment, while also preserving the ability to "max and match" and re-use components. For example, it'd probably be useful to have a bunch of shared I/O infrastructure, or to have utility crates for locks, for running threadpools, and the like. On the other hand, tokio takes advantage of the fact that it owns the I/O types *and* the locks *and* the scheduler to do some [nifty tricks](https://tokio.rs/blog/2020-04-preemption) and we would want to ensure that remains an option. + + +[character]: ../../characters.md +[comment]: ../../how_to_vision/comment.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[projects]: ../../projects.md +[htvsq]: ../shiny_future.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[DistriData]: ../../projects/DistriData.md +[MonsterMesh]: ../../projects/MonsterMesh.md diff --git a/src/vision/submitted_stories/shiny_future/alans_trust_in_the_compiler_is_rewarded.md b/src/vision/submitted_stories/shiny_future/alans_trust_in_the_compiler_is_rewarded.md new file mode 100644 index 00000000..a104c0ab --- /dev/null +++ b/src/vision/submitted_stories/shiny_future/alans_trust_in_the_compiler_is_rewarded.md @@ -0,0 +1,200 @@ +# ✨ Shiny future stories: Alan's trust in the compiler is rewarded + +[How To Vision: Shiny Future]: ../shiny_future.md +[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async/master/src/vision/shiny_future/template.md +[`shiny_future`]: https://github.com/rust-lang/wg-async/tree/master/src/vision/shiny_future +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md + +## 🚧 Warning: Draft status 🚧 + +This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]! + +## The story + +### Trust the compiler +Alan has a lot of experience in C#, but in the meantime has created some successful projects in Rust. +He has dealt with his fair share of race conditions/thread safety issues during runtime in C#, but is now starting to trust that if his Rust code compiles, +he won't have those annoying runtime problems to deal with. + +This allows him to try to squeeze his programs for as much performance as he wants, because the compiler will stop him when he tries things that could result in runtime problems. +After seeing the performance and the lack of runtime problems, he starts to trust the compiler more and more with each project finished. + +He knows what he can do with external libraries, he does not need to fear concurrency issues if the library cannot be used from multiple threads, because the compiler would tell him. + +His trust in the compiler solidifies further the more he codes in Rust. + +### The first async project + +Alan now starts with his first async project. He opens up the Rust book to the "Async I/O" chapter and it guides him to writing his first program. He starts by writing some synchronous code to write to the file system: + +```rust +use std::fs::File; + +fn main() -> Result<(), Box> { + let mut file = File::create("a.txt")?; + file.write_all(b"Hello, world!")?; + Ok(()) +} +``` + +Next, he adapts that to run in an async fashion. He starts by converting `main` into `async fn main`: + +```rust +use std::fs::File; + +async fn main() -> Result<(), Box> { + let mut file = File::create("a.txt")?; + file.write_all(b"Hello, world!")?; + Ok(()) +} +``` + +The code compiles, but he gets a warning: + +``` +warning: using a blocking API within an async function + --> src/main.rs:4:25 +1 | use std::fs::File; + | ------------- try changing to `std::async_io::fs::File` + | ... +4 | let mut file: u32 = File::create("a.txt")?; + | ^^^^^^^^^^^^ blocking functions should not be used in async fn +help: try importing the async version of this type + --> src/main.rs:1 +1 | use std::async_fs::File; +``` + +"Oh, right," he says, "I am supposed to use the async variants of the APIs." He applies the suggested fix in his IDE, and now his code looks like: + +```rust +use std::async_fs::File; + +async fn main() -> Result<(), Box> { + let mut file = File::create("a.txt")?; + file.write_all(b"Hello, world!")?; + Ok(()) +} +``` + +His IDE recompiles instantaneously and he now sees two little squiggles, one under each `?`. Clicking on the errors, he sees: + +``` +error: missing await + --> src/main.rs:4:25 +4 | let mut file: u32 = File::create("a.txt")?; + | ^ returns a future, which requires an await +help: try adding an await + --> src/main.rs:1 +4 | let mut file: u32 = File::create("a.txt").await?; +``` + +He again applies the suggested fix, and his code now shows: + +```rust +use std::async_fs::File; + +async fn main() -> Result<(), Box> { + let mut file = File::create("a.txt").await?; + file.write_all(b"Hello, world!").await?; + Ok(()) +} +``` + +Happily, it compiles, and when he runs it, everything works as expected. "Cool," he thinks, "this async stuff is pretty easy!" + +### Making some web requests + +Next, Alan decides to experiment with some simple web requests. This isn't part of the standard library, but the `fetch_rs` package is listed in the Rust book. He runs `cargo add fetch_rs` to add it to his `Cargo.toml` and then writes: + +```rust +use std::async_fs::File; +use fetch_rs; + +async fn main() -> Result<(), Box> { + let mut file = File::create("a.txt")?; + file.write_all(b"Hello, world!")?; + + let body = fetch_rs::get("https://www.rust-lang.org") + .await? + .text() + .await?; + println!("{}", body); + + Ok(()) +} +``` + +This feels pretty easy! + +## 🤔 Frequently Asked Questions + +### What status quo story or stories are you retelling? + +* [Alan started trusting the Rust compiler, but then async](../status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md) +* [Barbara makes their first foray into async](../status_quo/barbara_makes_their_first_steps_into_async.md) + +### What are the key points you were trying to convey with this status quo story? + +* Getting started with async should be as automated as possible: + * change `main` to an `async fn`; + * use the APIs found in modules like `std::async_foo`, which should map as closely as possible to their non-async equivalents. +* You should get some sort of default runtime that is decent +* Lints should guide you in using async: + * identifying blocking functions + * identifying missing `await` +* You should be able to grab libraries from the ecosystem and they should integrate with the default runtime without fuss + +### Is there a "one size fits all" runtime in this future? + +This particular story doesn't talk about what happens when the default runtime isn't suitable. But you may want to read its sequel, ["Alan Switches Runtimes"](./alan_switches_runtimes.md). + +### **What is [Alan] most excited about in this future? Is he disappointed by anything?** + +Alan is excited about how easy it is to get async programs up and running. He also finds the performance is good. He's good. + +### **What is [Grace] most excited about in this future? Is she disappointed by anything?** + +Grace is happy because she is getting strong safety guarantees and isn't getting surprising runtime panics when composing libraries. The question of whether she's able to use the tricks she knows and loves is a good one, though. The default scheduler may not optimize for maximum performance -- this is something to explore in future stories. The ["Alan Switches Runtimes"](./alan_switches_runtimes.md), for example, talks more about the ability to change runtimes. + +### **What is [Niklaus] most excited about in this future? Is he disappointed by anything?** + +Niklaus is quite happy. Async Rust is fairly familiar and usable for him. Further, the standard library includes "just enough" infrastructure to enable a vibrant crates-io ecosystem without centralizing everything. + +### **What is [Barbara] most excited about in this future? Is she disappointed by anything?** + +Barbara quite likes that the std APIs for sync and sync fit together, and that there is a consistent naming scheme across them. She likes that there is a flourishing ecosystem of async crates that she can choose from. + +### **What [projects] benefit the most from this future?** + +A number of projects benefit: + +* Projects like [YouBuy] are able to get up and going faster. +* Libraries like [SLOW] become easier because they can target the std APIs and there is a defined plan for porting across runtimes. + +[YouBuy]: ../../projects/YouBuy.md +[SLOW]: ../../projects/SLOW.md + +### **Are there any [projects] that are hindered by this future?** + +It depends on the details of how we integrate other runtimes. If we wound up with a future where most libraries are "hard-coded" to a single default runtime, this could very well hinder any number of projects, but nobody wants that. + +### **What are the incremental steps towards realizing this shiny future?** + +This question can't really be answered in isolation, because so much depends on the story for how we integrate with other runtimes. I don't think we can accept a future where is literally a single runtime that everyone has to use, but I wanted to pull out the question of "non-default runtimes" (as well as more details about the default) to other stories. + +### **Does realizing this future require cooperation between many projects?** + +Yes. For external libraries like `fetch_rs` to interoperate they will want to use the std APIs (and probably traits). + +[character]: ../../characters.md +[comment]: ../../how_to_vision/comment.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[projects]: ../../projects.md +[htvsq]: ../shiny_future.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/shiny_future/barbara_appreciates_performance_tools.md b/src/vision/submitted_stories/shiny_future/barbara_appreciates_performance_tools.md new file mode 100644 index 00000000..783df84d --- /dev/null +++ b/src/vision/submitted_stories/shiny_future/barbara_appreciates_performance_tools.md @@ -0,0 +1,85 @@ +# ✨ Shiny future stories: Barbara appreciates great performance analysis tools + +[How To Vision: Shiny Future]: ../shiny_future.md +[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async/master/src/vision/shiny_future/template.md +[`shiny_future`]: https://github.com/rust-lang/wg-async/tree/master/src/vision/shiny_future +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md + +## 🚧 Warning: Draft status 🚧 + +This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]! + +## The story + +[Barbara] has built an initial system prototype in sync Rust. She notes that it's completely I/O bound, and benchmarking shows that most of her CPU consumption is thread switch overheads. She decides to rewrite it in async Rust, using an executor that she believes will fix her bottlenecks. + +She sprinkles `async/.await` in all the right places, switches her sync dependencies to async libraries, and gets the code compiling. When she runs it, she discovers that the service no longer responds when she sends a request to the endpoint. Her logging shows her that the endpoint handler has been invoked, many tasks have been spawned, but that something isn't working as she expected. + +Fortunately, there are great tracing tools available for async Rust. Barbara turns on tracing, and immediately gets interesting information in her trace viewer. She can see all the tasks she has spawned, the lines of code where a `.await` returns control to the executor, and delays between a `Waker` being invoked and the corresponding `.await` resuming execution. + +With this information in hand, she finds a decompression path that is unexpectedly CPU-bound, because she can see a stack trace for the task that is running and blocking a woken up future from getting invoked again. The memory use of this future tells her that the compressed blobs are larger than she thought, but inspecting shows that this is reasonable. She thus puts the decompression onto its own blocking task, which doesn't fix things, but makes it clear that there is a deadlock passing data between two bounded channels; the trace shows the `Waker` for a `rx.next().await` being invoked, but the corresponding `.await` never runs. Looking into the code, she notes that the task is waiting on a `tx.send().await` call, and that the channel it is trying to send to is full. When Barbara reads this code, she identifies a classic AB-BA deadlock; the task that would consume items from the channel this task is waiting on is itself waiting on a transmit to the queue that this task will drain. + +She refactors her code to resolve this issue, and then re-checks traces. This time, the endpoint behaves as expected, but she's not seeing the wall clock time she expects; the trace shows that she's waiting on a network call to another service (also written in async Rust), and it's taking about 10x longer to reply than she would expect. She looks into the tracing libraries, and finds two useful features: + +1. She can annotate code with extra information that appears on the traces. +2. Every point in the code has access to a unique ID that can be passed to external services to let her correlate traces. + +Barbara adds annotations that let her know how many bytes she's sending to the external service; it's not unreasonable, so she's still confused. A bit of work with the service owner, and she can now get traces from the external service that have IDs she sends with a request in them. The tooling combines traces nicely, so that she can now trace across the network into the external service, and she realises that it's going down a slow code path because she set the wrong request parameters. + +With the extra insights from the external service's trace, she's able to fix up her code to run perfectly, and she gets the desired wins from async Rust. Plus, she's got a good arsenal of tooling to use when next she sees an unidentified problem. + +## 🤔 Frequently Asked Questions + +### What status quo story or stories are you retelling? + +* [Barbara compares some C++ code (and has a performance problem)](../status_quo/barbara_compares_some_cpp_code.md) +* [Barbara wants Async Insights](../status_quo/barbara_wants_async_insights.md) + +### **What is [Alan] most excited about in this future? Is he disappointed by anything?** + +Alan is excited about how easy it is to find out when his projects don't work as expected. He's happy + +### **What is [Grace] most excited about in this future? Is she disappointed by anything?** + +Grace is happy because the performance tools give her all the low level insights she wants into her code, and shows her what's going on "behind the scenes" in the executor. As a C++ developer, she is also excited when she sees that Rust developers who see an issue with her services can give her useful information about exactly what they see her C++ doing - which she can correlate with her existing C++ performance tools via the unique ID. + +### **What is [Niklaus] most excited about in this future? Is he disappointed by anything?** + +Niklaus is content. The tooling tells him what he needs to know, and allows him to add interesting information to places where he'd otherwise be stuck trying to extract it via `println!()`. He's not entirely sure how to use some of the detailed information, but he can ignore it easily because the tools let him filter down to just the information he added to the traces - getting timestamps and task identifiers "for free" is just gravy to Niklaus. + +### **What is [Barbara] most excited about in this future? Is she disappointed by anything?** + +Barbara is impressed at how easy it is to spot problems and handle them; she is especially impressed when the tooling is able to combine traces from two services and show her their interactions in a useful fashion as-if they were one process. She kinda wishes that the compiler would spot more of the mistakes she made - the decompression path should be something the compiler should get right for her - but at least the tooling made the problems easy to find. + +### **What [projects] benefit the most from this future?** + +All the projects benefit; there's a useful amount of tracing "for free", and places where you can add your own data as needed. + +### **Are there any [projects] that are hindered by this future?** + +[MonsterMesh] needs to be able to remove a lot of the tracing because the CPU and memory overhead is too high in release builds. + +[MonsterMesh]: ../../projects/MonsterMesh.md + +### **What are the incremental steps towards realizing this shiny future?** + +The [tracing] crate has a starting point for a useful API; combined with [tracing-futures], we have a prototype. + +Next steps are to make integrating that with executors trivial (minimal code change), and to add in extra information to [tracing-futures] so that we can output the best possible traces. In parallel to that, we'll want to work on tooling to display, combine, and filter traces so that we can always extract just what we need from any given trace. + +[tracing]: https://crates.io/crates/tracing +[tracing-futures]: https://crates.io/crates/tracing-futures + +### **Does realizing this future require cooperation between many projects?** + +Yes. We need an agreed API for tracing that all async projects use - both to add tracing information, and to consume it in a useful form. + +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[projects]: ../../projects.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/shiny_future/barbara_enjoys_an_async_sandwich.md b/src/vision/submitted_stories/shiny_future/barbara_enjoys_an_async_sandwich.md new file mode 100644 index 00000000..1098a315 --- /dev/null +++ b/src/vision/submitted_stories/shiny_future/barbara_enjoys_an_async_sandwich.md @@ -0,0 +1,160 @@ +# ✨ Shiny future stories: Barbara enjoys her async-sync-async sandwich :sandwich: + +:::warning +Alternative titles: +- Barbara enjoys her async-sync-async sandwich :sandwich: +- Barbara recursively blocks +- Barbara blocks and blocks and blocks +::: + + +## 🚧 Warning: Draft status 🚧 + +This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]! + +## The story + +Barbara wants to customize a permissions lookup when accepting requests. The library defines a `trait PermitRequest`, to allow the user to define their own rules. Nice! + +```rust +trait PermitRequest {} +``` + +She starts small, to get her feet wet. + +```rust +struct Always; + +impl PermitRequest for Always { + fn permit(&self, _: &Request) -> bool { + true + } +} +``` + +All requests are permitted! Simple, but now to actually to implement the permissions logic. + +One of the basic rules Barbara has is to check the request for the existence of a header, but the function is written as `async`, since Barbara figured it might need to be eventually. + +```rust +async fn req_has_header(req: &Request) -> bool { + req.headers().contains_key("open-sesame") +} +``` + +When Barbara goes to implement the `PermitRequest` trait, she realizes a problem: the trait did not think permissions would require an async lookup, so its method is not `async`. Barbara tries the easiest thing first, hoping that she can just block on the future. + +```rust +struct HasHeader; + +impl PermitRequest for HasHeader { + fn permit(&self, req: &Request) -> bool { + task::block_on(req_has_header(req)) + } +} +``` + +When Barbara goes to run the code, it works! Even though she was already running an async runtime at the top level, trying to block on *this* task didn't panic or deadlock. This is because the runtime optimistically hoped the future would be available without needing to go to sleep, and so when it found the currently running runtime, it re-used it to run the future. + +The compiler *does* emit a warning, thanks to a blocking lint (link to shiny future when written). It let Barbara know this could have performance problems, but she accepts the trade offs and just slaps a `#[allow(async_blocking)]` attribute in there. + + +Barbara, now energized that things are looking good, writes up the other permission strategy for her application. It needs to fetch some configuration from another server based on a request header, and to keep it snappy, she limits it with a timeout. + +```rust +struct FetchConfig; + +impl PermitRequest for FetchConfig { + fn permit(&self, req: &Request) -> bool { + let token = req.headers().get("authorization"); + + #[allow(async_blocking)] + task::block_on(async { + select! { + resp = fetch::get(CONFIG_SERVER).param("token", token) => { + resp.status() == 200 + }, + _ = time::sleep(2.seconds()) => { + false + } + } + }) + } +} +``` + +This time, there's no compiler warning, since Barbara was ready for that. And running the code, it works as expected. The runtime was able to reuse the IO and timer drivers, and not need to disrupt other tasks. + +However, the runtime chose to emit a runtime log at the warning level, informing her that while it was able to make the code work, it *could* have degraded behavior if the same parent async code were waiting on this and another async block, such as via `join!`. In the first case, since the async code was ready immediately, no actual harm could have happened. But this time, since it had to block the task waiting on a timer and IO, the log was emitted. + +Thanks to the runtime warning, Barbara does some checking that the surround code won't be affected, and once sure, is satisfied that it was easier than she thought to make an async-sync-async sandwich. + + +## 🤔 Frequently Asked Questions + +### What status quo stories are you retelling? + +While this story isn't an exact re-telling of an existing status quo, it covers the morals of a couple: + +- [Barbara bridges sync and async](https://rust-lang.github.io/wg-async/vision/status_quo/barbara_bridges_sync_and_async.html) +- [A comment about async in `ResolveServerCerts`](https://github.com/rust-lang/wg-async/pull/164#issuecomment-824028298) + +### What are the key attributes of this shiny future? + +- `block_on` tries to be forgiving and optimistic of nested usage. + - It does a best effort to "just work". +- But at the same time, it provides information to the user that it might not always work out. + - A compiletime lint warns about the problem in general. + - This prods a user to *try* to use `.await` instead of `block_on` if they can. + - A runtime log warns when the usage could have reacted badly with other code. + - This gives the user some more information if a specific combination degrades their application. + +### What is the "most shiny" about this future? + +It significantly increases the areas where `block_on` "just works", which should improve *productivity*. + +### What are some of the potential pitfalls about this future? + +- While this shiny future tries to be more forgiving when nesting `block_on`, the author couldn't think of a way to completely remove the potential dangers therein. +- By making it *easier* to nest `block_on`, it might increase the times a user writes code that degrades in performance. + - Some runtimes would purposefully panic early to try to encourage uses to pick a different design that wouldn't degrade. + - However, by keeping the warnings, hopefully users can evaluate the risks themselves. + +*Thing about Rust's core "value propositions": performance, safety and correctness, productivity. Are any of them negatively impacted? Are there specific application areas that are impacted negatively? You might find the sample [projects] helpful in this regard, or perhaps looking at the goals of each [character].* + +### Did anything surprise you when writing this story? Did the story go any place unexpected? + +No. + +### What are some variations of this story that you considered, or that you think might be fun to write? Have any variations of this story already been written? + +A variation would be an even more optimistic future, where we are able to come up with a technique to completely remove all possible bad behaviors with nested `block_on`. The author wasn't able to think of how, and it seems like the result would be similar to just being able to `.await` in every context, possibly implicitly. + +### What are some of the things we'll have to figure out to realize this future? What projects besides Rust itself are involved, if any? (Optional) + +- A runtime would need to be modified to be able to lookup through a thread-local or similar whether a runtime instance is already running. +- A runtime would need some sort of `block_in_place` mechanism. +- We could make a heuristic to guess when `block_in_place` would be dangerous. + - If the runtime knows the task's waker has been cloned since the last time it was woken, then *probably* the task is doing something like `join!` or `select!`. + - Then we could emit a warning like "nested block_on may cause problems when used in combination with `join!` or `select!`" + - The heuristic wouldn't work if the nested block_on were part of the *first* call of a `join!`/`select!`. + - Maybe a warning regardless is a good idea. + - Or a lint, that a user can `#[allow(nested_block_on)]`, at their own peril. +- This story uses a generic `task::block_on`, to not name any specific runtime. It doesn't specifically assume that this could work cross-runtimes, but maybe a shinier future would assume it could? +- This story refers to a lint in a proposed different shiny future, which is not yet written. + + + +[character]: ../../characters.md +[comment]: ../../how_to_vision/comment.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[projects]: ../../projects.md +[htvsq]: ../shiny_future.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade + diff --git a/src/vision/submitted_stories/shiny_future/barbara_makes_a_wish.md b/src/vision/submitted_stories/shiny_future/barbara_makes_a_wish.md new file mode 100644 index 00000000..45662303 --- /dev/null +++ b/src/vision/submitted_stories/shiny_future/barbara_makes_a_wish.md @@ -0,0 +1,173 @@ +# ✨ Shiny future stories: Barbara makes a wish + +## 🚧 Warning: Draft status 🚧 + +This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]! + +## The story + +[Barbara] has an initial prototype of a new service she wrote in sync Rust. She then decides, since the service is extremely I/O bound, to port it to async Rust and her benchmarks have led her to believe that performance is being left on the table. + +She does this by sprinkling `async/.await` everywhere, picking an executor, and moving dependencies from sync to async. + +Once she has the program compiling, she thinks "oh that was easy". She runs it for the first time and surprisingly she finds out that when hitting an endpoint, nothing happens. + +Barbara, always prepared, has already added logging to her service and she checks the logs. As she expected, she sees here that the endpoint handler has been invoked but then... nothing. Barbara exclaims, "Oh no! This was not what I was expecting, but let's dig deeper." + +She checks the code and sees that the endpoint spawns several tasks, but unfortunately those tasks don't have much logging in them. + +Barbara now remembers hearing something about a `wish4-async-insight` crate, which has gotten some buzz on her Rust-related social media channels. She decides to give that a shot. + +She adds the crate as a dependency to her `Cargo.toml`, renaming it to just `insight` to make it easier to reference in her code, and then initializes it in her main async function. + +```rust +async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { + insight::init(); // new code + ... +} +``` + +Barbara rebuilds and runs her program again. She doesn't see anything different in the terminal output for the program itself though, and the behavior is the same as before: hitting an endpoint, nothing happens. She double-checks the readme for the `wish4-async-insight` crate, and realizes that she needs to connect other programs to her service to observe the insights being gathered. Barbara decides that she wants to customize the port that `insight` is listening on before she starts her experiments with those programs. + +```rust +async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { + insight::init(listen_port => 8080); // new code, leveraging keyword arguments feature added in 2024 + ... +} +``` + +While her code rebuilds, Barbara investigates what programs she might use to connect to the insight crate. + +One such program, `consolation`, can run in the terminal. Barbara is currently just deploying her service locally on her development box, so she opts to try that out and see what it tells her. + +``` +% rustup install wish4-consolation +... +% consolation --port 8080 +``` + +This brings up a terminal window that looks similar to the Unix `top` program, except that instead of a list of OS processes, this offers a list of tasks, with each task having a type, ID, and status history (i.e. percentage of time spent in running, ready to poll, or blocked). Barbara skims the output in the list, and sees that one task is listed as currently blocked. + +Barbara taps the arrow-keys and sees that this causes a cursor to highlight different tasks in the list. She highlights the blocked task and hits the Enter key. This causes the terminal to switch to a Task view, describing more details about that task and its status. + +The Task view here says that the task is blocked, references a file and line number, and also includes the line from the source code, which says `chan.send(value).await`. The blocked task also lists the resources that the task is waiting on: `prototype_channel`, and next to that there is text on a dark red background: "waiting on channel capacity." Again, Barbara taps the arrow-keys and sees that she can select the line for the resource. + +Barbara notices that this whole time, at the bottom of the terminal, there was a line that says "For help, hit `?` key"; she taps question mark. This brings up a help message in a scrollable subwindow explaining the task view in general as well as link to online documentation. The help message notes that the user can follow the chain: One can go from the blocked task to the resource it's waiting on, and from that resource to a list of tasks responsible for freeing up the resource. + +Barbara hits the Escape key to close the help window. The highlight is still on the line that says "prototype_channel: waiting on channel capacity"; Barbara hits Enter, and this brings up a list with just one task on it: The channel reader task. Barbara realizes what this is saying: The channel resource is blocking the sender because it is full, and the only way that can be resolved is if the channel reader manages to receive some inputs from the channel. + +Barbara opens the help window again, and brings up the link to the online documentation. There, she sees discussion of resource starvation and the specific case of a bounded channel being filled up before its receiver makes progress. The main responses outlined there are 1. decrease the send rate, 2. increase the receive rate, or 3. increase the channel's internal capacity, noting the extreme approach of changing to an unbounded channel (with the caveat that this risks resource exhaustion). + +Barbara skims the task view for the channel reader, since she wants to determine why it is not making progress. However, she is eager to see if her service as a whole is workable apart from this issue, so she also adopts the quick fix of swapping in an unbounded channel. Barbara is betting that if this works, she can use the data from `wish4-async-insight` about the channel sizes to put a bounded channel with an appropriate size in later. + +Barbara happily moves along to some initial performance analysis of her "working" code, eager to see what other things `wish4-async-insight` will reveal during her explorations. + +### Alternate History + +*The original status quo story just said that Barbara's problem was resolved (sort of) by switching to an unbounded channel. I, much like Barbara, could not tell *why* this resolved her problem. In particular, I could not tell whether there was an outright deadlock due to a cycle in the task-resource dependency chain that, or if there something more subtle happening. In the story above, I assumed it was the second case: something subtle.* + +*Here's an important alternate history though, for the first case of a cycle. Its ...* the same story, right up to when Barbara first runs `consolation`: + +``` +% rustup install wish4-consolation +... +% consolation --port 8080 +``` + +This brings up a terminal window that looks similar to the Unix `top` program, except that instead of a list of OS processes, this offers a list of tasks, and shows their status (i.e. running, ready to poll, or blocked), as well as some metrics about how long the tasks spend in each state. + +At the top of the screen, Barbara sees highlighted warning: "deadlock cycle was detected. hit `P` for more info." + +Barbara types capital `P`. The terminal switches to "problem view," which shows + + * The task types, ID, and attributes for each type. + * The resources being awaited on + * The location / backtrace of the await. + * A link to a documentation page expanding on the issue. + +The screen also says "hit `D` to generate a graphviz `.dot` file to disk describing the cycle." + +Barbara hits `D` and stares at the resulting graph, which shows a single circle (labelled "task"), and an arc to a box (labelled "prototype_channel"), and an arc from that box back to the circle. The arc from the circle to the box is labelled `send: waiting on channel capacity`, and the arc from the box to the circle is labelled "sole consumer (mpsc channel)". + +```mermaid +graph TD + task -- send: waiting on channel capacity --> prototype_channel + prototype_channel -- "sole receiver (mpsc channel)" --> task + task((task)) +``` + +Barbara suddenly realizes her mistake: She had constructed a single task that was sometimes enqueuing work (by sending messages on the channel), and sometimes dequeuing work, but she had not put any controls into place to ensure that the dequeuing (via `recv`) would get prioritized as the channel filled up. + +Barbara reflects on the matter: she knows that she could swap in an unbounded channel to resolve this, but she thinks that she would be better off thinking a bit more about her system design, to see if she can figure out a way to supply back-pressure so that the send rate will go down as the channel fills up. + + +## 🤔 Frequently Asked Questions + +### **What status quo story or stories are you retelling?** + +[Barbara wants Async Insights](../status_quo/barbara_wants_async_insights.md) + +### **What is [Alan] most excited about in this future? Is he disappointed by anything?** + +Alan is happy to see a tool that gives one a view into the internals of the async executor. + +Alan is not so thrilled about using the `consolation` terminal interface; but luckily there are other options, namely IDE/editor plugins as well as a web-browser based client, that offer even richer functionality, such as renderings of the task/resource dependency graph. + +### **What is [Grace] most excited about in this future? Is she disappointed by anything?** + +Grace is happy to see a tool, but wonders whether it could have been integrated into `gdb`. + +Grace is not so thrilled to learn that this tool is not going to try to provide specific insight into performance issues that arise solely from computational overheads in her own code. (The readme for `wish4-async-insight` says on this matter "for that, use perf," which Grace finds unsatisfying.) + +### **What is [Niklaus] most excited about in this future? Is he disappointed by anything?** + +Niklaus is happy to learn that the `wish4-async-insight` is supported by both `async-std` and `tokio`, since he relies on friends in both communities to help him learn more about Async Rust. + +Niklaus is happy about the tool's core presentation oriented around abstractions he understands (tasks and resources). Niklaus is also happy about the integrated help. + +However, Niklaus is a little nervous about some of the details in the output that he doesn't understand. + +### **What is [Barbara] most excited about in this future? Is she disappointed by anything?** + +Barbara is thrilled with how this tool has given her insight into the innards of the async executor she is using. + +She is disappointed to learn that not every async executor supports the `wish4-async-insight` crate. The crate works by monitoring state changes within the executor, instrumented via the `tracing` crate. Not every async-executor is instrumented in a fashion compatible with `wish4-async-insight`. + +### **What [projects] benefit the most from this future?** + +Any async codebase that can hook into the `wish4-async-insight` crate and supply its data via a network port during development would benefit from this. So, I suspect any codebase that uses a sufficiently popular (i.e. appropriately instrumented) async executor will benefit. + +The main exception I can imagine right now is [MonsterMesh][]: its resource constraints and `#![no_std]` environment are almost certainly incompatible with the needs of the `wish4-async-insight` crate. + +### **Are there any [projects] that are hindered by this future?** + +The only "hindrance" is that the there is an expectation that the async-executor be instrumented appropriately to feed its data to the `wish4-async-insight` crate once it is initialized. + +### **What are the incremental steps towards realizing this shiny future?** (Optional) + + * Get tracing crate to 1.0 so that async executors can rely on it. + + * Prototype an insight console atop a concrete async executor (e.g. `tokio`) + + * Develop a shared protocol atop `tracing` that compatible async executors will use to provide the insightful data. + +### **Does realizing this future require cooperation between many projects?** (Optional) + +Yes. Yes it does. + +At the very least, as mentioned among the "incremental steps", we will need a common protocol that the async executors use to communicate their internal state. + + +[character]: ../../characters.md +[comment]: ../../how_to_vision/comment.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../shiny_future.md +[projects]: ../../projects.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[MonsterMesh]: ../../projects/MonsterMesh.md diff --git a/src/vision/submitted_stories/shiny_future/barbara_wants_async_rw.md b/src/vision/submitted_stories/shiny_future/barbara_wants_async_rw.md new file mode 100644 index 00000000..1a096f42 --- /dev/null +++ b/src/vision/submitted_stories/shiny_future/barbara_wants_async_rw.md @@ -0,0 +1,94 @@ +# ✨ Barbara Wants Async Read Write + +## 🚧 Warning: Draft status 🚧 + +This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]! + +## The story + +Character: Barbara. + +Barbara is the creator of a `sans-io` library for Rust. She designed her library to +integrate with `async` and her goal was to make it runtime agnostic; so that it could +be as broadly used as possible. Unfortunately, when she first wrote the library `async` +did not have a standard abstraction for Buffered IO. So her first implementation did +not use buffered IO. When she tried to update her library to use buffered IO so as to +improve performance she was confronted with the problem that each runtime had its own +implementation and abstractions. The result was several unavoidable compromises on her +runtime-agnostic design goals. She was able to achieve her performance improvements +but only with runtime specific implementations; leaving her with a larger more complex +code base. + +But today is a fantastic day for Barbara. The Rust async team has recently released +the latest version of `async` and part of that release was a standard Buffered Async +Read/Write abstraction. Since then, several runtimes have been updated to implement +the new abstraction and Barbara refactored the buffered IO module to use this new +abstraction and she deprecated the runtime specific solutions. Today is the day that +Barbara gets to release her new version of `sans-io` which takes full advantage of the +buffered Async Read/Write abstractions now defined in `async`. The result is a library +that maintains the same performance gains that it had with the runtime specific modules +while greatly reducing the amount of code. + +## 🤔 Frequently Asked Questions + +*NB: These are generic FAQs. Feel free to customize them to your story or to add more.* + +### What status quo stories are you retelling? + +*Link to status quo stories if they exist. If not, that's ok, we'll help find them.* + +### What are the key attributes of this shiny future? + +- Just like AsyncRead/AsyncWrite there are no standard traits for buffered I/O + + - This is made worse by the fact that there isn’t even ecosystem traits for buffered writes. + +- There are no standard (or even present in futures-io) concrete types for async buffered I/O. + + - Each major runtime has their own async BufReader, BufWriter types. + +- All the issues with creating runtime agnostic libraries are very present here. (TODO: link with runtime agnostic lib story) +std::io doesn’t have a BufWrite trait for sync I/O. + + - It’s less of an issue than in async Rust because of the existence of the standardized std::io::BufWriter. + + + +### What is the "most shiny" about this future? + +*Thing about Rust's core "value propositions": performance, safety and correctness, productivity. Which benefit the most relative to today?* +This benefits productivity and correctness the most. The problem is not performance, in particular, as each runtime provides buffered IO solutions. The problem is that they are inconsistent and not compatible. This means that writing code that is compatible with any async runtime becomes both: much more difficult and much more likely to be wrong when runtimes change. + +### What are some of the potential pitfalls about this future? + +*Thing about Rust's core "value propositions": performance, safety and correctness, productivity. Are any of them negatively impacted? Are there specific application areas that are impacted negatively? You might find the sample [projects] helpful in this regard, or perhaps looking at the goals of each [character].* +- Having a design which makes it difficult for existing runtimes to make their buffered IO types compatible or to migrate their runtimes over to the new designs. + +### Did anything surprise you when writing this story? Did the story go any place unexpected? + +*The act of writing shiny future stories can uncover things we didn't expect to find. Did you have any new and exciting ideas as you were writing? Realize some complications that you didn't foresee?* +The most surprising thing is that there is a buffered read type in `futures` but no buffered *write* type in `futures`. I would expect both or neither. + +### What are some variations of this story that you considered, or that you think might be fun to write? Have any variations of this story already been written? + +*Often when writing stories, we think about various possibilities. Sketch out some of the turning points here -- maybe someone will want to turn them into a full story! Alternatively, if this is a variation on an existing story, link back to it here.* +No variations. + +### What are some of the things we'll have to figure out to realize this future? What projects besides Rust itself are involved, if any? (Optional) + +*Often the 'shiny future' stories involve technical problems that we don't really know how to solve yet. If you see such problems, list them here!* + + + +[character]: ../../characters.md +[comment]: ../../how_to_vision/comment.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[projects]: ../../projects.md +[htvsq]: ../shiny_future.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/shiny_future/barbara_wants_async_tracing.md b/src/vision/submitted_stories/shiny_future/barbara_wants_async_tracing.md new file mode 100644 index 00000000..bea1eea5 --- /dev/null +++ b/src/vision/submitted_stories/shiny_future/barbara_wants_async_tracing.md @@ -0,0 +1,112 @@ +# ✨ Barbara wants async tracing + +## 🚧 Warning: Draft status 🚧 + +This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]! + +## The story + +The problem: +When you have a complex network of async tasks it can be difficult to debug issues or investigate behavior because it’s hard to reason through the path of execution just by reading the code. Adding async tracing helps solve this by letting you trace an event through the network and see which async tasks the event executed and when and in what order. + +Character is Barbara: +Barbara’s team works on a set of services that power the API that powers her company’s website and all the +features their customer’s use. They’ve built the backend for these services in Rust and make heavy use of +`async` to manage IO bound operations and help make concurrency easier to leverage. However, the services +have grown quite a bit and there are a large number of features and data requirements and different internal +systems which they must interact with. The result is a very complex network of `async` expressions that do the +job well and perform great, but, are too complex to easily reason about anymore and can be extraordinarily +intimidating when trying to fix transient small issues. Issues such as infrequent slow requests or a very small number +of requests executing certain actions out of order are very hard to resolve when the network of `async` expressions +is complex. + +Recently, Barbara and her team have been notified about some customers experiencing slow responses on +some features. The lag events are rare but Barbara and her team are determined to fix them. With some work +Barbara is able to recreate the lag reliably in the QA environment; but now she must figure out where in the +complex code base this lag could be coming from and why it’s happening. Fortunately, Rust’s `async` framework +now provides a built in Tracing tool. By building her service with the `tracing` flag on, her code is automatically +instrumented and will start logging trace data to a file for later analysis. + +Barbara runs the instrumented code in QA and recreates the laggy event several times. Then she takes the +generated trace file and looks through the data. When she views the trace data with the analysis tools she is given a list +of all the requests from her test, along with a timestamp and duration. She very quickly identifies the slow +requests and chooses to view more detail on one of them. Here she can view a graph of the request's execution: +each async expression is a vertex and edges connect parents to children. Each vertex shows the duration of the +expression and the vertices are arranged vertically by when they started according to the system time. +She immediately sees where each of the slow requests +actually lagged. Each request experienced a slow down in different async expressions, but each expression +had one thing in common: they each queried the same database table. She also noticed that there was a relation +in when the latency occurred: all the laggy requests tended to occur in clusters. From this she was able to identify +that the root cause was some updates made to the database which led to some queries, if they arrived together, +to run relatively slowly. With tracing, Barbara was saved the effort of having to meticulous work through the code +and try to deduce what the cause was and she didn’t have to add in a large amount of logging or other +instrumentation. All the instrumentation and analysis was provided out of the box and required no development +work for Barbara to isolate the cause. + +Barbara can’t believe how much time she saved having this debugging tool provided out of the box. + +## 🤔 Frequently Asked Questions + +*NB: These are generic FAQs. Feel free to customize them to your story or to add more.* + +### What status quo stories are you retelling? + +*Link to status quo stories if they exist. If not, that's ok, we'll help find them.* +[Alan Builds A Cache](https://rust-lang.github.io/wg-async/vision/status_quo/alan_builds_a_cache.html) +[Alan Iteratively Regresses Performance](https://rust-lang.github.io/wg-async/vision/status_quo/alan_iteratively_regresses.html) +[Alan Tries To Debug A Hang](https://rust-lang.github.io/wg-async/vision/status_quo/alan_tries_to_debug_a_hang.html) + +### What are the key attributes of this shiny future? + +- Provide a protocol for linking events across async expressions. +- Provide an output that allows a user to understand the path of execution of a program through a network of async expressions. + +### What is the "most shiny" about this future? + +*Thing about Rust's core "value propositions": performance, safety and correctness, productivity. Which benefit the most relative to today?* + +- This will benefit the productivity of a developer. Providing a whole new way of debugging Rust programs and giving a way to view the actual execution of code in a human readable form can make it significantly faster to debug programs. This also saves time for a developer from having to write a tracer themselves. +- This can also help with correctness. When working with asynchronous code it can be difficult; having a built-in means to trace a flow of execution makes it much easier to verify that specific inputs are following the correct paths in the correct order. + +### What are some of the potential pitfalls about this future? + +*Thing about Rust's core "value propositions": performance, safety and correctness, productivity. Are any of them negatively impacted? Are there specific application areas that are impacted negatively? You might find the sample [projects] helpful in this regard, or perhaps looking at the goals of each [character].* + +- Figuring out how to propagate a trace ID in a way that’s compatible with any use of async could be difficult +- Recording trace data will have some impact on performance. +- We could output too much data for a person to be able to use it. + +### Did anything surprise you when writing this story? Did the story go any place unexpected? + +*The act of writing shiny future stories can uncover things we didn't expect to find. Did you have any new and exciting ideas as you were writing? Realize some complications that you didn't foresee?* + +No. + +### What are some variations of this story that you considered, or that you think might be fun to write? Have any variations of this story already been written? + +Another variation of this story is tracking down functional bugs: where the program is not always executing the expected code paths. An example of this is from the status quo story [Alan Builds A Cache](https://rust-lang.github.io/wg-async/vision/status_quo/alan_builds_a_cache.html). In this type of story, a developer uses tracing to see execution flow of an event as it is fully processed by the application. This can the be used to make sure that every expected or required action is completed and done in the correct order; and if actions were missed, be able to determine why. + +### What are some of the things we'll have to figure out to realize this future? What projects besides Rust itself are involved, if any? (Optional) + +*Often the 'shiny future' stories involve technical problems that we don't really know how to solve yet. If you see such problems, list them here!* + +- There will need to be some form of protocol for how to trace data as they move through a graph of async expressions. Perhaps by weaving a trace ID through the execution of async workflows. We will also have to provide a way "inject" or "wrap" this protocol around the users data in a way that can be automatically done as a compile time option (or is always done behind the scenes). +- A protocol or standard for recording this information and decorating logs or metrics with this data would need to be provided. +- Collecting entry and exit events for async expressions and linking them together in a graph +- How to store the traces +- How to identify each async expression so that a user knows what step in the trace refers to. +- How to show this information to the user. + + +[character]: ../../characters.md +[comment]: ../../how_to_vision/comment.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[projects]: ../../projects.md +[htvsq]: ../shiny_future.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/shiny_future/grace_debugs_a_crash_dump_again.md b/src/vision/submitted_stories/shiny_future/grace_debugs_a_crash_dump_again.md new file mode 100644 index 00000000..190a3162 --- /dev/null +++ b/src/vision/submitted_stories/shiny_future/grace_debugs_a_crash_dump_again.md @@ -0,0 +1,90 @@ +# ✨ Shiny future stories: Grace debugs a crash dump again + +## 🚧 Warning: Draft status 🚧 + +This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]! + +## The story + +It's been a few years since the new [DistriData] database has shipped. For the most part things have gone smoothly. The whole team is confident in trusting the compiler, and they have far fewer bugs in production than they had in the old system. The downside is that now when a bug does make it to production, it tends to be really subtle and take a lot of time to get right. + +Today when Grace opens her e-mail, she discovers she's been assigned to investigate a dump from a crash that has been occurring in production lately. The crash happens rarely, so it's important to glean as much information as possible. They need to get this fixed soon! + +Even though there's a lot of pressure around this situation, Grace is grateful that she won't have to fight her tools to make progress. A lot has changed in Async Rust over the years. The async community got together and defined the Async Debugging Protocol, which provides a standard way for tools to inspect the state of an asynchronous Rust program. Many of the most popular runtimes like Tokio and async-std follow this protocol, and a number of tools have been written to use the protocol as well. Even though Grace's team has opted to build a custom runtime to address their own unique needs, it was not too much work to implement the Async Debugging Protocol and it was well worth it due to the increase in developer productivity. This has truly revolutionized async debugging in much the same way the [Language Server Protocol] did for IDEs. + +Upon opening the crash dump, her favorite debugger immediately gives an overview of the state of the program at the point it crashed. It shows what executors are running, how many OS-level threads each executor is using, what tasks are there, and what the state of each task is. For each thread, Grace can see a stack trace and the debugger provides a logical stack trace for each task as well. Many of the resources that the blocked tasks are waiting on are visible too, particularly those provided by the runtime like timers, mutexes, and I/O. + +This high level, generic view provides a good start, but the team's custom executor provides additional functionality that the Async Debugging Protocol does not support. Still, using the features already provided as a starting point, Grace was able to write some additional debugging macros to recover the additional state. These macros are used by the whole team and are now a standard part of their debugging toolkit. + +Grace has seen a few instances of this crash now and she notices a constellation of tasks that look a little funny. This gives her an idea for what might be going wrong. She uses that to add a new test case than ends up crashing the service in a way that looks very similar. It seems like she's found the bug! Even better, it looks like it should be a simple fix and the team will be able to put this issue behind them once and for all. + +## 🤔 Frequently Asked Questions + +### What status quo stories are you retelling? + +Grace debugs a crash dump. + +### What are the key attributes of this shiny future? + +* Most of the abilities to inspect executor and task state while debugging a live process also work on crash dumps. +* Debugging async programs is both runtime- and tooling- agnostic. + * People should be able to get a good experience using whatever tools they are comfortable with, whether that's gdb, lldb, VS Code, IntelliJ, or a specialized Rust async debugger. + * Debugging tools should be able to work with different runtimes. Not all projects in an organization will use the same runtime, and some may be custom. +* It's possible to see the following things while debugging: + * What tasks are running, along with logical stack traces. + * Some idea of what the task is waiting on if it is blocked. + * If there are multiple executors, we can inspect each one. + * Raw stack traces for the OS-level threads that the executors use to schedule tasks. + * Which futures have been passed into a `select!`, their current state, and which one is being polled. +* Additional tooling may be necessary for custom or exotic executors. The hypothetical Async Debugging Protocol is one size fits all, but one size won't fit all. We don't want to constrain what an executor can do just so we can debug it. +* An async runtime should not be required to support these common debugging features. For example, perhaps it requires more space to support and therefore is not appropriate for an extremely constrained embedded environment. + +I envisioned provided this with some kind of "Async Debugging Protocol" that is analogous to the Language Server Protocol. It's not really clear what this would be exactly, and there may be a better approach to solving these problems. For live debugging, it may be as simple as a few traits the executor can implement that provide introspection capabilities. For crash dumps, maybe there's a convention around including a couple of debugging symbols. It might require some kind of rich metadata format that tells the debugger how to inspect and interpret the core data structures for the executor. + +### What is the "most shiny" about this future? + + +The biggest aspect of this shiny future is the increased developer productivity, particularly in debugging. Many of the status quo stories called out the difficulty of debugging async code. In this shiny future, there are really good tools for live debugging, and many of these work offline in the crash dump case as well. + +As a follow-on, the enhanced developer productivity will support writing more correct and safer programs, and probably allow developers to diagnose performance problems as well. These are a direct consequence of better debugging, but rather an indirect consequence of giving the developer better tools. + +### What are some of the potential pitfalls about this future? + +Depending on how the "Async Debugging Protocol" works, there may be some overhead in following it. Hopefully this would be minimal, and not require any additional code during normal execution scenarios. But, it might make the debugging symbols or other metadata larger. Following the protocol may constrain some of the choices an async runtime can make. + +At the very least, choosing to follow the protocol will require additional work on the part of the runtime implementor. + +### Did anything surprise you when writing this story? Did the story go any place unexpected? + +Doing this in a way that is runtime and tooling agnostic will be challenging, so the details of how that could be done are not included in this story. + +In some ways, doing this for a live process seems easier, since you can write code that inspects or reports on its own state. This seems to be the approach that [tokio-console] is taking. + +There seems to be a lot of overlap between live debugging scenarios and post-mortem scenarios. With a little care, it might be able to support both using many of the same underlying capabilities. + +[tokio-console]: https://github.com/tokio-rs/console + +### What are some variations of this story that you considered, or that you think might be fun to write? Have any variations of this story already been written? + +It would be worth removing the runtime agnostic aspect of this story and looking at how things would look if we just focused on Tokio or async-std. Perhaps each runtime would include a set of debugger macros to help find the runtime's state. + +### What are some of the things we'll have to figure out to realize this future? What projects besides Rust itself are involved, if any? (Optional) + +A lot of the work here probably will not be done by the core Rust team, other than perhaps to coordinate and guide it. Most of the work will require coordination among projects like Tokio and async-std, as well as the debugging tool authors. + +There does not seem to be an obvious way to implement everything in this story. It would probably be good to focus on a particular runtime at least to get a proof of concept and better sketch out the requirements. + +[character]: ../../characters.md +[comment]: ../../how_to_vision/comment.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[projects]: ../../projects.md +[htvsq]: ../shiny_future.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[DistriData]: ../../projects/DistriData.md +[Language Server Protocol]: https://microsoft.github.io/language-server-protocol/ \ No newline at end of file diff --git a/src/vision/submitted_stories/shiny_future/template.md b/src/vision/submitted_stories/shiny_future/template.md new file mode 100644 index 00000000..5518a20f --- /dev/null +++ b/src/vision/submitted_stories/shiny_future/template.md @@ -0,0 +1,69 @@ +# ✨ Shiny future stories: template + +*This is a template for adding new "shiny future" stories. To propose a new shiny future PR, do the following:* + +* *Create a new file in the [`shiny_future`] directory named something like `Alan_loves_foo.md` or `Grace_does_bar_and_its_great.md`, and start from [the raw source from this template]. You can replace all the italicized stuff. :)* +* *Do not add a link to your story to the [`SUMMARY.md`] file; we'll do it after merging, otherwise there will be too many conflicts.* + +*For more detailed instructions, see the [How To Vision: Shiny Future] page!* + +[How To Vision: Shiny Future]: ../shiny_future.md +[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async/master/src/vision/shiny_future/template.md +[`shiny_future`]: https://github.com/rust-lang/wg-async/tree/master/src/vision/shiny_future +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md + + +## 🚧 Warning: Draft status 🚧 + +This is a draft "shiny future" story submitted as part of the brainstorming period. It is derived from what actual Rust users wish async Rust should be, and is meant to deal with some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as peoples needs and desires for async Rust may differ greatly, shiny future stories [cannot be wrong]. At worst they are only useful for a small set of people or their problems might be better solved with alternative solutions). Alternatively, you may wish to [add your own shiny vision story][htvsq]! + +## The story + +*Write your story here! Feel free to add subsections, citations, links, code examples, whatever you think is best.* + +## 🤔 Frequently Asked Questions + +*NB: These are generic FAQs. Feel free to customize them to your story or to add more.* + +### What status quo stories are you retelling? + +*Link to status quo stories if they exist. If not, that's ok, we'll help find them.* + +### What are the key attributes of this shiny future? + +*Summarize the main attributes of the design you were trying to convey.* + +### What is the "most shiny" about this future? + +*Thing about Rust's core "value propositions": performance, safety and correctness, productivity. Which benefit the most relative to today?* + +### What are some of the potential pitfalls about this future? + +*Thing about Rust's core "value propositions": performance, safety and correctness, productivity. Are any of them negatively impacted? Are there specific application areas that are impacted negatively? You might find the sample [projects] helpful in this regard, or perhaps looking at the goals of each [character].* + +### Did anything surprise you when writing this story? Did the story go any place unexpected? + +*The act of writing shiny future stories can uncover things we didn't expect to find. Did you have any new and exciting ideas as you were writing? Realize some complications that you didn't foresee?* + +### What are some variations of this story that you considered, or that you think might be fun to write? Have any variations of this story already been written? + +*Often when writing stories, we think about various possibilities. Sketch out some of the turning points here -- maybe someone will want to turn them into a full story! Alternatively, if this is a variation on an existing story, link back to it here.* + +### What are some of the things we'll have to figure out to realize this future? What projects besides Rust itself are involved, if any? (Optional) + +*Often the 'shiny future' stories involve technical problems that we don't really know how to solve yet. If you see such problems, list them here!* + + + +[character]: ../../characters.md +[comment]: ../../how_to_vision/comment.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[projects]: ../../projects.md +[htvsq]: ../shiny_future.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo.md b/src/vision/submitted_stories/status_quo.md new file mode 100644 index 00000000..8be24009 --- /dev/null +++ b/src/vision/submitted_stories/status_quo.md @@ -0,0 +1,172 @@ +# 😱 Status quo stories + +## 🚧 Under construction! Help needed! 🚧 + +We are still in the process of drafting the vision document. The stories you see on this page are examples meant to give a feeling for how a status quo story looks; you can expect them to change. See the ["How to vision"][htv] page for instructions and details. + +[htv]: ../how_to_vision.md + +## What is this + +The "status quo" stories document the experience of using Async Rust today. Each story narrates the challenges encountered by [one of our characters][cc] as they try (and typically fail in dramatic fashion) to achieve their goals. + +[cc]: ../characters.md + +Writing the "status quo" stories helps us to compensate for the [curse of knowledge][cok]: the folks working on Async Rust tend to be experts in Async Rust. We've gotten used to the workarounds required to be productive, and we know the little tips and tricks that can get you out of a jam. The stories help us gauge the cumulative impact all the paper cuts can have on someone still learning their way around. This gives us the data we need to prioritize. + +[cok]: https://en.wikipedia.org/wiki/Curse_of_knowledge + +### Based on a true story + +These stories may not be true, but they are not fiction. They are based on real-life experiences of actual people. Each story contains a "Frequently Asked Questions" section referencing sources used to create the story. In some cases, it may link to notes or summaries in the [conversations] section, though that is not required. The "Frequently Asked Questions" section also contains a summary of what the "morals" of the story are (i.e., what are the key takeaways), along with answers to questions that people have raised along the way. + +[conversations]: ../../conversations.md + +### The stories provide data we use to prioritize, not a prioritization itself + +**Just because a user story is represented here doesn't mean we're going to be able to fix it right now.** Some of these user stories will indicate more severe problems than others. As we consider the stories, we'll select some subset to try and address; that choice is reflected in the [roadmap]. + +[roadmap]: ../roadmap.md + +## Metanarrative + +*What follows is a kind of "metanarrative" of using async Rust that summarizes the challenges that are present today. At each point, we link to the various stories; you can read the full set in the table of contents on the left. We would like to extend this to also cover some of its glories, since reading the current stories is a litany of difficulties, but obviouly we see great promise in async Rust. Note that many stories here appear more than once.* + +Rust strives to be a language that brings together performance, productivity, and correctness. Rust programs are designed to surface bugs early and to make common patterns both ergonomic and efficient, leading to a sense that "if it compiles, it generally works, and works efficiently". Async Rust aims to extend that same feeling to an async setting, in which a single process interweaves numerous tasks that execute concurrently. Sometimes this works beautifully. However, other times, the reality falls short of that goal. + +
Making hard choices from a complex ecosystem from the start + +The problems begin from the very first moment a user starts to try out async Rust. The async Rust support in Rust itself is very basic, consisting only of the core Future mechanism. Everything else -- including the basic async runtimes themselves -- lives in user space. This means that users must make a number of choices from the very beginning: + +* what runtime to use + * [Barbara makes their first foray into async](status_quo/barbara_makes_their_first_steps_into_async.md) + * [Niklaus wants to share knowledge](status_quo/niklaus_wants_to_share_knowledge.md) +* what http libraries to use + * [Barbara anguishes over http](status_quo/barbara_anguishes_over_http.md) +* basic helpers and utility crates are hard to find, and there are many choices, often with subtle differences between them + * [Barbara needs async helpers](status_quo/barbara_needs_async_helpers.md) +* Furthermore, the async ecosystem is fractured. Choosing one library may entail choosing a specific runtime. Sometimes you may wind up with multiple runtimes running at once. But sometimes you want that! + * [Alan started trusting the rust compiler but then async](status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md) + * [Barbara needs async helpers](status_quo/barbara_needs_async_helpers.md) +* Of course, sometimes you *want* multiple runtimes running together + * [Alan has an external event loop and wants to use futures/streams](https://rust-lang.github.io/wg-async/vision/status_quo/alan_has_an_event_loop.html) + * 🚧 [Need more stories about multiple runtimes working together](https://github.com/rust-lang/wg-async/issues/183) +* There is a lack of common, standardized abstractions, which means that often there are multiple attempts to establish common traits and different libraries will employ a distinct subset. + * [`Sink` is not implemented by async-std websockets](status_quo/alan_tries_a_socket_sink.md) + * 🚧 [No standardized lower-level traits for read, write, iterators in an async setting](https://github.com/rust-lang/wg-async/issues/177) + * 🚧 [Lack of widely used higher-level abstractions (like those tower aims to provide)](https://github.com/rust-lang/wg-async/issues/178) + * 🚧 [Tokio has `Stream` support in tokio-stream for stability concerns](https://github.com/rust-lang/wg-async/issues/179) +* Some of the problems are due to the design of Rust itself. The coherence rules in particular. + * 🚧 [Write about how coherence makes it nearly impossible to establish standard traits outside of libstd.](https://github.com/rust-lang/wg-async/issues/180) + +
+ +
Once your basic setup is done, the best design patterns are subtle and not always known. + +Writing async programs turns out to have all kinds of subtle tradeoffs. Rust aims to be a language that gives its users control, but that also means that users wind up having to make a lot of choices, and we don't give them much guidance. + +* If you need synchronization, you might want an async lock, but you might want a synchronous lock, it's hard to know. + * [Alan thinks he needs async locks](status_quo/alan_thinks_he_needs_async_locks.md) +* Mixing sync and async code is tricky and it's not always obvious how to do it -- something it's not even clear what is "sync" (how long does a loop have to run before you can consider it blocking?) + * [Barbara bridges sync and async](status_quo/barbara_bridges_sync_and_async.md) + * [Barbara compares some C++ code](status_quo/barbara_compares_some_cpp_code.md) + * [Alan thinks he needs async locks](status_quo/alan_thinks_he_needs_async_locks.md) -- "locks are ok if they don't take too long" +* There are often many options for doing things like writing futures or other core concepts; which libraries or patterns are best? + * [Barbara needs async helpers](status_quo/barbara_needs_async_helpers.md) + * [Grace wants to integrate c api](status_quo/grace_wants_to_integrate_c_api.html#the-second-problem-doing-this-many-times) + * [Barbara plays with async](status_quo/barbara_plays_with_async.md), where she tries a number of combinations before she lands on `Box::pin(async move { .. })` +* If you would to have data or task parallel operations, it's not always obvious how to do that + * [Barbara plays with async](status_quo/barbara_plays_with_async.md) + * [Barbara tries async streams](status_quo/barbara_tries_async_streams.md) + * [Niklaus builds a hydrodynamic simulator](status_quo/niklaus_simulates_hydrodynamics.md) +* Sometimes it's hard to understand what will happen when the code runs + * [Grace wants to integrate c api](status_quo/grace_wants_to_integrate_c_api.html#the-second-problem-doing-this-many-times) + * [Barbara bridges sync and async](status_quo/barbara_bridges_sync_and_async.md) +* Sometimes async may not even be the right solution + * [Niklaus builds a hydrodynamic simulator](status_quo/niklaus_simulates_hydrodynamics.md) + * 🚧 [Avoiding async entirely](https://github.com/rust-lang/wg-async/issues/58) + +
+ +
Even once you've chosen a pattern, gettings things to compile can be a challenge. + +* Async fn doesn't work everywhere + * [not in traits](status_quo/alan_needs_async_in_traits.md) + * not in closures -- [barbara plays with async](status_quo/barbara_plays_with_async.md) + * [barbara needs async helpers](status_quo/barbara_needs_async_helpers.md) +* Recursion doesn't work + * [barbara needs async helpers](status_quo/barbara_needs_async_helpers.md) +* Things have to be Send all the time, some things can't live across an await + * [send isn't what it means anymore](https://tomaka.medium.com/a-look-back-at-asynchronous-rust-d54d63934a1c) + * [alan thinks he needs async locks](status_quo/alan_thinks_he_needs_async_locks.md) +* The tricks you know from Sync rust apply but don't quite work + * e.g., Box::pin, not Box::new -- [barbara plays with async](status_quo/barbara_plays_with_async.md) +* Sometimes you have to add `boxed` + * [Grace tries new libraries](status_quo/grace_tries_new_libraries.md) +* Writing strings is hard + * [Grace wants to integrate a C API](status_quo/grace_wants_to_integrate_c_api.html#the-second-problem-doing-this-many-times) +* When you stray from the happy path, the complexity cliff is very steep + * Working with Pin is really hard, but necessary in various scenarios + * 🚧 [Need a story about implementing async-read, async-write](https://github.com/rust-lang/wg-async/issues/181) + * [Alan hates writing a stream](status_quo/alan_hates_writing_a_stream.md) + * It's easy to forget to invoke a waker + * [Alan hates writing a stream](status_quo/alan_hates_writing_a_stream.html#-frequently-asked-questions) + * [Grace deploys her service](status_quo/grace_deploys_her_service.md) + * Ownership and borrowing rules get really complicated when async is involved + * [Alan writes a web framework](status_quo/alan_writes_a_web_framework.md) + * Sometimes you want `&mut` access that ends while the future is suspended + * [Alan lost the world](status_quo/alan_lost_the_world.md) + * [Ghostcell](status_quo/barbara_wants_to_use_ghostcell.md) + * Writing executors is pretty non-trivial, things have to have references to one another in a way that is not very rusty + * [barbara builds an async executor](status_quo/barbara_builds_an_async_executor.md) + +
+ +
Once you get it to compile, things don't "just work" at runtime, or they may be unexpectedly slow. + +* Libraries are tied to particular runtimes and those runtimes can panic when combined, or require special setup + * [Alan started trusting the rust compiler but then async](status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md) + * [Alan picks a web server](status_quo/alan_picks_web_server.md) +* Cancellation can in principle occur at any point in time, which leads to subtle bugs + * [Alan builds a cache](status_quo/alan_builds_a_cache.md) + * [Alan finds dropping database handles is hard](status_quo/alan_finds_database_drops_hard.md) + * [Barbara gets burned by select](https://github.com/rust-lang/wg-async/pull/169) +* Dropping is synchronous but sometimes wants to do asynchronous things and block for them to complete + * [Alan finds dropping database handles is hard](status_quo/alan_finds_database_drops_hard.md) +* Nested awaits mean that outer awaits cannot make progress + * [Barbara battles buffered streams](status_quo/barbara_battles_buffered_streams.md) +* Async functions let you build up large futures that execute without allocation, which is great, but can be its own cost + * [Alan iteratively regresses](status_quo/alan_iteratively_regresses.md) + * [Alan runs into stack allocation trouble](status_quo/alan_runs_into_stack_trouble.md) +* It's easy to have async functions that inadvertently spend too long in between awaits + * [Barbara compares some C++ code](status_quo/barbara_compares_some_cpp_code.md) + +
+ +
When you have those problems, you can't readily debug them or get visibility into what is going on. + +* The state of the executor can be very opaque: what tasks exist? why are they blocked? + * [Alan tries to debug a hang](status_quo/alan_tries_to_debug_a_hang.md) + * [Barbara tries unix socket](status_quo/barbara_tries_unix_socket.md) + * [Barbara wants async insights](status_quo/barbara_wants_async_insights.md) + * [Grace deploys her service](status_quo/grace_deploys_her_service.md) +* Stacktraces are full of gobbly gook and hard to read. + * [Barbara trims a stacktrace](status_quo/barbara_trims_a_stacktrace.md) +* Tooling doesn't work as well with async or just plain doesn't exist. + * [Grace waits for gdb](status_quo/grace_waits_for_gdb_next.md) + * [Alan iteratively regresses](status_quo/alan_iteratively_regresses.md) + +
+ +
Rust has always aimed to interoperate well with other languages and to fit itself into every niche, but that's harder with async. + +* Runtimes like tokio and async-std are not designed to "share ownership" of the event loop with foreign runtimes + * [Alan has an event loop](status_quo/alan_has_an_event_loop.md) +* Embedded environments can have pretty stringent requirements; Future was designed to be minimal, but perhaps not minimal enough + * [Barbara carefully discusses embedded future](status_quo/barbara_carefully_dismisses_embedded_future.md) +* Evolving specs for C and C++ require careful thought to integrate with async Rust's polling model + * 🚧 [Wrapping C++ APIs in Rust Futures](https://github.com/rust-lang/wg-async/issues/67) + * 🚧 [Write about the challenges of io-uring integration](https://github.com/rust-lang/wg-async/issues/182) +* Advanced new techniques like [Ghostcell](status_quo/barbara_wants_to_use_ghostcell.md) may not fit into the traits as designed + +
diff --git a/src/vision/submitted_stories/status_quo/alan_builds_a_cache.md b/src/vision/submitted_stories/status_quo/alan_builds_a_cache.md new file mode 100644 index 00000000..b0220fcd --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_builds_a_cache.md @@ -0,0 +1,97 @@ +# 😱 Status quo stories: Alan tries to cache requests, which doesn't always happen. + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + + +## The story + +[Alan][] is working on an HTTP server. The server makes calls to some other service. The performance of the downstream service is somewhat poor, so Alan would like to implement some basic caching. + +[Alan]: ../../characters/alan.md + +Alan writes up some code which does the caching: + +```rust +async fn get_response(&mut self, key: String) { + // Try to get the response from cache + if let Some(cached_response) = self.cache.get(key) { + self.channel.send(cached_response).await; + return; + } + + // Get the response from the downstream service + let response = self.http_client.make_request(key).await; + self.channel.send(response).await; + + // Store the response in the cache + self.cache.set(key, response); +} +``` + +Alan is happy with how things are working, but notices every once in a while the downstream service hangs. To prevent that, Alan implements a timeout. + +He remembers from the documentation for his favorite runtime that there is the `race` function which can kick off two futures and polls both until one completes (similar to tokio's [select](https://docs.rs/tokio/1.5.0/tokio/macro.select.html) and async-std's [race](https://docs.rs/async-std/1.9.0/async_std/future/trait.Future.html#method.race) for example). + + +```rust +runtime::race(timeout(), get_response(key)).await +``` + +## The bug + +Alan ships to production but after several weeks he notices some users complaining that they receive old data. + +Alan looks for help. The compiler unfortunately doesn't provide any hints. He turns to his second best friend clippy, who cannot help either. +Alan tries debugging. He uses his old friend `println!`. After hours of working through, he notices that sometimes the line that sets the response in the cache never gets called. + +## The solution + +Alan goes to [Barbara][] and asks why in the world that might be ⁉️ + +💡 Barbara looks through the code and notices that there is an await point between sending the response over the channel and setting the cache. + +Since the `get_response` future can be dropped at each available await point, it may be dropped *after* the http request has been made, but *before* the response has successfully been sent over the channel, thus not executing the remaining instructions in the function. + +This means the cache might not be set. + +Alan fixes it by setting the cache before sending the result over the channel. 🎉 + +```rust +async fn get_response(&mut self, key: String) { + // ... cache miss happened here + + // We perform the HTTP request and our code might continue + // after this .await once the HTTP request is complete + let response = self.http_client.make_request(key).await; + + // Immediately store the response in the cache + self.cache.set(key, response); + + self.channel.send(response).await; +} +``` + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +* Futures can be "canceled" at any await point. Authors of futures must be aware that after an await, the code might not run. + * This is similar to `panic` safety but way more likely to happen +* Futures might be polled to completion causing the code to work. But then many years later, the code is changed and the future might conditionally not be polled to completion which breaks things. +* The burden falls on the user of the future to poll to completion, and there is no way for the lib author to enforce this - they can only document this invariant. +* Diagnosing and ultimately fixing this issue requires a fairly deep understanding of the semantics of futures. +* Without a Barbara, it might be hard to even know where to start: No lints are available, Alan is left with a normal debugger and `println!`. + +### **What are the sources for this story?** +The relevant sources of discussion for this story have been gathered [in this github issue](https://github.com/rust-lang/wg-async/issues/65). + +### **Why did you choose Alan to tell this story?** +Alan has enough experience and understanding of push based async languages to make the assumptions that will trigger the bug. + +### **How would this story have played out differently for the other characters?** +This story would likely have played out the same for almost everyone but Barbara, who has probably been bitten by that already. +The debugging and fixing time would however probably have varied depending on experience and luck. diff --git a/src/vision/submitted_stories/status_quo/alan_builds_a_task_scheduler.md b/src/vision/submitted_stories/status_quo/alan_builds_a_task_scheduler.md new file mode 100644 index 00000000..b9d62826 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_builds_a_task_scheduler.md @@ -0,0 +1,704 @@ +# 😱 Status quo stories: Alan builds a task scheduler + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +A core component of [`DistriData`][distridata], called `TaskScheduler`, is in charge of (1) receiving client requests via an HTTP server, (2) serializing them in a task queue, (3) relaying each task to the state machine applier (e.g., apply change to the storage backend), and (4) returning the result back to the client. + +`TaskScheduler` was originally implemented in Go. New to Rust, [Alan][] believes Rust could provide the same quality of service but with less memory. Then decides to reimplement `TaskScheduler` in Rust, without knowing the challenges ahead. + +Alan only read the first few chapters of [Rust book](https://doc.rust-lang.org/nightly/book/title-page.html) to understand the core concepts like ownership model and syntax. Already proficient in Go, Alan jumped into the coding by working through a hands-on project. Alan often referred to the examples found in each Rust crate but may lack deep understanding of how Rust works. Alan first focused on translating the Go code to Rust and as a result, the first iteration may be filled with non-idiomatic Rust code. + +### **Implementing request ID generator** + +Alan first transliterates request ID generator code, originally written in Go: + +```go +import "sync/atomic" + +type Generator interface { + next() uint64 +} + +type generator struct { + prefix uint64 + suffix uint64 +} + +func (gen *generator) next() uint64 { + suffix := atomic.SwapUint64(&gen.suffix, gen.suffix+1) + id := gen.prefix | (suffix & (math.MaxUint64 >> 16)) + return id +} +``` + +Alan learns Rust trait as the closest concept to Go interface but is now torn between [std::sync::atomic](https://doc.rust-lang.org/std/sync/atomic) and [crossbeam::atomic::AtomicCell](https://docs.rs/crossbeam). Reading multiple articles about how great crossbeam is and for its thread-safety promises, Alan chooses crossbeam (see ["crates better than std (from Reddit)"](https://www.reddit.com/r/rust/comments/hat5bt/what_are_your_favorite_better_than_std_crates/)): + +```rust +use crossbeam::atomic::AtomicCell; + +pub struct Generator { + prefix: u64, + suffix: AtomicCell, +} + +impl Generator { + pub fn new(...) -> Self { + ... + } + + pub fn next(&self) -> u64 { + let suffix = self.suffix.fetch_add(1); + let id = self.prefix | (suffix & (u64::MAX >> 16)); + id + } +} +``` + +Accustomed to an opinionated way of doing concurrency in Go, Alan loses confidence in Rust async support, as he sees fragmented but specialized solutions in Rust async ecosystem. + +### **Implementing event notifier** + +Alan then implements the notifier to propagate the request and apply the progress with the scheduler and low-level state machine. In Go, it can be simply implemented as below: + +```go +type Notifier interface { + register(id uint64) (<-chan string, error) + trigger(id uint64, x string) error +} + +type notifier struct { + mu sync.RWMutex + requests map[uint64]chan string +} + +func (ntf *notifier) register(id uint64) (<-chan string, error) { + ntf.mu.Lock() + defer ntf.mu.Unlock() + ch := ntf.requests[id] + if ch != nil { + return nil, fmt.Errorf("dup id %x", id) + } + + ch = make(chan string, 1) + ntf.requests[id] = ch + return ch, nil +} + +func (ntf *notifier) trigger(id uint64, x string) error { + ntf.mu.Lock() + ch, ok := ntf.requests[id] + if ch == nil || !ok { + ntf.mu.Unlock() + return fmt.Errorf("request ID %d not found", id) + } + delete(ntf.requests, id) + ntf.mu.Unlock() + ch <- x + close(ch) + return nil +} +``` + +Alan now needs the equivalent to Go `sync.RWMutex`, and found multiple options: + +- [`std::sync::RwLock`](https://doc.rust-lang.org/std/sync/struct.RwLock.html) +- [`parking_lot::RwLock`](https://docs.rs/parking_lot) + +Already losing confidence in Rust std, Alan instead chooses `parking_lot`, as it claims up to 5x faster performance than `std::sync::Mutex` (see [github](https://github.com/Amanieu/parking_lot#parking_lot)). After numeruous hours of trials and errors, Alan discovered that `parking_lot::RwLock` is not intended for async/future environments (see [github issue](https://github.com/Amanieu/parking_lot/issues/86)). Having to think about which library to use for thread and async programming, Alan appreciates the simplicity of Go concurrency where threads are effectively abstracted away from its users. Alan is now using [`async_std::sync::RwLock`](https://docs.rs/async-std/1.9.0/async_std/sync/struct.RwLock.html) which seems nicely integrated with Rust async programming. + +To send and receive events, Alan needs the equivalent of Go channel but is not sure about [`std::sync::mpsc::channel`](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html), as he sees two other options: [Flume](https://github.com/zesterer/flume) which claims to be much faster than std (see ["Flume, a 100% safe MPSC that's faster than std (from Reddit)"](https://www.reddit.com/r/rust/comments/fj17z6/flume_a_100_safe_mpsc_thats_faster_than_std_and/)), and [`crossbeam_channel`](https://docs.rs/crossbeam-channel/0.5.1/crossbeam_channel/). Having used crossbeam, Alan chose crossbeam channel: + +```rust +use async_std::sync::RwLock; +use crossbeam_channel::{self, unbounded}; + +pub struct Notifier { + requests: RwLock>>, +} + +impl Notifier { + pub fn new() -> Self { + Self { + requests: RwLock::new(HashMap::new()), + } + } + + pub fn register(&self, id: u64) -> io::Result> { + let mut _mu; + match self.requests.try_write() { + Some(guard) => _mu = guard, + None => return Err(...), + } + + let (request_tx, request_rx) = unbounded(); + if _mu.get(&id).is_none() { + _mu.insert(id, request_tx); + } else { + return Err(...) + } + + Ok(request_rx) + } + + pub fn trigger(&self, id: u64, x: String) -> io::Result<()> { + let mut _mu; + match self.requests.try_write() { + Some(guard) => _mu = guard, + None => return Err(...), + } + + let request_tx; + match _mu.get(&id) { + Some(ch) => request_tx = ch, + None => return Err(...), + } + + match request_tx.send(x) { + Ok(_) => _mu.remove(&id), + Err(e) => return Err(...), + } + + Ok(()) + } +} +``` + +Alan is still not sure if `crossbeam_channel` is safe for async programming and whether he should instead use another crate [`async_std::channel`](https://docs.rs/async-std/1.9.0/async_std/channel/index.html). While `crossbeam_channel` seems to work, Alan is not confident about his choice. Disgruntled with seemingly unnecessary divergence in the community, Alan wonders why all those cool improvements had not been made back to Rust core std libraries. + +### **Implementing task applier** + +Alan implements a task applier, which simply echoes the requested message, as in Go: + +```go +type EchoManager interface { + apply(req *EchoRequest) (string, error) +} + +type echoManager struct { + mu sync.RWMutex +} + +func (ea *echoManager) apply(req *EchoRequest) (string, error) { + ea.mu.Lock() + defer ea.mu.Unlock() + switch req.Kind { + case "create": + return fmt.Sprintf("SUCCESS create %q", req.Message), nil + case "delete": + return fmt.Sprintf("SUCCESS delete %q", req.Message), nil + default: + return "", fmt.Errorf("unknown request %q", req) + } +} +``` + +Having implemented event notifier above, Alan is now somewhat familiar with Rust mutex and writes the following Rust code: + +```rust +// 1st version +use async_std::sync::RwLock; + +pub struct Manager { + mu: RwLock<()>, +} + +impl Manager { + pub fn new() -> Self { + Self { + mu: RwLock::new(()), + } + } + + pub fn apply(&self, req: &Request) -> io::Result { + let _mu; + match self.mu.try_write() { + Some(guard) => _mu = guard, + None => return Err(...), + } + match req.kind.as_str() { + "create" => Ok(format!( + "SUCCESS create {}", + to_string(req.message.to_owned()) + )), + "delete" => Ok(format!( + "SUCCESS delete {}", + to_string(req.message.to_owned()) + )), + _ => Err(...), + } + } +} +``` + +The code compiles and thus must be safe. However, after reviewing the code with [Barbara][], Alan learns that while `std::sync::Mutex` protects data from concurrent access, `std::sync::Mutex` itselt must be also protected between threads. And the code will not compile if he tries to use it from multiple threads. This is where [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) comes in to provide safe multi-threaded access to the `Mutex`. + +[`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) documentation explains `Arc` in depth. If Alan had chosen `std::sync::Mutex` library, he would have known about `Arc`. Because Alan was initially given multiple alternatives for mutex, he overlooked the documentation in `std::sync::Mutex` and instead used [`async_std::sync::RwLock`](https://docs.rs/async-std/1.9.0/async_std/sync/struct.RwLock.html) whose documentation did not explain `Arc`. As a result, Alan did not know how to properly use mutex in Rust. + +Deeply confused, Alan made a quick fix to wrap `Mutex` with `Arc`: + +```rust +// 2nd version +use async_std::{sync::Arc, sync::RwLock}; + +pub struct Manager { + mu: Arc>, +} + +impl Manager { + pub fn new() -> Self { + Self { + mu: Arc::new(RwLock::new(())), + } + } + ... +``` + +This raises multiple questions for Alan: + +1. If `Mutex` itself had to be protected, why `Arc` is not unified into a single type? Is the flexibility of having different types really worth the less safety guarantee? +2. Rust claims unparalleled safety. Is it still true for async programming? Rust compiler not complaining about the missing `Arc` means `Mutex` is still safe without `Arc`? +3. What happens if the code went into production without `Arc`? Would the code have race conditions? +4. Does having `Arc` make code slower? Did I just introduce extra runtime cost? +5. Which one is safe for async programming: `std::sync::Arc` and `async_std::sync::Arc`? + +### **Implementing task scheduler** + +Alan then implements the task scheduler that calls event notifier and task applier above, as in Go: + +```go +type Request struct { + echoRequest *EchoRequest +} + +type Applier interface { + start() + stop() error + apply(req Request) (string, error) +} + +type applier struct { + requestTimeout time.Duration + + requestIDGenerator Generator + notifier Notifier + + requestCh chan requestTuple + + stopCh chan struct{} + doneCh chan struct{} + + echoManager EchoManager +} + +type requestTuple struct { + requestID uint64 + request Request +} + +func (ap *applier) start() { + go func() { + for { + select { + case tup := <-ap.requestCh: + reqID := tup.requestID + req := tup.request + switch { + case req.echoRequest != nil: + rs, err := ap.echoManager.apply(req.echoRequest) + if err != nil { + rs = fmt.Sprintf("failed to apply %v", err) + } + if err = ap.notifier.trigger(reqID, rs); err != nil { + fmt.Printf("failed to trigger %v", err) + } + default: + } + case <-ap.stopCh: + ap.doneCh <- struct{}{} + return + } + } + }() +} + +func (ap *applier) stop() error { + select { + case ap.stopCh <- struct{}{}: + case <-time.After(5 * time.Second): + return errors.New("took too long to signal stop") + } + select { + case <-ap.doneCh: + case <-time.After(5 * time.Second): + return errors.New("took too long to receive done") + } + return nil +} + +func (ap *applier) apply(req Request) (string, error) { + reqID := ap.requestIDGenerator.next() + respRx, err := ap.notifier.register(reqID) + if err != nil { + return "", err + } + + select { + case ap.requestCh <- requestTuple{requestID: reqID, request: req}: + case <-time.After(ap.requestTimeout): + if err = ap.notifier.trigger(reqID, fmt.Sprintf("failed to schedule %d in time", reqID)); err != nil { + return "", err + } + } + + msg := "" + select { + case msg = <-respRx: + case <-time.After(ap.requestTimeout): + return "", errors.New("apply timeout") + } + + return msg, nil +} +``` + +Not fully grokking Rust ownership model in async, Alan implements the following code, but faced with a bunch of compiler error messages: + +```rust +use async_std::task; + +pub struct Applier { + notifier: notify::Notifier, + ... +} + +impl Applier { + pub fn new(req_timeout: Duration) -> Self { + ... + Self { + ... + notifier: notify::Notifier::new(), + ... + } + } + ... + + pub async fn start(&self) -> io::Result<()> { + task::spawn(apply_async( + self.notifier, + ... + )); + ... + Ok(()) + } + ... + + +pub async fn apply_async( + notifier: notify::Notifier, + ... +) -> io::Result<()> { + ... +``` + +``` +error[E0507]: cannot move out of `self.notifier` which is behind a shared reference + --> src/apply.rs:72:13 + | +72 | self.notifier, + | ^^^^^^^^^^^^^ move occurs because `self.notifier` has type `Notifier`, which does not implement the `Copy` trait +``` + +After discussing with [Barbara][], Alan adds `Arc` to provide a shared ownership between async tasks: + +```rust +use async_std::{sync::Arc, task}; + +pub struct Applier { + notifier: Arc, + ... +} + +impl Applier { + pub fn new(req_timeout: Duration) -> Self { + ... + Self { + ... + notifier: Arc::new(notify::Notifier::new()), + ... + } + } + ... + + pub async fn start(&self) -> io::Result<()> { + task::spawn(apply_async( + self.notifier.clone(), + ... + )); + ... + Ok(()) + } + ... + + +pub async fn apply_async( + notifier: Arc, + ... +) -> io::Result<()> { + ... +``` + +Alan is satisfied with the compilation success for the moment, but doesn't feel confident about the production readiness of Rust async. + +### Implementing HTTP server handler + +Familiar with Go standard libraries, Alan implemented the following request handler without any third-party dependencies: + +```go +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +type Handler interface { + start() +} + +type handler struct { + listenerPort uint64 + applier Applier +} + +func (hd *handler) start() { + hd.applier.start() + + serverMux := http.NewServeMux() + serverMux.HandleFunc("/echo", hd.wrapFunc(handleRequest)) + + httpServer := &http.Server{ + Addr: fmt.Sprintf(":%d", hd.listenerPort), + Handler: serverMux, + } + + tch := make(chan os.Signal, 1) + signal.Notify(tch, syscall.SIGINT) + done := make(chan struct{}) + go func() { + httpServer.Close() + close(done) + }() + + if err := httpServer.ListenAndServe(); err != nil { + fmt.Printf("http server error: %v\n", err) + } + select { + case <-done: + default: + } + + if err := hd.applier.stop(); err != nil { + panic(err) + } +} + +func (hd *handler) wrapFunc(fn func(applier Applier, w http.ResponseWriter, req *http.Request)) func(w http.ResponseWriter, req *http.Request) { + return func(w http.ResponseWriter, req *http.Request) { + fn(hd.applier, w, req) + } +} + +func handleRequest(applier Applier, w http.ResponseWriter, req *http.Request) { + switch req.Method { + case "POST": + var echoRequest EchoRequest + err := json.NewDecoder(req.Body).Decode(&echoRequest) + if err != nil { + fmt.Fprintf(w, "failed to read request %v", err) + return + } + s, err := applier.apply(Request{echoRequest: &echoRequest}) + if err != nil { + fmt.Fprintf(w, "failed to apply request %v", err) + return + } + fmt.Fprint(w, s) + + default: + http.Error(w, "Method Not Allowed", 405) + } +} +``` + +For Rust, Alan has multiple options to build a web server: [hyper](https://github.com/hyperium/hyper), [actix-web](https://github.com/actix/actix-web), [warp](https://github.com/seanmonstar/warp), [rocket](https://github.com/SergioBenitez/Rocket), [tide](https://github.com/http-rs/tide), etc.. + +Alan strongly believes in Go's minimal dependency approach, and thereby chooses "hyper" for its low-level API. While "hyper" is meant to be a low-level building block, implementing a simple request handler in "hyper" still requires four different external dependencies. Alan is not surprised anymore, and rather accepts the status quo of split Rust ecosystem: + +```bash +cargo add http +cargo add futures +cargo add hyper --features full +cargo add tokio --features full +``` + +After multiple days, Alan finally writes the following code: + +```rust +use async_std::sync::Arc; +use futures::TryStreamExt; +use http::{Method, Request, Response, StatusCode, Version}; +use hyper::server::conn::AddrStream; +use hyper::service::{make_service_fn, service_fn}; +use hyper::{Body, Server}; +use tokio::signal; + +pub struct Handler { + listener_port: u16, + applier: Arc, +} + +impl Handler { + ... + pub async fn start(&self) -> Result<(), Box> { + println!("starting server"); + match self.applier.start().await { + Ok(_) => println!("started applier"), + Err(e) => panic!("failed to stop applier {}", e), + } + + let addr = ([0, 0, 0, 0], self.listener_port).into(); + let svc = make_service_fn(|socket: &AddrStream| { + let remote_addr = socket.remote_addr(); + let applier = self.applier.clone(); + async move { + Ok::<_, Infallible>(service_fn(move |req: Request| { + handle_request(remote_addr, req, applier.clone()) + })) + } + }); + + let server = Server::bind(&addr) + .serve(svc) + .with_graceful_shutdown(handle_sigint()); + + if let Err(e) = server.await { + println!("server error: {}", e); + } + + match self.applier.stop().await { + Ok(_) => println!("stopped applier"), + Err(e) => println!("failed to stop applier {}", e), + } + + Ok(()) + } +} + +async fn handle_request( + addr: SocketAddr, + req: Request, + applier: Arc, +) -> Result, hyper::Error> { + let http_version = req.version(); + let method = req.method().clone(); + let cloned_uri = req.uri().clone(); + let path = cloned_uri.path(); + + let resp = match http_version { + Version::HTTP_11 => { + match method { + Method::POST => { + let mut resp = Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR)... + match req + .into_body() + .try_fold(Vec::new(), |mut data, chunk| async move { + data.extend_from_slice(&chunk); + Ok(data) + }) + .await + { + Ok(body) => { + let mut success = false; + let mut req = apply::Request::new(); + match path { + "/echo" => match echo::parse_request(&body) { + Ok(bb) => { + req.echo_request = Some(bb); + success = true; + } + Err(e) => { + resp = Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR)... + } + }, + _ => { + println!("unknown path {}", path); + resp = Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR)... + } + } + if success { + match applier.apply(req).await { + Ok(rs) => resp = Response::new(Body::from(rs)), + Err(e) => { + resp = Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR)... + } + } + } + } + Err(e) => ... + } + resp + } + + _ => Response::builder() + .status(StatusCode::NOT_FOUND)... + } + } + + _ => Response::builder() + .status(StatusCode::HTTP_VERSION_NOT_SUPPORTED)... + }; + Ok(resp) +} +``` + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +Alan's trust in Go mainly comes from its consistent and coherent approach to the problems. Alan prefers a standard way of doing things and as a result, multiple libraries available for async Rust caused Alan confusion. For instance, [etcd](https://github.com/etcd-io/etcd) relies on Go's standard HTTP libraries for HTTP/1 and [grpc-go](https://github.com/grpc/grpc-go) for HTTP/2 which is used by many other Go projects. The core networking library [`golang.org/x/net`](https://github.com/golang/net) is actively maintained by Go team with common interests from the community. + +The existing Rust syntax becomes more unwieldy and complicated to use for async Rust code. To make things worse, the lack of coherence in Rust async ecosystem can easily undermine basic user trust in a significant way. + +### **What are the sources for this story?** + +- Years of experience building a distributed key-value store in Go, [etcd](https://github.com/etcd-io/etcd). +- Simplified etcd server implementation in Go and Rust can be found at [gyuho/task-scheduler-examples](https://github.com/gyuho/task-scheduler-examples). + +### **Why did you choose Alan to tell this story?** + +I chose Alan because he is used to Go, where these issues play out differently. Go natively supports: (1) asynchronous task with "goroutine", (2) asynchronous communication with "channel", and (3) performant HTTP server library. Each component is nicely composed together. There is no need to worry about picking the right external dependencies or resolving dependency conflicts. Concurrency being treated as first-class by Go maintainers built great confidence in Alan's decision to use Go. + +### **How would this story have played out differently for the other characters?** + +This story would likely have played out the same for almost everyone new to Rust (except [Barbara][]). + +[distridata]: ../../projects/DistriData.md +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/alan_creates_a_hanging_alarm.md b/src/vision/submitted_stories/status_quo/alan_creates_a_hanging_alarm.md new file mode 100644 index 00000000..0ef2012e --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_creates_a_hanging_alarm.md @@ -0,0 +1,146 @@ +# 😱 Status quo stories: Alan Creates a Hanging Alarm + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +[Alan] is a developer on the Bottlerocket project. +[Bottlerocket] is a Linux-based open-source operating system that is purpose-built by Amazon Web Services for running containers. +Alan created a rust program, [pubsys], to ensure that Bottlerocket update repositories are healthy. +A _repository verification alarm_ uses pubsys to check the validity of Bottlerocket update repositories and notifies the team if any issues are found. + +### Multiple Tokio Runtimes + +Bottlerocket uses its own [tough] library to read and write TUF repositories. +This library was created before async became widespread and [reqwest] changed its main interface to `async`. +When reqwest switched to async, Alan used the `reqwest::blocking` feature instead of re-writing tough to be an async interface. +(Maybe Alan [should](https://github.com/awslabs/tough/issues/213) make tough an async interface, but he hasn't yet.) +In order to provide a non-async interface, `reqwest::blocking` creates a tokio runtime so that it can await futures. + +In pubsys Alan created some parallel downloading logic while using the above libraries: +Without realizing the danger, he created a tokio runtime in pubsys and used futures/await to do this parallelization, like this: + +```rust +for target in targets { + // use pubsys, which uses reqwest::blocking to get a response body reader + let mut reader = pubsys_repo.read_target(&target).unwrap(); + + // spawn a task in our own tokio runtime that conflicts with reqwest::blocking's runtime + tasks.push(tokio::spawn(async move { + io::copy(&mut reader, &mut io::sink()).context(error::TargetDownload { + target: target.to_string(), + }) + })); +} +``` + +Surprisingly, in retrospect, this worked... until it didn't. + +Recently Alan discovered that his repository verification alarm was hanging. +Alan discovered this by turning on trace level debugging and noticing that tokio was in an endless loop. +Alan remembered previous development efforts when multiple tokio runtimes caused a panic, but he had never seen a hang for this reason. +Still, he suspected multiple runtimes might be in play and audited to code. +The root cause _was_, in fact, having multiple tokio runtimes, though Alan don't know what change exposed the issue. +(Maybe it was a `cargo update`?) + +The fix was to eliminate the need for a tokio runtime in the pubsys code path by doing the parallel downloads in a different way +(first with [threads] for a quick fix, then with a [thread pool]). + +[Alan] is surprised and sad since he thought the compiler would help him write safe code. +Instead the compiler was ignorant of his misuse of the de-facto standard Rust async runtime. + +### Addendum: Multiple Tokio Major Versions + +Alan is also sad that the cargo package manager doesn't understand the de-facto standard runtime's versioning requirements. + +Alan had trouble updating to tokio v1 because: +- Having two major versions of the tokio runtime can/will cause problems. +- Cargo does not understand this and allows multiple major versions of tokio. + +Ultimately Alan's strategy for this in Bottlerocket is to ensure that only one version of tokio exists in the Cargo.lock. +This requirement delayed his ability to upgrade to tokio v1 and caused him to use a beta version of actix-web since all depenencies need to agree on tokio v1. + +### Not Easy to Block-On + +When Alan is writing a procedural program, and it is perfectly fine to block, then encountering an async function is problematic. + +```rust +fn my_blocking_program() { + blocking_function_1(); + blocking_function_2(); + + // uh oh, now what? + async_function_1().await +} +``` + +Uh oh. +Now Alan needs to decide what third-party runtime to use. +Should he create that runtime around main, or should I create it and clean it up around this one function call? +Put differently, should he bubble up async throughout the program even though the program is blocking and procedural (non-async) by nature? + +If he uses tokio, and gets it wrong (foot-guns described above), his program may hang or panic at runtime. + +In this scenario, Alan would consider this a nicer experience: + +```rust +fn my_blocking_program() { + blocking_function_1(); + blocking_function_2(); + + std::thread::block_on({ + async_function_1() + }) +} +``` + + + +[Bottlerocket]: https://github.com/bottlerocket-os/bottlerocket +[pubsys]: https://github.com/bottlerocket-os/bottlerocket/tree/develop/tools/pubsys +[tough]: https://github.com/awslabs/tough/ +[reqwest]: https://github.com/seanmonstar/reqwest +[threads]: https://github.com/bottlerocket-os/bottlerocket/pull/1521/files#diff-7546c95d0732614af12f62ff8c072f8c1061f82945c714daf1dd2962c42921ffL47 +[thread pool]: https://github.com/bottlerocket-os/bottlerocket/pull/1564/files + +## 🤔 Frequently Asked Questions + +*Here are some standard FAQ to get you started. Feel free to add more!* + +### **What are the morals of the story?** + +When you use a Rust async runtime, which is unavoidable these days, you *really* need to know what you're doing. + +Although the first two of the following points are about tokio, they are really about Rust async since tokio serves as the de-facto `std::runtime` for Rust. + +- It is confusing and dangerous that multiple tokio runtimes can panic or hang at program runtime. +- It is challenging that using multiple major versions of tokio (which is allowed by cargo) can fail at runtime. +- It is unfortunate that we need a 3rd party runtime in order to `block_on` a future, even if we are not trying to write async code. + +### **What are the sources for this story?** + +See the links embedded in the story itself (mostly at the top). + +### **Why did you choose *Bottlerocket* to tell this story?** + +Bottlerocket is a real-life project that experienced these real-life challenges! +[Alan] is representative of several programmers on the project that have experience with batteries-included languages like Go and Java. + +### **How would this story have played out differently for the other characters?** + +- [Barbara] would not have made this mistake given her experience. +- [Grace] could have made the same mistake since this issue is very specific to the Rust ecosystem. +- [Niklaus] could have easily made this mistake and might also have had a hard time understanding anything about the runtime or what went wrong. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/status_quo/alan_finds_database_drops_hard.md b/src/vision/submitted_stories/status_quo/alan_finds_database_drops_hard.md similarity index 84% rename from src/vision/status_quo/alan_finds_database_drops_hard.md rename to src/vision/submitted_stories/status_quo/alan_finds_database_drops_hard.md index ea7b4593..411b2c2d 100644 --- a/src/vision/status_quo/alan_finds_database_drops_hard.md +++ b/src/vision/submitted_stories/status_quo/alan_finds_database_drops_hard.md @@ -11,7 +11,7 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee Alan has been adding an extension to YouBuy that launches a singleton actor which interacts with a Sqlite database using the `sqlx` crate. The Sqlite database only permits a single active connection at a time, but this is not a problem, because the actor is a singleton, and so there only should be one at a time. He consults the documentation for `sqlx` and comes up with the following code to create a connection and do the query he needs: -```rust,ignore +```rust use sqlx::Connection; #[async_std::main] @@ -31,7 +31,7 @@ async fn main() -> Result<(), sqlx::Error> { } ``` -Things seem to be working fairly well but sometimes when he refreshes the page he encounters a panic with the message "Cannot open a new connecton: connection is already open". He is flummoxed. +Things seem to be working fairly well but sometimes when he refreshes the page he encounters a panic with the message "Cannot open a new connection: connection is already open". He is flummoxed. ## Searching for the Solution @@ -41,7 +41,7 @@ Alan tries to figure out what happened from the logs, but the only information h He's a bit confused, because he's accustomed to having things generally be cleaned up automatically when they get dropped (for example, dropping a [`File`](https://doc.rust-lang.org/std/fs/struct.File.html) will close it). Searching the docs, he sees the [`close`](https://docs.rs/sqlx/0.5.1/sqlx/trait.Connection.html#tymethod.close) method, but the comments confirm that he shouldn't have to call it explicitly: "This method is not required for safe and consistent operation. However, it is recommended to call it instead of letting a connection drop as the database backend will be faster at cleaning up resources." Still, just in case, he decides to add a call to `close` into his code. It does seem to help some, but he is still able to reproduce the problem if he refreshes often enough. Feeling confused, he adds a log statement right before calling `close` to see if it is working: -```rust,ignore +```rust use sqlx::Connection; #[async_std::main] @@ -64,7 +64,7 @@ async fn do_the_thing() -> Result<(), sqlx::Error> { } ``` -He observes that in the cases where he has the problem the log statement never executes. He asks Barbara for help and she points him to [this gist](https://gist.github.com/Matthias247/ffc0f189742abf6aa41a226fe07398a8) that explains how `await` can be canceled and it will the destructors for things that are in scope. He reads the [source for the SqliteConnection destructor](https://github.com/launchbadge/sqlx/blob/0ed524d65c2a3ee2e2a6706910b85bf2bb72115f/sqlx-core/src/pool/connection.rs#L70-L74) and finds that destructor spawns a task to actually close the connection. +He observes that in the cases where he has the problem the log statement never executes. He asks Barbara for help and she points him to [this gist](https://gist.github.com/Matthias247/ffc0f189742abf6aa41a226fe07398a8) that explains how `await` can be canceled, and cancellation will invoke the destructors for things that are in scope. He reads the [source for the SqliteConnection destructor](https://github.com/launchbadge/sqlx/blob/0ed524d65c2a3ee2e2a6706910b85bf2bb72115f/sqlx-core/src/pool/connection.rs#L70-L74) and finds that destructor spawns a task to actually close the connection. He realizes there is a race condition and the task may not have actually closed the connection before `do_the_thing` is called a second time. At this point, he is feeling pretty frustrated! @@ -72,12 +72,10 @@ Next, Alan seeks verification and validation of his understanding of the source ## Finding the Solution -Alan briefly considers rearchitecting his application in more extreme ways to retain use of async, but he gives up and seeks a more straight forward solution. He discovers `rusqlite`, a sychronous database library and adopts it. This requires some rearchitecting but solves the problem. +Alan briefly considers rearchitecting his application in more extreme ways to retain use of async, but he gives up and seeks a more straight forward solution. He discovers `rusqlite`, a synchronous database library and adopts it. This requires some rearchitecting but solves the problem. ## 🤔 Frequently Asked Questions -*Here are some standard FAQ to get you started. Feel free to add more!* - ### **What are the morals of the story?** * Rust's async story is lacking a way of executing async operations in destructors. Spawning is a workaround, but it can have unexpected side-effects. * The story demonstrates solid research steps that Alan uses to understand and resolve his problem. @@ -92,11 +90,11 @@ His experience and understanding of other languages coupled with his desire to a ### **How would this story have played out differently for the other characters?** This story would likely have played out the same for everyone. -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-t +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-t diff --git a/src/vision/submitted_stories/status_quo/alan_has_an_event_loop.md b/src/vision/submitted_stories/status_quo/alan_has_an_event_loop.md new file mode 100644 index 00000000..dd828e43 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_has_an_event_loop.md @@ -0,0 +1,104 @@ +# 😱 Status quo stories: Alan has an external event loop and wants to use futures/streams + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +As a first Rust Project, Alan decides to program his own IRC Client. + +Since it is Alan's first Project in Rust, it is going to be a private one. He is going to use it on is Mac, so he decides to go with the cocoa crate to not have to learn any Framework specific quirks. This way Alan can get a feel of Rust itself. + +### Alans hopes and dreams +Despite a learning curve, he managed to creating a first window and have some buttons and menus works. After the initialisation is done, the App hand over control to [CFRunLoop::Run](https://developer.apple.com/documentation/corefoundation/1542011-cfrunlooprun?language=occ). + +Once Alan is happy with his Mock UI, he wants to make it actually do something. Reading about async Rust, he sees that several of the concepts there map pretty well to some core Cocoa concepts: +* Promises => Futures +* Observables => Streams. + +Alan smiles, thinking he knows what and more importantly how to do this. + +### First time dealing with runtimes + +Unfortunately, coming from frameworks like Angular or Node.js, Alan is not used to being responsible for driving the processing of Futures/Streams. + +After reading up about Runtimes, his mental image of a runtime is something like: + +```rust +impl Runtime { + fn run() { + while !self.tasks.is_empty() { + while let Some(task) = self.awoken_tasks.pop() { + task.poll(); + //... remove finished task from 'tasks' + } + } + } +} +``` + +Coming from Single-Threaded Angular development, Alan decides to limit his new App to Single-Threaded. He does not feel like learning about Send/Sync/Mutex as well as struggling with the borrow checker. + +On top of that, his App is not doing any heavy calculation so he feels async should be enough to not block the main thread too bad and have a hanging UI. + +### Fun time is over + +Soon Alan realises that he cannot use any of those runtimes because they all take control of the thread and block. The same as the OS Event loop. + +Alan spends quite some time to look through several runtime implementations. Ignoring most internal things, all he wants is a runtime that looks a bit like this: + +```rust +impl Runtime { + fn make_progress() { + while let Some(task) = self.awoken_tasks.pop() { + task.poll(); + //... remove finished task from 'tasks' + } + } + fn run() { + while !self.tasks.is_empty() { + self.make_progress(); + } + } +} +``` + +It could be so easy. Unfortunately he does not find any such solution. Having already looked through quite a bit of low level documentation and runtime code, Alan thinks about implementing his own runtime... + +...but only for a very short time. Soon after looking into it, he finds out that he has to deal with ```RawWakerVTable```, ```RawWaker```, ```Pointers```. Worst of all, he has to do that without the safety net of the rust compiler, because this stuff is ```unsafe```. + +Reimplementing the OS Event Loop is also not an option he wants to take. See [here](https://developer.apple.com/documentation/appkit/nsapplication) +>Override run() if you want the app to manage the main event loop differently than it does by default. (This a critical and complex task, however, that you should only attempt with good reason). + + +### The cheap way out + +Alan gives up and uses a runtime in a separate thread from the UI. This means he has to deal with the additional burden of syncing and he has to give up the frictionless use of some of the patterns he is accustomed to by treating UI events as ```Stream```. + +## 🤔 Frequently Asked Questions + + +* **What are the morals of the story?** + * Even though you come from a language that has async support, does not mean you are used to selecting und driving a runtime. + * It should be possible to integrate runtimes into existing Event loops. +* **What are the sources for this story?** + * The authors own experience working on a GUI Framework (very early stage) + * Blog post: [Integrating Qt events into Actix and Rust](https://www.rubdos.be/corona/qt/rust/tokio/actix/2020/05/23/actix-qt.html) +* **Why did you choose Alan to tell this story?** + * The story deals about UI event loops, but the other characters could run into similar issues when trying to combine event loops from different systems/frameworks. +* **Is this Apple specific?** + * No! You have the same issue with other OSs/Frameworks that don't already support Rust Async. +* **How would this story have played out differently for the other characters?** + * Since this is a technical and not a skill or experience issue, this would play out similar for other Characters. Although someone with deep knowledge of those Event loops, like Grace, might be more willing to re-implement them. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/status_quo/alan_hates_writing_a_stream.md b/src/vision/submitted_stories/status_quo/alan_hates_writing_a_stream.md similarity index 96% rename from src/vision/status_quo/alan_hates_writing_a_stream.md rename to src/vision/submitted_stories/status_quo/alan_hates_writing_a_stream.md index 1ddea07e..b09043d5 100644 --- a/src/vision/status_quo/alan_hates_writing_a_stream.md +++ b/src/vision/submitted_stories/status_quo/alan_hates_writing_a_stream.md @@ -14,7 +14,7 @@ After a couple weeks learning Rust basics, Alan quickly understands `async` and Eventually, Alan realizes that some responses have enormous bodies, and would like to stream them instead of buffering them fully in memory. He's *used* the `Stream` trait before. Using it was very natural, and followed a similar pattern to regular `async`/`await`: -```rust,ignore +```rust while let Some(chunk) = body.next().await? { file.write_all(&chunk).await?; } @@ -26,7 +26,7 @@ However, _implementing_ `Stream` turns out to be rather different. With a quick Alan first hoped he could simply write signing stream imperatively, reusing his new knowledge of `async` and `await`, and assuming it'd be similar to JavaScript: -```rust,ignore +```rust async* fn sign(file: ReaderStream) -> Result, Error> { let mut sig = Signature::new(); @@ -73,7 +73,7 @@ Implementing a `Stream` means writing async code in a way that doesn't _feel_ li Unsure of what the final code will look like, he starts with: -```rust,ignore +```rust struct SigningFile; impl Stream for SigningFile { @@ -101,7 +101,7 @@ With `Pin` hopefully ignored, Alan next notices that in the imperative style he He thinks about his stream's states, and settles on the following structure: -```rust,ignore +```rust struct SigningFile { state: State, file: ReaderStream, @@ -118,7 +118,7 @@ enum State { It turns out it was more complicated than Alan thought (the author made this same mistake). The `digest` method of `Signature` is `async`, _and_ it consumes the signature, so the state machine needs to be adjusted. The signature needs to be able to be moved out, and it needs to be able to store a future from an `async fn`. Trying to figure out how to represent that in the type system was difficult. He considered adding a generic `T: Future` to the `State` enum, but then wasn't sure what to set that generic to. Then, he tries just writing `Signing(impl Future)` as a state variant, but that triggers a compiler error that `impl Trait` isn't allowed outside of function return types. Patient [Barbara] helped again, so that Alan learns to just store a `Pin>`, wondering if the `Pin` there is important. -```rust,ignore +```rust struct SigningFile { state: State, } @@ -132,7 +132,7 @@ enum State { Now he tries to write the `poll_next` method, checking readiness of individual steps (thankfully, Alan remembers `ready!` from the futures 0.1 blog posts he read) and proceeding to the next state, while grumbling away the weird `Pin` noise: -```rust,ignore +```rust match self.state { State::File(ref mut file, ref mut sig) => { match ready!(Pin::new(file).poll_next(cx)) { @@ -166,7 +166,7 @@ Oh well, at least it _works_, right? So far, Alan hasn't paid too much attention to `Context` and `Poll`. It's been fine to simply pass them along untouched. There's a confusing bug in his state machine. Let's look more closely: -```rust,ignore +```rust // zooming in! match ready!(Pin::new(file).poll_next(cx)) { Some(result) => { @@ -188,7 +188,7 @@ The compiler doesn't help at all, and he re-reads his code multiple times, but b All too often, since we don't want to duplicate code in multiple branches, the solution for Alan is to add an odd `loop` around the whole thing, so that the next match branch uses the `Context`: -```rust,ignore +```rust loop { match self.state { State::File(ref mut file, ref mut sig) => { @@ -239,9 +239,9 @@ Choosing Alan was somewhat arbitrary, but this does get to reuse the experience * This likely would have been a similar story for any character. * It's possible [Grace][] would be more used to writing state machines, coming from C. -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/alan_iteratively_regresses.md b/src/vision/submitted_stories/status_quo/alan_iteratively_regresses.md new file mode 100644 index 00000000..4addc833 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_iteratively_regresses.md @@ -0,0 +1,111 @@ +# 😱 Status quo stories: Alan iteratively regresses performance + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +A core part of DistriData, called DDSplit, is in charge of splitting input data records into fragments that are stored on distinct servers, and then reassembling those fragments back into records in response to user queries. + +DDSplit was originally implemented using Java code (plus some C, interfaced via JNI). Alan thinks that Rust could provide the same quality of service while requiring less memory. He decides to try reimplementing DDSplit in Rust, atop tokio. + +Alan wants to copy some of the abstractions he sees in the Java code that are defined via Java interfaces. Alan sees Rust traits as the closest thing to Java interfaces. However, when he experimentally defines a trait with an `async fn`, he gets the following message from the compiler: + +``` +error[E0706]: functions in traits cannot be declared `async` + --> src/main.rs:2:5 + | +2 | async fn method() { } + | -----^^^^^^^^^^^^^^^^ + | | + | `async` because of this + | + = note: `async` trait functions are not currently supported + = note: consider using the `async-trait` crate: https://crates.io/crates/async-trait +``` + +This diagnostic leads Alan to add the [async-trait crate][] as a dependency to his project. Alan then uses the `#[async_trait]` attribute provided by that crate to be able to define `async fn` methods within traits. + +When Alan finishes the prototype code, he finds the prototype performance has 20% slower throughput compared to the Java version. + +[async-trait crate]: https://crates.io/crates/async-trait +[async-trait transform]: https://crates.io/crates/async-trait#explanation + +Alan is disappointed; his experience has been that Rust code performs great, (at least once you managed to get the code to be accepted by the compiler). Alan was not expecting to suffer a 20% performance hit over the Java code. + +The DDSplit service is being developed on a Linux machine, so Alan is able use the `perf` tool to gather sampling-based profiling data the async/await port of DDSplit. + +Looking at a [flamegraph][] for the call stacks, Alan identified two sources of execution time overhead that he did not expect: calls into the memory allocator (`malloc`) with about 1% of the execution time, and calls to move values in memory (`memcpy`), with about 8% of execution time. + +[flamegraph]: https://crates.io/crates/flamegraph + +Alan reaches out to Barbara, as the local Rust expert, for help on how identify where the performance pitfalls are coming from. + +Alan asks Barbara whether the problem could be caused by the tokio executor. Barbara says it is hard to know that without more instrumentation. She explains it *could* be that the program is overloading tokio's task scheduler (for example), but it also could be that the application code itself has expensive operations, such as lots of small I/O operations rather than using a buffer. + +Alan and Barbara look at the `perf` data. They find the output of `perf report` difficult to navigate and interpret. The data has stack trace fragments available, which gives them a few hints to follow up on. But when they try to make `perf report` annotate the original source, `perf` only shows disassembled machine code, not the original Rust source code. Alan and Barbara both agree that trying to dissect the problem from the machine code is not an attractive strategy. + +Alan asks Barbara what she thinks about the `malloc` calls in the profile. Barbara recommends that Alan try to eliminate the allocation calls, and if they cannot be eliminated, then that Alan try tuning the parameters for the global memory allocator, or even switching which global memory allocator he is using. Alan looks at Barbara in despair: his time tweaking GC settings on the Java Virtual Machine taught him that allocator tuning is often a black art. + +Barbara suggests that they investigate where the calls to `memcpy` are arising, since they look like a larger source of overhead based on the profile data. From the call stacks in `perf report`, Alan and Barbara decide to skim over the source code files for the corresponding functions. + +Upon seeing `#[async_trait]` in Alan's source code, Barbara recommends that if performance is a concern, then Alan should avoid `#[async_trait]`. She explains that `#[async_trait]` [transforms][async-trait transform] a trait's async methods into methods that return `Pin>`, and the overhead that injects that will be hard to diagnose and impossible to remove. When Alan asks what other options he could adopt, Barbara thinks for a moment, and says he could make an enum that carries all the different implementations of the code. Alan says he'll consider it, but in the meantime he wants to see how far they can improve the code while keeping `#[async_trait]`. + +They continue looking at the code itself, essentially guessing at potential sources of where problematic `memcpy`'s may be arising. They identify two potential sources of moves of large datatypes in the code: pushes and pops on vectors of type `Vec`, and functions with return types of the form `Result`. + +Barbara asks how large the `DistriQuery`, `SuccessCode`, and `DistriErr` types are. Alan immediately notes that `DistriQuery` may be large, and they discuss options for avoiding the memory traffic incurred by pushing and popping `DistriQuery`. + +For the other two types, Alan responds that the `SuccessCode` is small, and that the error variants are never constructed in his benchmark code. Barbara explains that the size of `Result` has to be large enough to hold either variant, and that `memcpy`'ing a result is going to move all of those bytes. Alan investigates and sees that `DistriErr` has variants that embed byte arrays that go up to 50kb in size. Barbara recommends that Alan look into boxing the variants, or the whole `DistriErr` type itself, in order to reduce the cost of moving it around. + +Alan uses Barbara's feedback to box some of the data, and this cuts the `memcpy` traffic in the `perf report` to one quarter of what it had been reporting previously. + +However, there remains a significant performance delta between the Java version and the Rust version. Alan is not sure his Rust-rewrite attempt is going to get anywhere beyond the prototype stage. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +1. Rust promises great performance, but when performance is not meeting one's targets, it is hard to know what to do next. Rust mostly leans on leveraging existing tools for native code development, but those tools are (a.) foreign to many of our developers, (b.) do not always measure up to what our developers have access to elsewhere, (c.) do not integrate as well with Rust as they might with C or C++. + +2. Lack of certain language features leads developers to use constructs like `#[async_trait]` which add performance overhead that is (a.) hard to understand and (b.) may be significant. + +3. Rust makes some things very explicit, e.g. the distinction between `Box` versus `T` is quite prominent. But Rust's expressive type system also makes it easy to compose types without realizing how large they have gotten. + +4. Programmers do not always have a good mental model for where expensive moves are coming from. + +5. An important specific instance of (1c.) for the async vision: Native code tools do not have any insight into Rust's async model, as that is even more distant from the execution model of C and C++. + +6. We can actually generalize (5.) further: When async performance does not match expectations, developers do not have much insight into whether the performance pitfalls arise from issues deep in the async executor that they have selected, or if the problems come directly from overheads built into the code they themselves have written. + +### **What are the sources for this story?** + +Discussions with engineers at Amazon Web Services. + +### **Why did you choose Alan to tell this story?** + +I chose Alan because he is used to Java, where these issues play out differently. + +Java has very mature tooling, including for performance investigations. Alan has used [JProfiler](https://www.ej-technologies.com/products/jprofiler/overview.html) at his work, and [VisualVM](https://visualvm.github.io/) for personal hobby projects. Alan is frustrated by his attempts to use (or even identify) equivalent tools for Rust. + +With respect to memory traffic: In Java, every object is handled via a reference, and those references are cheap to copy. (One pays for that convenience in other ways, of course.) + + +### **How would this story have played out differently for the other characters?** + +From her C and C++ background, [Grace][] probably would avoid letting her types get so large. But then again, C and C++ do not have enums with a payload, so Grace would likely have fallen in the same trap that Alan did (of assuming that the cost of moving an enum value is proportional to its current variant, rather than to its type's overall size). Also, Grace might report that her experience with gcc-based projects yielded programs that worked better with `perf`, due in part to gcc producing higher quality DWARF debuginfo. + +[Barbara][] probably would have added direct instrumentation via the `tracing` crate, potentially even to tokio itself, rather than spend much time wrestling with `perf`. + +[Niklaus][] is unlikely to be as concerned about the 20% throughput hit; he probably would have been happy to get code that seems functionally equivalent to the original Java version. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/status_quo/alan_lost_the_world.md b/src/vision/submitted_stories/status_quo/alan_lost_the_world.md similarity index 94% rename from src/vision/status_quo/alan_lost_the_world.md rename to src/vision/submitted_stories/status_quo/alan_lost_the_world.md index 373e4b3d..ea75ce3f 100644 --- a/src/vision/status_quo/alan_lost_the_world.md +++ b/src/vision/submitted_stories/status_quo/alan_lost_the_world.md @@ -15,7 +15,7 @@ Alan heard about a project to reimplement a deprecated browser plugin using Rust 3. `await` the `Future` in an `async` function 4. Do whatever they want with the resulting data -```rust,ignore +```rust use web_sys::{Request, window}; fn make_request(src: &url) -> Request { @@ -34,7 +34,7 @@ Alan adds calls to `load_image` where appropriate. They realize that nothing is At this point, Alan wants to put the downloaded image onto the screen, which in this project means putting it into a `Node` of the current `World`. A `World` is a bundle of global state that's passed around as things are loaded, rendered, and scripts are executed. It looks like this: -```rust,ignore +```rust /// All of the player's global state. pub struct World<'a> { @@ -50,7 +50,7 @@ pub struct World<'a> { In synchronous code, this was perfectly fine. Alan figures it'll be fine in async code, too. So Alan adds the world as a function parameter and everything else needed to parse an image and add it to our list of nodes: -```rust,ignore +```rust async fn load_image(src: String, inside_of: usize, world: &mut World<'_>) { let request = make_request(&url); let data = window().unwrap().fetch_with_request(&request).await.unwrap().etc.etc.etc; @@ -64,7 +64,7 @@ async fn load_image(src: String, inside_of: usize, world: &mut World<'_>) { } ``` -Bang! Suddently, the project stops compiling, giving errors like... +Bang! Suddenly, the project stops compiling, giving errors like... ```ignore error[E0597]: `world` does not live long enough @@ -73,7 +73,7 @@ error[E0597]: `world` does not live long enough Hmm, okay, that's kind of odd. We can pass a `World` to a regular function just fine - why do we have a problem here? Alan glances over at `loader.rs`... -```rust,ignore +```rust fn attach_image_from_net(world: &mut World<'_>, args: &[Value]) -> Result { let this = args.get(0).coerce_to_object()?; let url = args.get(1).coerce_to_string()?; @@ -84,9 +84,9 @@ fn attach_image_from_net(world: &mut World<'_>, args: &[Value]) -> Result(future: F) where F: Future + 'static @@ -96,7 +96,7 @@ So, `spawn_local` only works with futures that return nothing - so far, so good Barbara explains that when you borrow a value in a closure, the closure doesn't gain the lifetime of that borrow. Instead, the borrow comes with it's own lifetime, separate from the closure's. The only time a closure can have a non-`'static` lifetime is if one or more of its borrows is *not* provided by it's caller, like so: -```rust,ignore +```rust fn benchmark_sort() -> usize { let mut num_times_called = 0; let test_values = vec![1,3,5,31,2,-13,10,16]; @@ -114,7 +114,7 @@ The closure passed to `sort_by` has to copy or borrow anything not passed into i Async functions, it turns out, *act like closures that don't take parameters*! They *have to*, because all `Future`s have to implement the same trait method `poll`: -```rust,ignore +```rust pub trait Future { type Output; @@ -124,9 +124,9 @@ pub trait Future { When you call an async function, all of it's parameters are copied or borrowed into the `Future` that it returns. Since we need to borrow the `World`, the `Future` has the lifetime of `&'a mut World`, not of `'static`. -Barbara suggests changing all of the async function's parameters to be owned types. Alan asks Grace, who architected this project. Grace recommends holding a reference to the `Plugin` that owns the `World`, and then borrowing it whenver you need the `World`. That ultimately looks like the following: +Barbara suggests changing all of the async function's parameters to be owned types. Alan asks Grace, who architected this project. Grace recommends holding a reference to the `Plugin` that owns the `World`, and then borrowing it whenever you need the `World`. That ultimately looks like the following: -```rust,ignore +```rust async fn load_image(src: String, inside_of: usize, player: Arc>) { let request = make_request(&url); let data = window().unwrap().fetch_with_request(&request).await.unwrap().etc.etc.etc; @@ -166,14 +166,14 @@ It works, well enough that Alan is able to finish his changes and PR them into t * Grace would have felt constrained by the `async` syntax preventing some kind of workaround for this problem. * Barbara already knew about Futures and 'static and carefully organizes their programs accordingly. -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade [RuffleAsync]: https://github.com/ruffle-rs/ruffle/blob/master/core/src/loader.rs [WasmFetch]: https://docs.rs/web-sys/0.3.50/web_sys/struct.Window.html#method.fetch_with_request [WasmJsFuture]: https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/struct.JsFuture.html diff --git a/src/vision/submitted_stories/status_quo/alan_misses_c_sharp_async.md b/src/vision/submitted_stories/status_quo/alan_misses_c_sharp_async.md new file mode 100644 index 00000000..fcc08d71 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_misses_c_sharp_async.md @@ -0,0 +1,414 @@ +# 😱 Status quo stories: Alan misses C# async + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async +Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR +making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories +[cannot be wrong], only inaccurate). Alternatively, you may wish to +[add your own status quo story][htvsq]! + +## The story + +### First attempt + +[Alan] has finally gotten comfortable working in rust and finally decides to try writing async code. +He's used C#'s async and mostly loved the experience, so he decides to try writing it the same way: + +```rust +async fn run_async() { + println!("Hello async!"); +} + +fn main() { + run_async(); +} +``` + +But the compiler didn't like this: +``` +warning: unused implementer of `Future` that must be used + --> src/main.rs:6:5 + | +6 | run_async(); + | ^^^^^^^^^^^^ + | + = note: `#[warn(unused_must_use)]` on by default + = note: futures do nothing unless you `.await` or poll them +``` + +Alan has no idea what `Future` is; he's never seen this before and it's not in his code. He sees the +note in the warning and adds `.await` to the line in `main`: +```rust +fn main() { + run_async().await; +} +``` + +The compiler does't like this either. +``` +error[E0728]: `await` is only allowed inside `async` functions and blocks + --> src/main.rs:6:5 + | +5 | fn main() { + | ---- this is not `async` +6 | run_async().await; + | ^^^^^^^^^^^^^^^^^ only allowed inside `async` functions and blocks +``` + +... so Alan adds `async` to `main`: +```rust +async fn main() { + run_async().await; +} +``` + +which prompts yet another error from the compiler: +``` +error[E0277]: `main` has invalid return type `impl Future` + --> src/main.rs:5:17 + | +5 | async fn main() { + | ^ `main` can only return types that implement `Termination` + | + = help: consider using `()`, or a `Result` + +error[E0752]: `main` function is not allowed to be `async` + --> src/main.rs:5:1 + | +5 | async fn main() { + | ^^^^^^^^^^^^^^^ `main` function is not allowed to be `async` +``` + +So Alan decides to do a lot of research online and hunting around on StackOverflow. He learns that +`async fn` returns a value, but it's not the same as the value returned from async functions in C#. +In C#, the object he gets back can only be used to query the result of an already running thread of +work. The rust one doesn't seem to do anything until you call `.await` on it. Alan thinks this is +really nice because he now has more control over when the processing starts. You seem to get the same +control as constructing a `Task` [manually] in C#, but with a lot less effort. + +[manually]: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-5.0#task-instantiation + +He also ends up finding out a little about executors. `tokio` seems to be really popular, so he +incorporates that into his project: + +```rust +async fn run_async() { + println!("Hello async!"); +} + +#[tokio::main] +async fn main() { + run_async().await; +} +``` + +And it works! +``` +Hello async! +``` + +### Attempting concurrency + +Alan decides to try running two async functions concurrently. "This is pretty easy in C#," he +thinks, "This can't be too hard in rust." + +In C# Alan would usually write something like: +```csharp +async Task expensive1() { + ... +} + +async Task expensive2() { + ... +} + +public static async Main() { + Task task = expensive1(); + await expensive2(); + task.Wait(); +} +``` + +If the code was more dynamic, Alan could have also used the Task API to simplify the await: +```csharp +public static Main() { + List tasks = new List(); + tasks.push(expensive1()); + tasks.push(expensive2()); + try { + Task.WaitAll(tasks.ToArray()); + } + // Ignore exceptions here. + catch (AggregateException) {} +} +``` + +So Alan tries the first approach in rust: + +```rust +use std::sync::mpsc::{self, Sender, Receiver}; + +async fn expensive1(tx: Sender<()>, rx: Receiver<()>) { + println!("Doing expensive work in 1"); + tx.send(()).ok(); + let _ = rx.recv(); + println!("Got result, finishing processing in 1"); + println!("1 done"); +} + +async fn expensive2(tx: Sender<()>, rx: Receiver<()>) { + println!("Doing simple setup in 2"); + let _ = rx.recv(); + println!("Got signal from 1, doing expensive processing in 2"); + tx.send(()).ok(); + println!("2 done"); +} + +#[tokio::main] +async fn main() { + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + expensive1(tx1, rx2).await; + expensive2(tx2, rx1).await; +} +``` + +But this just hangs after printing: +``` +Doing expensive work in 1 +``` + +Alan wonders if this means he can't run code concurrently... he does some research and learns about +`join`, which doesn't seem to be part of the std. This seems like the second example in C#, but Alan +is surprised it doesn't come with the standard library. He has to import `futures` as a dependency +and tries again: +```rust +use futures::join; +... + +#[tokio::main] +async fn main() { + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let fut1 = expensive1(tx1, rx2); + let fut2 = expensive2(tx2, rx1); + join!(fut1, fut2); +} +``` + +But this still hangs the same way as the first attempt. After more research, Alan learns that he +can't use the standard `mpsc::channel` in async contexts. He needs to use the ones in the external +`futures` crate. This requires quite a few changes since the API's don't line up with the one's in +std: +* `rx` has to be `mut` +* there's bounded and unbounded mpsc channels, Alan went with unbounded since the API seemed simpler +for now +* you need to import the `StreamExt` trait to be able to get a value out of `rx`, this took a lot of +research to get right. + +```rust +use futures::{ + join, + channel::mpsc::{self, UnoundedSender, UnboundedReceiver}, + StreamExt, +}; +use std::sync::mpsc::{self, Sender, Receiver}; + +async fn expensive1(tx: Sender<()>, mut rx: Receiver<()>) { + println!("Doing expensive work in 1"); + tx.unbounded_send(()).ok(); + let _ = rx.next().await; + println!("Got result, finishing processing in 1"); + println!("1 done"); +} + +async fn expensive2(tx: Sender<()>, mut rx: Receiver<()>) { + println!("Doing simple setup in 2"); + let _ = rx.next().await; + println!("Got signal from 1, doing expensive processing in 2"); + tx.unbounded_send(()).ok(); + println!("2 done"); +} + +#[tokio::main] +async fn main() { + let (tx1, rx1) = mpsc::channel(); + let (tx2, rx2) = mpsc::channel(); + let fut1 = expensive1(tx1, rx2); + let fut2 = expensive2(tx2, rx1); + join!(fut1, fut2); +} +``` + +And now it works! +``` +Doing expensive work in 1 +Doing simple setup in 2 +Got signal from 1, doing expensive processing in 2 +2 done +Got result, finishing processing in 1 +1 done +``` + +While this is more similar to using the `Task.WaitAll` from C#, there were a lot more changes needed +than Alan expected. + +### Cancelling tasks + +Another pattern Alan had to use frequently in C# was accounting for cancellation of tasks. Users in +GUI applications might not want to wait for some long running operation or in a web server some +remote calls might time out. C# has a really nice API surrounding [`CancellationTokens`]. + +[`CancellationTokens`]: https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=net-5.0 + +They can be used in a fashion similar to (overly simplified example): +```csharp +async Task ExpensiveWork(CancellationToken token) { + while (not_done) { + // Do expensive operations... + if (token.IsCancellationRequested) { + // Cleanup... + break; + } + } +} + +public static async Main() { + // Create the cancellation source and grab its token. + CancellationTokenSource source = new CancellationTokenSource(); + CancellationToken token = source.Token; + + // Setup a handler so that on user input the expensive work will be canceled. + SetupInputHandler(() => { + // on user cancel + source.Cancel(); + }); + + // Pass the token to the functions that should be stopped when requested. + await ExpensiveWork(token); +} +``` + +Alan does some research. He searches for "rust async cancellation" and can't find anything similar. +He reads that "dropping a future is cancelling it". In his junior dev days, Alan might have run with +that idea and moved on to the next task, but experienced Alan knows something is wrong here. If he +drops a `Future` how does he control the cleanup? Which `await` point is the one that will not be +processed? This scares Alan since he realized he could get some really nasty bugs if this happens +in production. In order to work around this, Alan needs to make sure *every* future around critical +code is carefully reviewed for drops in the wrong places. Alan also decided he needs to come up with +some custom code to handle cancelling. + +Alan decides to ask around, and gets suggestions for searching with "rust cancel future" or +"rust cancel async". He finds out about tokio's [`tokio_util::sync::CancellationToken`], and also +the [`stop-token`] and [`stopper`] crates. He decides to try working with the version in +`tokio_util` since he's already using `tokio`. Looking at the docs for each, they all seem to +behave how Alan expected, though he couldn't use `stop-token` since that only works with +`async-std`. `stopper` also seems like a good alternative, but he decides to go with the type that +is built by the tokio team. + +Reading the docs it seems that the tokio `CancellationToken` acts more like a combination of C#'s +`CancellationTokenSource` and `CancellationToken`. He needs to pass the tokens generated from a call +to `child_token()` and keep the main token for triggering cancellation. One advantage that all of +the token crates seem to have is that they can also integrate directly with streams and futures, +or be polled directly (as a stream or boolean). + +[`tokio_util::sync::CancellationToken`]: https://docs.rs/tokio-util/0.6.7/tokio_util/sync/struct.CancellationToken.html +[`stop-token`]: https://docs.rs/stop-token/0.2.0/stop_token/ +[`stopper`]: https://docs.rs/stopper/0.2.0/stopper/ + +```rust +use tokio_util::sync::CancellationToken; +use futures::StreamExt; +// ... + +fn generate_work() -> impl Stream { + // ... +} + +async fn expensive_work(token: CancellationToken) { + let mut work_stream = generate_work(); + loop { + if let Some(op) = work_stream.next().await { + op.work().await; + } else { + break; + } + + if token.is_cancelled() { + break; + } + } +} + +#[tokio::main] +async fn main() { + let token = CancellationToken::new(); + let child_token = token.child_token(); + setup_input_handler(move || { + token.cancel(); + }); + + expensive_work(child_token).await; +} +``` + +This seems relatively straightforward! + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** +* First Attempt + * Unused implementer warnings for `Futures` are less clear than they are for, e.g. `Result`. + * It's not as easy to jump into experimenting with async as compared to synchronous code. It + requires a lot more front-end research on the user's end. + * Developers might need to unlearn async behavior from other languages in order to understand + async rust. + * Dynamic languages with async provide async main, but rust does not. We could be more helpful + by explaining this in compiler errors. +* Attempting Concurrency + * Trying to use items from std is the obvious thing to try, but wrong because they are blocking. + * The corresponding async versions of the std items don't exist in std, but are in `futures` + crate. So it's hard to actually develop in async without the `futures` crates. +* Cancelling Tasks + * It's not obvious that futures could only run part-way. + * Async types and crates can be bound to certain ecosystems, limiting developers' ability to + reuse existing code. + +### **What are the sources for this story?** +* The docs for [`oneshot::Canceled`] mentions that dropping a `Sender` will cancel the future. +Someone inexperienced might accidentally apply this to a broader scope of types. +* [This IRLO post] has a nice discussion on cancellation, where the [linked gist] +is a thorough overview of problems surrounding cancelation in async rust, with comparisons to other +languages. + +[`oneshot::Canceled`]: https://docs.rs/futures/0.3.15/futures/channel/oneshot/struct.Canceled.html +[This IRLO post]: https://internals.rust-lang.org/t/async-await-the-challenges-besides-syntax-cancellation/10287 +[linked gist]: https://gist.github.com/Matthias247/ffc0f189742abf6aa41a226fe07398a8 + +### **Why did you choose Alan to tell this story?** +C# is a garbage collected language that has had async for a long time. Alan best fit the model for +a developer coming from such a language. + +### **How would this story have played out differently for the other characters?** +* [Barbara] may already be used to the ideosynchracies of async in rust. She may not realize how +difficult it could be for someone who has a very different model of async engrained into them. +* [Grace] has likely never used async utilities similar to the ones in C# and other GC languages. C +and C++ tend to use callbacks to manage async workflows. She may have been following the C++ +proposals for coroutines (e.g. `co_await`, `co_yield`, `co_return`), but similar to rust, the +utilities are not yet thoroughly built out in those spaces. She may be familiar with cancelation in +external libraries like [`cppcoro`](https://github.com/lewissbaker/cppcoro#Cancellation), or async in +general with [`continuable`](https://github.com/Naios/continuable) +* [Niklaus] may not have had enough experience to be wary of some of the pitfalls encountered here. +He might have introduced bugs around dropping futures (to cancel) without realizing it. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../../how_to_vision/status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/alan_needs_async_in_traits.md b/src/vision/submitted_stories/status_quo/alan_needs_async_in_traits.md new file mode 100644 index 00000000..4add4a3f --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_needs_async_in_traits.md @@ -0,0 +1,110 @@ +# 😱 Status quo stories: Alan needs async in traits + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +Alan is working on a project with Barbara which has already gotten off to a [somewhat rocky start](./barbara_anguishes_over_http.md). He is working on abstracting away the HTTP implementation the library uses so that users can provide their own. He wants the user to implement an async trait called `HttpClient` which has one method `perform(request: Request) -> Response`. Alan tries to create the async trait: + +```rust +trait HttpClient { + async fn perform(request: Request) -> Response; +} +``` + +When Alan tries to compile this, he gets an error: + +``` + --> src/lib.rs:2:5 + | +2 | async fn perform(request: Request) -> Response; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | `async` because of this + | + = note: `async` trait functions are not currently supported + = note: consider using the `async-trait` crate: https://crates.io/crates/async-trait +``` + +Alan, who has been using Rust for a little while now, has learned to follow compiler error messages and adds `async-trait` to his `Cargo.toml`. Alan follows the README of `async-trait` and comes up with the following code: + +```rust +#[async_trait] +trait HttpClient { + async fn perform(request: Request) -> Response; +} +``` + +Alan's code now compiles, but he also finds that his compile times have gone from under a second to around 6s, at least for a clean build. + +After Alan finishes adding the new trait, he shows his work off to Barbara and mentions he's happy with the work but is a little sad that compile times have worsened. Barbara, an experienced Rust developer, knows that using `async-trait` comes with some additional issues. In this particular case she is especially worried about tying their public API to a third-party dependency. Even though it is technically possible to implement traits annotated with `async_trait` without using `async_trait`, doing so in practice is very painful. For example `async_trait`: + +* handles lifetimes for you if the returned future is tied to the lifetime of some inputs. +* boxes and pins the futures for you. + +which the implementer will have to manually handle if they don't use `async_trait`. She decides to not worry Alan with this right now. Alan and Barbara are pretty happy with the results and go on to publish their crate which gets lots of users. + +Later on, a potential user of the library wants to use their library in a `no_std` context where they will be providing a custom HTTP stack. Alan and Barbara have done a pretty good job of limiting the use of standard library features and think it might be possible to support this use case. However, they quickly run into a show stopper: `async-trait` boxes all of the futures returned from a async trait function. They report this to Alan through an issue. + +Alan, feeling (over-) confident in his Rust skills, decides to try to see if he can implement async traits without using `async-trait`. + +```rust +trait HttpClient { + type Response: Future; + + fn perform(request: Request) -> Self::Response; +} +``` + +Alan seems to have something working, but when he goes to update the examples of how to implement this trait in his crate's documentation, he realizes that he either needs to: + +* use trait object: + + ```rust + struct ClientImpl; + + impl HttpClient for ClientImpl { + type Response = Pin>>; + + fn perform(request: Request) -> Self::Response { + Box::pin(async move { + // Some async work here creating Reponse + }) + } + } + ``` + + which wouldn't work for `no_std`. + +* implement `Future` trait manually, which isn't particularly easy/straight-forward for non-trivial cases, especially if it involves making other async calls (likely). + +After a lot of thinking and discussion, Alan and Barbara accept that they won't be able to support `no_std` users of their library and add mention of this in crate documentation. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +* `async-trait` is awesome, but has some drawbacks + * compile time increases + * performance cost of boxing and dynamic dispatch + * not a standard solution so when this comes to language, it might break things +* Trying to have a more efficient implementation than `async-trait` is likely not possible. + +### **What are the sources for this story?** + +* [Zeeshan](https://github.com/zeenix/) is looking for a way to implement async version of the [service-side zbus API](https://docs.rs/zbus/1.9.1/zbus/trait.Interface.html). +* [Ryan](https://github.com/rylev) had to use `async-trait` in an internal project. + +### **Why did you choose Alan to tell this story?** + +We could have used Barbara here but she'd probably know some of the work-arounds (likely even the details on why they're needed) and wouldn't need help so it wouldn't make for a good story. Having said that, Barbara is involved in the story still so it's not a pure Alan story. + +### **How would this story have played out differently for the other characters?** + +* Barbara: See above. +* Grace: Probably won't know the solution to these issues much like Alan, but might have an easier time understanding the **why** of the whole situation. +* Niklaus: would be lost - traits are somewhat new themselves. This is just more complexity, and Niklaus might not even know where to go for help (outside of compiler errors). diff --git a/src/vision/submitted_stories/status_quo/alan_picks_web_server.md b/src/vision/submitted_stories/status_quo/alan_picks_web_server.md new file mode 100644 index 00000000..6c3eaa6b --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_picks_web_server.md @@ -0,0 +1,71 @@ +# 😱 Status quo stories: Alan wants to migrate a web server to Rust + + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +## The story + +### Is Rust ready for the web? + +Alan has been following the [arewewebyet site](https://www.arewewebyet.org/) for quite some time. He is a Typescript full-stack developer and follows the project in order to know when it would be sensible to migrate the backend of a web application he's responsible for. Alan loves Rust and has used it for some tasks that didn't quite need async routines. Since `arewewebyet` is [an official Rust language project](https://github.com/rust-lang/arewewebyet), he trusts their reviews of several web frameworks, tools, libraries, etc. + +Alan was thrilled during the 2020 Xmas holiday. It turns out that at that time [Rust was declared to be web ready](https://github.com/rust-lang/arewewebyet/pull/309)! Alan takes this is a sign that not only is Rust great for web servers, but also a confirmation that async features have matured and stabilised. For, how can a language be web ready and not fully support asynchronous tasks? + +Alan's point of reference are the Golang and Javascript languages. They were both created for web servers and clients. They also support async/await natively. At the same time, Alan is not aware of the complexities that these languages are "hiding" from him. + + +### Picking a web server is ok +Golang native http server is nice but, as a Typescript developer, Alan is also used to dealing with "Javascript fatigue". Javascript developers often use this term to refer to a fast-pace framework ecosystem, where every so often there is the "new" thing everybody else is migrating to. Similarly, Javascript engineers are used to having to pick from a myriad of options within the vast npm ecosystem. And so, the lack of a web sever in Rust's standard library didn't surprise him. The amount of options didn't overwhelm him either. + +The [arewewebyet site](https://www.arewewebyet.org/) mentions four good web servers. Alan picks Tide because the interfaces and the emphasis on middleware reminds him of Nodejs' Express framework. + + +### The first endpoint +Alan sets up all the boilerplate and is ready to write the first endpoint. He picks `PUT /support-ticket` because it barely has any logic in it. When a request arrives, the handler only makes a request to Zendesk to create a support ticket. The handler is stateless and has no middleware. + +The [arewewebyet site](https://www.arewewebyet.org/) doesn't recommend a specific http client, so Alan searches for one in crates.io. He picks reqwest simply because it's the most popular. + +Alan combines the knowledge he has from programming in synchronous Rust and asynchronous Javascript to come up with a few lines that should work. If the compiler is happy, then so is he! + +### First problem: incompatible runtimes + +The first problem he runs into is very similar to the one described in [the compiler trust story](alan_started_trusting_the_rust_compiler_but_then_async.md#fractured-futures-fractured-trust): `thread 'main' panicked at 'there is no reactor running, must be called from the context of a Tokio 1.x runtime`. + +In short, Alan has problems because Tide is based on `std-async` and reqwest on the latest version of `tokio`. This is a real pain for Alan as he has now to change either the http client or the server so that they use the same runtime. + +He decides to switch to Actix web. + +### Second problem: incompatible versions of the same runtime + +Alan migrates to Actix web and again the compiler seems to be happy. To his surprise, the same problem happens again. The program panics with the message as before: `there is no reactor running, must be called from the context of a Tokio 1.x runtime`. He is utterly puzzled as Actix web is based on Tokio just like reqwest. Didn't he just fix problem number 1? + +It turns out that the issue is that Alan's using v0.11.2 of reqwest, which uses tokio v1, and v3.3.2 of actix-web, which uses tokio v0.3. + +The solution to this problem is then to dig into all the versions of `reqwest` until he finds one which uses the same version of tokio. + +### Can Alan sell the Rust migration to his boss? + +This experience has made Alan think twice about whether Rust is indeed web ready. On the one hand, there are very good libraries for web servers, ORMs, parsers, session management, etc. On the other, Alan is fearful that in 2/3/6 months time he has to develop new features with libraries that already exist but turn out to be incompatible with the runtime chosen at the beginning of the project. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** +* Rust's ecosystem has a lot of great components that may individually be ready for the web, but combining them is still a fraught proposition. In a typical web server project, dependencies that use async features form an intricate web which is hard to decipher for both new and seasoned Rust developers. Alan picked Tide and reqwest, only to realise later that they are not compatible. How many more situations like this will he face? Can Alan be confident that it won't happen again? New users especially are not accustomed to having to think about what "runtime" they are using, since there is usually not a choice in the matter. +* The situation is so complex that it's not enough knowing that all dependencies use the same runtime. They all have to actually be compatible with the same runtime **and** version. Newer versions of reqwest are incompatible with the latest stable version of actix web (verified at the time of writing) +* Developers that need a stable environment may be fearful of the complexity that comes with managing async dependencies in Rust. For example, if reqwest had a security or bug fix in one of the latest versions that's not backported to older ones, Alan would not be able to upgrade because actix web is holding him back. He has in fact to wait until **ALL** dependencies are using the same runtime to apply fixes and upgrades. + +### **What are the sources for this story?** +Personal experience of the author. + +### **Why did you choose Alan to tell this story?** +As a web developer in GC languages, Alan writes async code every day. A language without stable async features is not an option. + +### **How would this story have played out differently for the other characters?** +Learning what async means and what it entails in a codebase is usually hard enough. Niklaus would struggle to learn all that while at the same time dealing with the many gotchas that can happen when building a project with a lot of dependencies. + +Barbara may be more tolerant with the setup since she probably knows the rationale behind keeping Rust's standard library lean and the need for external async runtimes. + +### **How would this story have played out differently if Alan came from another GC'd language?** +Like the trust story, it would be very close, since all other languages (that I know of) provide async runtimes out of the box and it's not something the programmer needs to concern themselves with. diff --git a/src/vision/status_quo/alan_runs_into_stack_trouble.md b/src/vision/submitted_stories/status_quo/alan_runs_into_stack_trouble.md similarity index 97% rename from src/vision/status_quo/alan_runs_into_stack_trouble.md rename to src/vision/submitted_stories/status_quo/alan_runs_into_stack_trouble.md index 77808438..af0dc058 100644 --- a/src/vision/status_quo/alan_runs_into_stack_trouble.md +++ b/src/vision/submitted_stories/status_quo/alan_runs_into_stack_trouble.md @@ -1,9 +1,9 @@ # 😱 Status quo stories: Alan runs into stack allocation trouble -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md ## 🚧 Warning: Draft status 🚧 diff --git a/src/vision/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md b/src/vision/submitted_stories/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md similarity index 93% rename from src/vision/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md rename to src/vision/submitted_stories/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md index d6b1cd09..da637428 100644 --- a/src/vision/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md +++ b/src/vision/submitted_stories/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md @@ -12,7 +12,7 @@ He has dealt with his fair share of race conditions/thread safety issues during he won't have those annoying runtime problems to deal with. This allows him to try to squeeze his programs for as much performance as he wants, because the compiler will stop him when he tries things that could result in runtime problems. -After seeing the perfomance and the lack of runtime problems, he starts to trust the compiler more and more with each project finished. +After seeing the performance and the lack of runtime problems, he starts to trust the compiler more and more with each project finished. He knows what he can do with external libraries, he does not need to fear concurrency issues if the library cannot be used from multiple threads, because the compiler would tell him. @@ -20,8 +20,8 @@ His trust in the compiler solidifies further the more he codes in Rust. ### The first async project Alan now starts with his first async project. He sees that there is no async in the standard library, but after googling for "rust async file open", he finds 'async_std', a crate that provides some async versions of the standard library functions. -He has some code written that asynchrously interacts with some files: -```rust,ignore +He has some code written that asynchronously interacts with some files: +```rust use async_std::fs::File; use async_std::prelude::*; @@ -33,7 +33,7 @@ fn main() -> Result<(), Box> { ``` But now the compiler complains that `await` is only allowed in `async` functions. He now notices that all the examples use `#[async_std::main]` as an attribute on the `main` function in order to be able to turn it into an `async main`, so he does the same to get his code compiling: -```rust,ignore +```rust use async_std::fs::File; use async_std::prelude::*; @@ -55,7 +55,7 @@ The project is working like a charm. The project Alan is building is starting to grow, and he decides to add a new feature that needs to make some API calls. He starts using `reqwest` in order to help him achieve this task. After a lot of refactoring to make the compiler accept the program again, Alan is satisfied that his refactoring is done. His program now boils down to: -```rust,ignore +```rust use async_std::fs::File; use async_std::prelude::*; @@ -91,7 +91,7 @@ Do you recall in Spider-Man, that after getting bitten by the radioactive spider In his work, Alan sees an async call to a C# wrapper around SQLite, his equivalent of a spider-sense (async-sense?) starts tingling. Now knowing from Rust the complexities that arise when trying to create asynchronicity, what kind of complex mechanisms are at play here to enable these async calls from C# that end up in the C/C++ of SQLite? -He quickly discovers that there are no complex mechanism at all! It's actually just a synchronous call all the way down, with just some exta overhead from wrapping it into an asynchronous function. There are no points where the async function will yield. He transforms all these asynchronous calls to their synchronous counterparts, and sees a slight improvement in performance. Alan is happy, product management is happy, customers are happy! +He quickly discovers that there are no complex mechanism at all! It's actually just a synchronous call all the way down, with just some extra overhead from wrapping it into an asynchronous function. There are no points where the async function will yield. He transforms all these asynchronous calls to their synchronous counterparts, and sees a slight improvement in performance. Alan is happy, product management is happy, customers are happy! Over the next few months, he often takes a few seconds to reflect about why certain parts of the code are async, if they should be, or how other parts of the code might benefit from being async and if it's possible to make them async. He also uses what he learned from async Rust in his C# code reviews to find similar problems or general issues (With great power...). He even spots some lifetime bugs w.r.t. asynchronous code in C#, imagine that. diff --git a/src/vision/submitted_stories/status_quo/alan_thinks_he_needs_async_locks.md b/src/vision/submitted_stories/status_quo/alan_thinks_he_needs_async_locks.md new file mode 100644 index 00000000..5c46a5b2 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_thinks_he_needs_async_locks.md @@ -0,0 +1,128 @@ +# 😱 Status quo stories: Alan thinks he needs async locks + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +One of Alan's first Rust related tasks in his job at YouBuy is writing an HTTP based service. This service is a simple internal proxy router that inspects an incoming HTTP request and picks the downstream service to call based on certain aspects of the HTTP request. + +Alan decides that he'll simply use some shared state that request handlers can read from in order to decide how to proxy the request. + +Alan, having read the Rust book and successfully completed the challenge in the [last chapters](https://doc.rust-lang.org/book/ch20-02-multithreaded.html), knows that shared state can be achieved in Rust with reference counting (using `std::sync::Arc`) and locks (using `std::sync::Mutex`). Alan starts by throwing his shared state (a `std::collections::HashMap`) into an `Arc>`. + +Alan, smitten with how quickly he can write Rust code, ends up with some code that compiles that looks roughly like this: + +```rust +#[derive(Clone)] +struct Proxy { + routes: Arc>, +} + +impl Proxy { + async fn handle(&self, key: String, request: Request) -> crate::Result { + let routes = self.state.lock().unwrap(); + let route = routes.get(key).unwrap_or_else(crate::error::MissingRoute)?; + Ok(self.client.perform_request(route, request).await?) + } +} +``` + +Alan is happy that his code seems to be compiling! The short but hard learning curve has been worth it. He's having fun now! + +Unfortunately, Alan's happiness soon comes to end as he starts integrating his request handler into calls to `tokio::spawn` which he knows will allow him to manage multiple requests at a time. The error message is somewhat cryptic, but Alan is confident he'll be able to figure it out: + +``` +189 | tokio::spawn(async { + | ^^^^^^^^^^^^ future created by async block is not `Send` +::: /home/alan/.cargo/registry/src/github.amrom.workers.dev-1ecc6299db9ec823/tokio-1.5.0/src/task/spawn.rs:129:21 + | +129 | T: Future + Send + 'static, + | ---- required by this bound in `tokio::spawn` + +note: future is not `Send` as this value is used across an await + --> src/handler.rs:787:9 + | +786 | let routes = self.state.lock().unwrap(); + | - has type `std::sync::MutexGuard<'_, HashMap>` which is not `Send` +787 | Ok(self.client.perform_request(route, request).await?) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `routes` maybe used later +788 | }) + | - `routes` is later dropped here +``` + +Alan stops and takes a deep breath. He tries his best to make sense of the error message. He sort of understands the issue the compiler is telling him. Apparently `routes` is not marked as `Send`, and because it is still alive over a call to `await`, it is making the future his handler returns not `Send`. And tokio's `spawn` function seems to require that the future it received be `Send`. + +Alan reaches the boundaries of his knowledge of Rust, so he reaches out over chat to ask his co-worker Barbara for help. Not wanting to bother her, Alan provides the context he's already figured out for himself. + +Barbara knows that mutex guards are not `Send` because sending mutex guards to different threads is not a good idea. She suggests looking into async locks which can be held across await points because they are `Send`. Alan looks into the tokio documentation for more info and is easily able to move the use of the standard library's mutex to tokio's mutex. It compiles! + +Alan ships his code and it gets a lot of usage. After a while, Alan notices some potential performance issues. It seems his proxy handler does not have the throughput he would expect. Barbara, having newly joined his team, sits down with him to take a look at potential issues. Barbara is immediately worried by the fact that the lock is being held much longer than it needs to be. The lock only needs to be held while accessing the route and not during the entire duration of the downstream request. + +She suggests to Alan to switch to not holding the lock across the I/O operations. Alan first tries to do this by explicitly cloning the url and dropping the lock before the proxy request is made: + +```rust +impl Proxy { + async fn handle(&self, key: String, request: Request) -> crate::Result { + let routes = self.state.lock().unwrap(); + let route = routes.get(key).unwrap_or_else(crate::error::MissingRoute)?.clone(); + drop(routes); + Ok(self.client.perform_request(route, request).await?) + } +} +``` + +This compiles fine and works in testing! After shipping to production, they notice a large increase in throughput. It seems their change made a big difference. Alan is really excited about Rust, and wants to write more! + +Alan continues his journey of learning even more about async Rust. After some enlightening talks at the latest RustConf, he decides to revisit the code that he and Barbara wrote together. He asks himself, is using an *async* lock the right thing to do? This lock should only be held for a very short amount of time. Yielding to the runtime is likely more expensive than just synchronously locking. But he remembers vaguely hearing that you should never use blocking code in async code as this will block the entire async executor from being able to make progress, so he doubts his intuition. + +After chatting with Barbara, who encourages him to benchmark and measure, he decides to switch back to synchronous locks. + +Unfortunately, switching back to synchronous locks brings back the old compiler error message about his future not being `Send`. Alan is confused as he's dropping the mutex guard before it ever crosses an await point. + +Confused Alan goes to Barbara for advice. She is also confused, and it takes several minutes of exploration before she comes to a solution that works: wrapping the mutex access in a block and implicitly dropping the mutex. + +```rust +impl Proxy { + async fn handle(&self, key: String, request: Request) -> crate::Result { + let route = { + let routes = self.state.lock().unwrap(); + routes.get(key).unwrap_or_else(crate::error::MissingRoute)?.clone() + }; + Ok(self.client.perform_request(route, request).await?) + } +} +``` + +Barbara mentions she's unsure why explicitly dropping the mutex guard did not work, but they're both happy that the code compiles. In fact it seems to have improved the performance of the service when its under extreme load. Alan's intuition was right! + +In the end, Barbara decides to write a blog post about how blocking in async code isn't always such a bad idea. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + * Locks can be quite common in async code as many tasks might need to mutate some shared state. + * Error messages can be fairly good, but they still require a decent understanding of Rust (e.g., `Send`, `MutexGuard`, drop semantics) to fully understand what's going on. + * This can lead to needing to use certain patterns (like dropping mutex guards early) in order to get code working. + * The advice to never block in async code is not always true: if blocking is short enough, is it even blocking at all? +### **What are the sources for this story?** + * Chats with [Alice](https://github.com/Darksonn) and [Lucio](https://github.com/LucioFranco). + * Alice's [blog post](https://ryhl.io/blog/async-what-is-blocking/) on the subject has some good insights. + * The issue of conservative analysis of whether values are used across await points causing futures to be `!Send` is [known](https://rust-lang.github.io/async-book/07_workarounds/03_send_approximation.html), but it takes some digging to find out about this issue. A tracking issue for this can be [found here](https://github.com/rust-lang/rust/issues/57478). +### **Why did you choose [Alan](../../characters/alan.md) to tell this story?** + * While Barbara might be tripped up on some of the subtleties, an experienced Rust developer can usually tell how to avoid some of the issues of using locks in async code. Alan on the other hand, might be surprised when his code does not compile as the issue the `Send` error is protecting against (i.e., a mutex guard being moved to another thread) is not protected against in other languages. +### **How would this story have played out differently for the other characters?** + * Grace would have likely had a similar time to Alan. These problems are not necessarily issues you would run into in other languages in the same way. + * Niklaus may have been completely lost. This stuff requires a decent understanding of Rust and of async computational systems. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/alan_tries_a_socket_sink.md b/src/vision/submitted_stories/status_quo/alan_tries_a_socket_sink.md new file mode 100644 index 00000000..d2383979 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_tries_a_socket_sink.md @@ -0,0 +1,138 @@ +# 😱 Status quo stories: Alan tries using a socket Sink + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +Alan is working on a project that uses `async-std`. He has worked a bit with `tokio` in the past and is more familiar with that, but he is interested to learn something how things work in `async-std`. + +One of the goals is to switch from a WebSocket implementation using raw TCP sockets to one managed behind an HTTP server library, so both HTTP and WebSocket RPC calls can be forwarded to a transport-agnostic RPC server. + +In this server implementation: + +* RPC call strings can be received over a WebSocket +* The strings are decoded and sent to an RPC router that calls the methods specified in the RPC call +* Some of the methods that are called can take some time to return a result, so they are spawned separately + * RPC has built-in properties to organize call IDs and methods, so results can be sent in any order +* Since WebSockets are bidirectional streams (duplex sockets), the response is sent back through the same client socket + +He finds the HTTP server `tide` and it seems fairly similar to `warp`, which he was using with `tokio`. He also finds the WebSocket middleware library `tide-websockets` that goes with it. + +However, as he's working, Alan encounters a situation where the socket needs to be written to within an async thread, and the traits just aren't working. He wants to split the stream into a sender and receiver: + +```rust +use futures::{SinkExt, StreamExt}; +use async_std::sync::{Arc, Mutex}; +use log::{debug, info, warn}; + +async fn rpc_ws_handler(ws_stream: WebSocketConnection) { + let (ws_sender, mut ws_receiver) = ws_stream.split(); + let ws_sender = Arc::new(Mutex::new(ws_sender)); + + while let Some(msg) = ws_receiver.next().await { + debug!("Received new WS RPC message: {:?}", msg); + + let ws_sender = ws_sender.clone(); + + async_std::task::spawn(async move { + let res = call_rpc(msg).await?; + + match ws_sender.lock().await.send_string(res).await { + Ok(_) => info!("New WS data sent."), + Err(_) => warn!("WS connection closed."), + }; + }); + } +} +``` + +The `split` method splits the `ws_stream` into two separate halves: + + * a producer (`ws_sender`) that implements a `Stream` with the messages arriving on the websocket; + * a consumer (`ws_receiver`) that implements `Sink`, which can be used to send responses. + + This way, one task can pull items from the `ws_sender` and spawn out subtasks. Those subtasks share access to the `ws_receiver` and send messages there when they're done. Unfortunately, Alan finds that he can't use this pattern here, as the `Sink` trait wasn't implemented in the WebSockets middleware library he's using. + +Alan also tries creating a sort of poller worker thread using an intermediary messaging channel, but he has trouble reasoning about the code and wasn't able to get it to compile: + +```rust +use async_std::channel; +use async_std::sync::{Arc, Mutex}; +use log::{debug, info, warn}; + +async fn rpc_ws_handler(ws_stream: WebSocketConnection) { + let (ws_sender, mut ws_receiver) = channel::unbounded::(); + let ws_receiver = Arc::new(ws_receiver); + + let ws_stream = Arc::new(Mutex::new(ws_stream)); + let poller_ws_stream = ws_stream.clone(); + + async_std::task::spawn(async move { + while let Some(msg) = ws_receiver.next().await { + match poller_ws_stream.lock().await.send_string(msg).await { + Ok(msg) => info!("New WS data sent. {:?}", msg), + Err(msg) => warn!("WS connection closed. {:?}", msg), + }; + } + }); + + while let Some(msg) = ws_stream.lock().await.next().await { + async_std::task::spawn(async move { + let res = call_rpc(msg).await?; + ws_sender.send(res); + }); + } +} +``` + +Alan wonders if he's thinking about it wrong, but the solution isn't as obvious as his earlier `Sink` approach. Looking around, he realizes a solution to his problems already exists-- as others have been in his shoes before-- within two other nearly-identical pull requests, but they were both closed by the project maintainers. He tries opening a third one with the same code, pointing to an example where it was actually found to be useful. To his joy, his original approach works with the code in the closed pull requests in his local copy! Alan's branch is able to compile for the first time. + +However, almost immediately, his request is closed [with a comment suggesting that he try to create an intermediate polling task instead](https://github.com/http-rs/tide-websockets/issues/15#issuecomment-797090892), much as he was trying before. Alan is feeling frustrated. "I already tried that approach," he thinks, "and it doesn't work!" + +As a result of his frustration, Alan calls out one developer of the project on social media. He knows this developer is opposed to the `Sink` traits. Alan's message is not well-received: the maintainer sends a short response and Alan feels dismissed. Alan later finds out he was blocked. A co-maintainer responds to the thread, defending and supporting the other maintainer's actions, and suggests that Alan "get over it". Alan is given a link to a blog post. The post provides a number of criticisms of `Sink` but, after reading it, Alan isn't sure what he should do instead. + +Because of this heated exchange, Alan grows concerned for his own career, what these well-known community members might think or say about his to others, and his confidence in the community surrounding this language that he really enjoys using is somewhat shaken. + +Despite this, Alan takes a walk, gathers his determination, and commits to maintaining his fork with the changes from the other pull requests that were shut down. He publishes his version to crates.io, vowing to be more welcoming to "misfit" pull requests like the one he needed. + +A few weeks later, Alan's work at his project at work is merged with his new forked crate. It's a big deal, his first professional open source contribution to a Rust project! Still, he doesn't feel like he has a sense of closure with the community. Meanwhile, his friends say they want to try Rust, but they're worried about its async execution issues, and he doesn't know what else to say, other than to offer a sense of understanding. Maybe the situation will get better someday, he hopes. + +## 🤔 Frequently Asked Questions + + +### **What are the morals of the story?** +* There are often many sources of opinion in the community regarding futures and async, but these opinions aren't always backed up with examples of how it should be better accomplished. Sometimes we just find a thing that works and would prefer to stick with it, but others argue that some traits make implementations unnecessarily complex, and choose to leave it out. Disagreements like these in the ecosystem can be harmful to the reputation of the project and the participants. +* If there's a source of substantial disagreement, the community becomes even further fragmented, and this may cause additional confusion in newcomers. +* Alan is used to fragmentation from the communities he comes from, so this isn't too discouraging, but what's difficult is that there's enough functionality overlap in async libraries that it's tempting to get them to interop with each other as-needed, and this can lead to architectural challenges resulting from a difference in design philosophies. +* It's also unclear if Futures are core to the Rust asynchronous experience, much as Promises are in JavaScript, or if the situation is actually more complex. +* The `Sink` trait is complex but it solves a real problem, and the workarounds required to solve problems without it can be unsatisfactory. +* Disagreement about core abstractions like `Sink` can make interoperability between runtimes more difficult; it also makes it harder for people to reproduce patterns they are used to from one runtime to another. +* It is all too easy for technical discussions like this to become heated; it's important for all participants to try and provide each other with the "benefit of the doubt". +### **What are the sources for this story?** +* + * - Third pull request + * - Suggestion to use a broadcast channel +* - Where some of the original polling work is replaced + * - File with Sink solution +* +* +* +* +* [[C++] Throwing Out The Kitchen Sink](https://thephd.github.io/output-ranges) +### **Why did you choose [Alan](../../characters/alan.md) to tell this story?** +* Alan is more representative of the original author's background in JS, TypeScript, and NodeJS. +### **How would this story have played out differently for the other characters?** +* (I'm not sure.) + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/alan_tries_processing_some_files.md b/src/vision/submitted_stories/status_quo/alan_tries_processing_some_files.md new file mode 100644 index 00000000..4dab34b5 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_tries_processing_some_files.md @@ -0,0 +1,290 @@ +# 😱 Status quo stories: Alan tries processing some files + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +Alan is new to Rust. He wants to build a program that recurses over all the files in a directory (and its subdirectories), reads each file, and produces some fingerprint of the file. + +Since so much blocking I/O is involved, he chooses async in order to process many files concurrently. + +### Async +Alan does some research into `async` Rust. New to the language, he's heard that `async` support has recently landed, so he starts by reading [the release notes](https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html) and much of the [Async Book](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html), bookmarking the dense parts about Pinning as something he'll come back to when it makes more sense. Notably, he skips over the [Recursion Workaround](https://rust-lang.github.io/async-book/07_workarounds/04_recursion.html) and other workaround bits. + +As someone who hasn't followed the evolution of `async` Rust closely, the [Ecosystem](https://rust-lang.github.io/async-book/08_ecosystem/00_chapter.html) page of the Async Book provides a critical bit of context that he wishes he'd found first. Coming from Python and Go, where `asyncio` and goroutines are fully supported by the core language, Alan had been unclear exactly what _was_ and what _wasn't_ included in the language. This page puts everything into place. + +The [Popular Runtimes](https://rust-lang.github.io/async-book/08_ecosystem/00_chapter.html#popular-async-runtimes) section makes it clear that he'll need to choose a third party ecosystem. He chooses Tokio because: +- It's the only ecosystem of those listed that he's already heard about. +- It seems to be widely used based on some web searches. +- It has bite-sized, approachable [tutorial pages](https://tokio.rs/tokio/tutorial) that provide higher-level introduction than the average `rustdoc`. +- It provides rich RPC libraries, like Tonic, which he plans to fiddle with in a future project. + +### Recursion +Alan starts by writing a recursive function that can call some operation on each regular file in a directory and recurse on each subdirectory. +```rust +async fn process_directory<'a, F, P, T>(path: PathBuf, processor: &'a P) -> Vec +where + P: Fn(DirEntry) -> F, + F: Future, +{ + ReadDirStream::new(read_dir(path).await.unwrap()) + .filter_map(|x| async { + let dir_entry = x.unwrap(); + let ft = dir_entry.file_type().await.unwrap(); + if ft.is_file() { + Some(vec![processor(dir_entry)]) + } else if ft.is_dir() { + Some(process_directory(dir_entry.path(), processor).await) + } else { + None + } + }) + .collect::>>() + .await + .into_iter() + .flatten() + .collect() +} +``` + +The first paper cut comes when the compiler complains: +``` +error[E0733]: recursion in an `async fn` requires boxing + --> src/main.rs:23:77 + | +23 | async fn process_directory<'a, F, P, T>(path: PathBuf, processor: &'a P) -> Vec + | ^^^^^^ recursive `async fn` + | + = note: a recursive `async fn` must be rewritten to return a boxed `dyn Future` + +... +For more information about an error, try `rustc --explain E0733`. +``` + +From the explainer, Alan learns that he cannot use the `async` sugaring, and needs to use a Boxed Pin in his function signature: +``` +fn process_directory<'a, F, P, T>( + path: PathBuf, + processor: &'static P, +) -> Pin>>> +``` + +New to Rust, Alan still doesn't really understand what `Pin` does, so he reads [the docs](https://doc.rust-lang.org/std/pin/index.html), sees that it marks which objects are *"guaranteed not to move"*, and wonders why the compiler couldn't determine this automatically since he read so much about how the borrow checker can already detect _moves_ versus borrows. + +He's also not entirely sure why the returned `Future` needs to be `Boxed`. The suggested explainer helps a bit: +``` +The `Box<...>` ensures that the result is of known size, and the pin is +required to keep it in the same place in memory. +``` +But Alan figures that the size of `Future` should be determined by the type `T`. It's not like he's implementing a custom struct that is `Future`; he's returning a `Vec` inside the standard `async move {}`. Alan wishes there was a way to express "*Hey I'm returning a Future created by `async move`, whose `Output` attribute has a known size, so the resulting Future should have a known size too!*" + +But Alan does what the compiler tells him to do and adds some extra stuff to his function, which now looks like: + +```rust +fn process_directory<'a, F, P, T>( + path: PathBuf, + processor: &'static P, +) -> Pin> + 'a>> +where + P: Fn(DirEntry) -> F, + F: Future, +{ + Box::pin(async move { + ReadDirStream::new(read_dir(path).await.unwrap()) + .filter_map(|x| async { + let dir_entry = x.unwrap(); + let ft = dir_entry.file_type().await.unwrap(); + if ft.is_file() { + Some(vec![processor(dir_entry)]) + } else if ft.is_dir() { + Some(process_directory(dir_entry.path(), processor).await) + } else { + None + } + }) + .collect::>>() + .await + .into_iter() + .flatten() + .collect() + }) +} +``` + +### Rate Limiting +Alan knows that `process_directory` may be called on directories with many thousands of files or subdirectories, and is wary of exhausting file descriptor limits. Since he can't find much documentation about how to keep the number of async tasks in check - Tokio's docs suggest we can [spawn millions of tasks](https://tokio.rs/tokio/tutorial/spawning), but don't offer advice on how to manage tasks with expensive side effects - he decides he needs to build a simple rate limiter. + +Alan's rate limiter will wrap some `Future`, acquire a semaphore, and then await the Future, returning the same type `T`: +```rust +async fn rate_limit(fut: F, sem: &Semaphore) -> T +where + F: Future, +{ + let _permit = sem.acquire().await; + fut.await +} +``` + +Since the `async fn foo() -> T` syntax desugars to `fn foo() -> Future`, and since `fut.await` returns `T`, Alan assumes that the above is equivalent to: +```rust +fn rate_limit(fut: F, sem: &Semaphore) -> F +where + F: Future, +{ + ... +} +``` + +So he plugs this new `rate_limit` logic into `process_directory`: + +```rust +use futures::future::join_all; +use futures::stream::StreamExt; +use futures::Future; +use std::path::PathBuf; +use std::pin::Pin; +use tokio::fs::{read_dir, DirEntry}; +use tokio::sync::Semaphore; +use tokio_stream::wrappers::ReadDirStream; + +async fn rate_limit(fut: F, sem: &Semaphore) -> T +where + F: Future, +{ + let _permit = sem.acquire().await; + fut.await +} + +fn process_directory<'a, F, P, T>( + path: PathBuf, + processor: &'a P, + sem: &'static Semaphore, +) -> Pin> + 'a>> +where + P: Fn(DirEntry) -> F, + F: Future, +{ + Box::pin(async move { + ReadDirStream::new(read_dir(path).await.unwrap()) + .filter_map(|x| async { + let dir_entry = x.unwrap(); + let ft = dir_entry.file_type().await.unwrap(); + if ft.is_file() { + Some(vec![rate_limit(processor(dir_entry), sem)]) + } else if ft.is_dir() { + Some(process_directory(dir_entry.path(), processor, sem).await) + } else { + None + } + }) + .collect::>>() + .await + .into_iter() + .flatten() + .collect() + }) +} + +async fn expensive(de: DirEntry) -> usize { + // assume this function spawns a task that does heavy I/O on the file + de.file_name().len() +} + +#[tokio::main(flavor = "current_thread")] +async fn main() { + let sem = Semaphore::new(10); + let path = PathBuf::from("/tmp/foo"); + let results = join_all(process_directory(path, &expensive, &sem).await); + dbg!(results.await); +} +``` + +And is met with a new complaint from the compiler: +``` +error[E0308]: `if` and `else` have incompatible types + --> src/main.rs:34:24 + | +18 | fn process_directory<'a, F, P, T>( + | - this type parameter +... +32 | / if ft.is_file() { +33 | | Some(vec![rate_limit(processor(dir_entry), sem)]) + | | ------------------------------------------------- expected because of this +34 | | } else if ft.is_dir() { + | |________________________^ +35 | || Some(process_directory(dir_entry.path(), processor, sem).await) +36 | || } else { +37 | || None +38 | || } + | || ^ + | ||_________________| + | |__________________`if` and `else` have incompatible types + | expected opaque type, found type parameter `F` + | + = note: expected type `Option>` + found enum `Option>` + = help: type parameters must be constrained to match other types + = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters +``` + +Alan is confused. In line 33, `rate_limit` returns `Future`, so why is this an opaque `Future`? So far as he can tell, the `Option` returned on line 33 is the same type as the `Option>` where `F: Future` returned on line 35. + +So he strips the problem down to only a few lines of code, and still he cannot figure out why the compiler complains: +```rust +use futures::{future::pending, Future}; + +async fn passthru(fut: F) -> T +where + F: Future, +{ + fut.await +} + +fn main() { + let func = pending::; + match true { + true => passthru(func()), + false => func(), + }; +} +``` + +To which the compiler nevertheless replies: +``` +error[E0308]: `match` arms have incompatible types + --> src/main.rs:14:18 + | +12 | / match true { +13 | | true => passthru(func()), + | | ---------------- this is found to be of type `impl futures::Future` +14 | | false => func(), + | | ^^^^^^ expected opaque type, found struct `futures::future::Pending` +15 | | }; + | |_____- `match` arms have incompatible types + | + = note: expected type `impl futures::Future` + found struct `futures::future::Pending` +``` + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** +- The manual desugaring required for `async` recursion erases some of the "magic" of `async`. +- Some programmers may never implement custom types that are `Future`, instead using standard constructs like `async` blocks to produce them. In these cases, the programmer might assume the returned `Future`s should have concrete types with known sizes, which would allow them to work directly with the returned types rather than have to deal with the complexities of trait objects, `Box`-ing, and opaque type comparisons. +- `Pin` documentation focuses on data that can or cannot "move" in memory. To someone new to Rust, it might be easy to confuse this concept with "move" semantics in the context of ownership. + +### **What are the sources for this story?** +I describe my own experience while working on my first Rust project. + +### **Why did you choose Alan to tell this story?** +I chose Alan to tell this story because I envision him comping from Python. I mostly work in `asyncio` Python by day, which means my exposure to async is shaped by what I'd expect from a language without traits, and one where heap wrangling and memory addressing is abstracted away. + +### **How would this story have played out differently for the other characters?** +I'm not sure, but I'd assume: +- **Grace** would not get tripped up on the need for `Box::pin` +- **Niklaus** might share the confusion expressed above +- **Barbara** might wish we could use `async` sugaring in recursive functions. diff --git a/src/vision/status_quo/alan_tries_to_debug_a_hang.md b/src/vision/submitted_stories/status_quo/alan_tries_to_debug_a_hang.md similarity index 97% rename from src/vision/status_quo/alan_tries_to_debug_a_hang.md rename to src/vision/submitted_stories/status_quo/alan_tries_to_debug_a_hang.md index 99bd572e..2e91f59e 100644 --- a/src/vision/status_quo/alan_tries_to_debug_a_hang.md +++ b/src/vision/submitted_stories/status_quo/alan_tries_to_debug_a_hang.md @@ -130,11 +130,11 @@ Disgruntled, Alan begins the arduous, boring task of instrumenting the applicati * Some of the characters with either a background in Rust or a background in systems programming might know that Rust's async doesn't always map to an underlying system feature and so they might expect that `gdb` or `lldb` is unable to help them. * Barbara, the experienced Rust dev, might also have used a tracing/instrumentation library from the beginning and have that to fall back on rather than having to do the work to add it now. -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[characters]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/alan_wants_prefetch_iterator.md b/src/vision/submitted_stories/status_quo/alan_wants_prefetch_iterator.md new file mode 100644 index 00000000..579290b9 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/alan_wants_prefetch_iterator.md @@ -0,0 +1,86 @@ +# 😱 Status quo stories: Alan wants an async iterator with prefetch + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +Alan once wrote a data processing microservice in a GC'd language which was designed for high throughput. Now he wants to write it in Rust and have strong ownership model. + +The original service consumes messages from a source stream (e.g. Kafka), process them and produces results to another stream and/or saves them to a database. Since the service acquires some data from other sources like external services and its own PostgreSQL database, Alan batches incoming messages to acquire as much as possible data from that sources with minimal overhead. + +Since messages might arrive with some delays between them, or can end at some point for a while, their number is unknown, there's an async iterator which reads the input stream and waits some time before producing a batch if the next message isn't immediately ready. + +Alan explored `FutureExt` from `async-std` and found no evidence that it's possible to wait for multiple futures returning different results (it's not possible for `ValueTask`s in .NET, but it worked well with `Task`s which can be awaited multiple times). Later he was suggested to use an `enum` and the `race` method to achive his goal: + +```rust +enum Choices { + A(A), + B(B), + C(C), +} + +// convert each future into the type `Choices<...>`: +let future_a = async move { A(future_a.await) }; +let future_b = async move { B(future_b.await) }; +let future_c = async move { C(future_c.await) }; + +// await the race: +match future_a.race(future_b).race(future_c).await { + A(a) => ..., + B(b) => ...., + C(c) => ..., +} +``` + +While that helped Alan, it was completely unobvious to him. He expected to see a macro accepting futures and producing a new future to be awaited: + +```rust +match race!(future_a, future_b, future_c).await { + // ... +} +``` + +Having `join!` would be nice too for Alan, so he can avoid binding variables to futures which later shall be awaited: + +```rust +// How it's now +let future_a = do_async_a(); +let future_b = do_async_b(); +let future_c = do_async_c(); + +let result_a = future_a.await; +let result_b = future_b.await; +let result_c = future_c.await; + +// How it could be +let (result_a, result_b, result_c) = join!(future_a, future_b, future_c).await; +``` + +## 🤔 Frequently Asked Questions + +*Here are some standard FAQ to get you started. Feel free to add more!* + +### **What are the morals of the story?** +* Even though Alan had experience writing async code in other languages, he had a hard time figuring out how to do relatively simple things in Rust, like joining or racing on futures of different types. + +### **What are the sources for this story?** +Personal experience of the author. + +### **Why did you choose Alan to tell this story?** +As a backend developer in a GC'd language, Alan writes async code every day. He wants to gain the maximum performance and have memory safety at the same time. + +### **How would this story have played out differently for the other characters?** +*In some cases, there are problems that only occur for people from specific backgrounds, or which play out differently. This question can be used to highlight that.* + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/submitted_stories/status_quo/alan_writes_a_web_framework.md similarity index 82% rename from src/vision/status_quo/alan_writes_a_web_framework.md rename to src/vision/submitted_stories/status_quo/alan_writes_a_web_framework.md index 6d2eab59..5e73601a 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/submitted_stories/status_quo/alan_writes_a_web_framework.md @@ -1,12 +1,12 @@ # 😱 Status quo stories: Alan writes a web framework -[How To Vision: Status Quo]: ../how_to_vision/status_quo.md -[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async-foundations/master/src/vision/status_quo/template.md -[`status_quo`]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/status_quo -[`SUMMARY.md`]: https://github.com/rust-lang/wg-async-foundations/blob/master/src/SUMMARY.md -[open issues]: https://github.com/rust-lang/wg-async-foundations/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas -[open an issue of your own]: https://github.com/rust-lang/wg-async-foundations/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= +[How To Vision: Status Quo]: ../status_quo.md +[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async/master/src/vision/status_quo/template.md +[`status_quo`]: https://github.com/rust-lang/wg-async/tree/master/src/vision/status_quo +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md +[open issues]: https://github.com/rust-lang/wg-async/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas +[open an issue of your own]: https://github.com/rust-lang/wg-async/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= ## 🚧 Warning: Draft status 🚧 @@ -17,9 +17,9 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee ## The story -[YouBuy](../projects/YouBuy.md) is written using an async web framework that predates the stabilization of async function syntax. When [Alan] joins the company, it is using async functions for its business logic, but can't use them for request handlers because the framework doesn't support it yet. It requires the handler's return value to be `Box>`. Because the web framework predates async function syntax, it requires you to take ownership of the request context (`State`) and return it alongside your response in the success/error cases. This means that even with async syntax, an http route handler in this web framework looks something like this (from [the Gotham Diesel example](https://github.com/gotham-rs/gotham/blob/9f10935bf28d67339c85f16418736a4b6e1bd36e/examples/diesel/src/main.rs)): +[YouBuy](../../projects/YouBuy.md) is written using an async web framework that predates the stabilization of async function syntax. When [Alan] joins the company, it is using async functions for its business logic, but can't use them for request handlers because the framework doesn't support it yet. It requires the handler's return value to be `Box>`. Because the web framework predates async function syntax, it requires you to take ownership of the request context (`State`) and return it alongside your response in the success/error cases. This means that even with async syntax, an http route handler in this web framework looks something like this (from [the Gotham Diesel example](https://github.com/gotham-rs/gotham/blob/9f10935bf28d67339c85f16418736a4b6e1bd36e/examples/diesel/src/main.rs)): -```rust,ignore +```rust // For reference, the framework defines these type aliases. pub type HandlerResult = Result<(State, Response), (State, HandlerError)>; pub type HandlerFuture = dyn Future + Send; @@ -43,7 +43,7 @@ fn get_products_handler(state: State) -> Pin> { } ``` and then it is registered like this: -```rust,ignore +```rust router_builder.get("/").to(get_products_handler); ``` @@ -51,7 +51,7 @@ The handler code is forced to drift to the right a lot, because of the async blo Rather than switching YouBuy to a different web framework, Alan decides to contribute to the web framework himself. After a bit of a slog and a bit of where-clause-soup, he manages to make the web framework capable of using an `async fn` as an http request handler. He does this by extending the router builder with a closure that boxes up the `impl Future` from the async fn and then passes that closure on to `.to()`. -```rust,ignore +```rust fn to_async(self, handler: H) where Self: Sized, @@ -62,13 +62,13 @@ Rather than switching YouBuy to a different web framework, Alan decides to contr } ``` The handler registration then becomes: -```rust,ignore +```rust router_builder.get("/").to_async(get_products_handler); ``` This allows him to strip out the async blocks in his handlers and use `async fn` instead. -```rust,ignore +```rust // Type the library again, in case you've forgotten: pub type HandlerResult = Result<(State, Response), (State, HandlerError)>; @@ -90,7 +90,7 @@ async fn get_products_handler(state: State) -> HandlerResult { It's still not fantastically ergonomic though. Because the handler takes ownership of State and returns it in tuples in the result, Alan can't use the `?` operator inside his http request handlers. If he tries to use `?` in a handler, like this: -```rust,ignore +```rust async fn get_products_handler(state: State) -> HandlerResult { use crate::schema::products::dsl::*; @@ -117,7 +117,7 @@ error[E0277]: `?` couldn't convert the error to `(gotham::state::State, HandlerE Alan knows that the answer is to make another wrapper function, so that the handler can take an `&mut` reference to `State` for the lifetime of the future, like this: -```rust,ignore +```rust async fn get_products_handler(state: &mut State) -> Result, HandlerError> { use crate::schema::products::dsl::*; @@ -131,17 +131,17 @@ async fn get_products_handler(state: &mut State) -> Result, Handl } ``` and then register it with: -```rust,ignore +```rust route.get("/").to_async_borrowing(get_products_handler); ``` but Alan can't work out how to express the type signature for the `.to_async_borrowing()` helper function. He submits his `.to_async()` pull-request upstream as-is, but it nags on his mind that he has been defeated. -Shortly afterwards, someone raises a bug about `?`, and a few other web framework contributors try to get it to work, but they also get stuck. When Alan tries it, the compiler diagnostics keep sending him around in circles . He can work out how to express the lifetimes for a function that returns a `Box` but not an `impl Future` because of how where clauses are expressed. Alan longs to be able to say "this function takes an async function as a callback" (`fn register_handler(handler: impl async Fn(state: &mut State) -> Result)`) and have Rust elide the lifetimes for him, like how they are elided for async functions. +Shortly afterwards, someone raises a bug about `?`, and a few other web framework contributors try to get it to work, but they also get stuck. When Alan tries it, the compiler diagnostics keep sending him around in circles . He can work out how to express the lifetimes for a function that returns a `Box` but not an `impl Future` because of how where clauses are expressed. Alan longs to be able to say "this function takes an async function as a callback" (`fn register_handler(handler: impl async Fn(state: &mut State) -> Result)`) and have Rust elide the lifetimes for him, like how they are elided for async functions. A month later, one of the contributors finds a forum comment by [Barbara] explaining how to express what Alan is after (using higher-order lifetimes and a helper trait). They implement this and merge it. The final `.to_async_borrowing()` implementation ends up looking like this (also from [Gotham](https://github.com/gotham-rs/gotham/blob/89c491fb4322bbc6fbcc8405c3a33e0634f7cbba/gotham/src/router/builder/single.rs)): -```rust,ignore +```rust pub trait AsyncHandlerFn<'a> { type Res: IntoResponse + 'static; type Fut: std::future::Future> + Send + 'a; @@ -211,7 +211,7 @@ When Alan sees another open source project struggling with the same issue, he no ### What are the sources for this story? -* This is from the author's [own experience](https://github.com/rust-lang/wg-async-foundations/issues/78#issuecomment-808193936). +* This is from the author's [own experience](https://github.com/rust-lang/wg-async/issues/78#issuecomment-808193936). ### Why did you choose Alan/YouBuy to tell this story? @@ -221,11 +221,11 @@ When Alan sees another open source project struggling with the same issue, he no * I suspect that even many Barbara-shaped developers would struggle with this problem. -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/aws_engineer.md b/src/vision/submitted_stories/status_quo/aws_engineer.md new file mode 100644 index 00000000..9c8ccb61 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer.md @@ -0,0 +1,60 @@ +# 😱 Status quo stories: Status quo of an AWS engineer + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +This tells the story of Alan, an engineer who works at AWS. + +* [Writing a Java-based service at AWS](aws_engineer/writing_a_java_based_service.md): Alan is accustomed to using many convenient tools for writing Java-based services. +* [Getting started with Rust](aws_engineer/getting_started_with_rust.md): Alan gets tapped to help spin up a new project on a tight timeline. He hasn't used Rust before, so he starts trying to setup an environment and learn the basics. +* [Coming from Java](aws_engineer/coming_from_java.md): Alan finds that some of the patterns he's accustomed to from Java don't translate well to Rust. +* [Exploring the ecosystem](aws_engineer/ecosystem.md): The Rust ecosystem has a lot of useful crates, but they're hard to find. "I don't so much find them as stumble upon them by accident." +* At first, Rust feels quite ergonomic to Alan. The async-await system seems pretty slick. But as he gets more comfortable with Rust, he starts to encounter situations where he can't quite figure out how to get things setup the way he wants, and he has to settle for suboptimal setups: + * [Juggling error handling](aws_engineer/juggling_error_handling.md): Alan tries to use `?` to process errors in a stream. + * [Failure to parallelize](aws_engineer/failure_to_parallelize.md): Alan can't figure out how to parallelize a loop. + * [Borrow check errors](aws_engineer/borrow_check_errors.md): Alan tries to write code that fills a buffer and returns references into it to the caller, only to learn that Rust's borrow checker makes that pattern difficult. +* As Alan goes deeper into Async Rust, he learns that its underlying model can be surprising. One particular [deadlock](aws_engineer/solving_a_deadlock.md) takes him quite a long time to figure out. +* [Encountering pin](aws_engineer/encountering_pin.md): Wrapping streams, `AsyncRead` implementations, and other types requires using `Pin` and it is challenging. +* [Figuring out the best option](aws_engineer/figuring_out_the_best_option.md): Alan often encounters cases where he doesn't know what is the best way to implement something. He finds he has to implement it both ways to tell, and sometimes even then he can't be sure. +* [Testing his service](aws_engineer/testing_the_service.md): Alan invents patterns for Dependency Injection in order to write tests. +* [Using the debugger](aws_engineer/using_the_debugger.md): Alan wishes for a smoother debugging experience. +* [Missed Waker leads to lost performance](aws_engineer/missed_waker_leads_to_lost_performance.md): Alan finds his service his not as fast as the reference server; the problem is ultimately due to a missed `Waker`, which was causing his streams to wake up much later than it should've. +* [Debugging performance problems](aws_engineer/debugging_performance_problems.md): Alan finds more performance problems and tries to figure out their cause using tooling like `perf`. It's hard. +* [Getting ready to deploy](aws_engineer/getting_ready_to_deploy.md): Alan prepares to deply the service. +* [Using JNI](aws_engineer/using_jni.md): Alan uses JNI to access services that are only available using Java libraries. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +* Building services in Rust can yield really strong results, but a lot of hurdles remain: + * 'If it compiles, it works' is not true: there are lots of subtle variations. + * Debugging correctness and performance problems is hard, and the tooling is not what folks are used to. + * Few established patterns to things like DI. + * The ecosystem has a lot of interesting things in it, but it's hard to navigate. + +### **What are the sources for this story?** + +This story is compiled from discussions with service engineers in various AWS teams. + +### **Why did you choose Alan to tell this story?** + +Because Java is a very widely used language at AWS. + +### **How would this story have played out differently for the other characters?** + +Most parts of it remain the same; the main things that were specific to Java are some of the patterns Alan expected to use. Similarly, few things are specific to AWS apart from some details of the setup. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/borrow_check_errors.md b/src/vision/submitted_stories/status_quo/aws_engineer/borrow_check_errors.md new file mode 100644 index 00000000..66a423b1 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/borrow_check_errors.md @@ -0,0 +1,62 @@ +# Status quo of an AWS engineer: Borrow check errors + +Alan has more or less gotten the hang of the borrow checker, but sometimes it still surprises him. One day, he is working on a piece of code in DistriData. There are a set of connections: + +```rust= +struct Connection { + buffer: Vec, +} +``` + +and each `Connection` has the ability to iterate through various requests. These requests return subslices of the data in the connection: + +```rust= +struct Request<'a> { + headers: Vec<&'a u8>, +} +``` + +He writes a routine to get the next request from the connection. It begins by reading data into the internal buffer and then parsing from that buffer and returning the request ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6d8f2e7349e25677b25c527964842de8)): + +```rust= +impl Connection { + pub async fn read_next(&mut self) -> Request<'_> { + loop { + self.read_into_buffer(); + + // can't borrow self.buffer, even though we only hang on to it in the + // return branch + match Request::try_parse(&self.buffer) { + Some(r) => return r, + None => continue, + } + } + } + + async fn read_into_buffer(&mut self) { + self.buffer.push(1u8); + } +} +``` + +This code, however, doesn't build. He gets the following error: + +``` +error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable + --> src/lib.rs:15:12 + | +13 | pub async fn read_next(&mut self) -> Request<'_> { + | - let's call the lifetime of this reference `'1` +14 | loop { +15 | self.read_into_buffer().await; + | ^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here +... +19 | match Request::try_parse(&self.buffer) { + | ------------ immutable borrow occurs here +20 | Some(r) => return r, + | - returning this value requires that `self.buffer` is borrowed for `'1` +``` + +This is confusing. He can see that there is a mutable borrow occurring, and an immutable one, but it seems like they occur at disjoint periods of time. Why is the compiler complaining? + +After asking on `#rust` in the AWS Slack, he learns that this is a pattern that Rust's borrow checker just can't support. It gets confused when you return data from functions and winds up producing errors that aren't necessary. Apparently there's some [research project named after a Hamlet play](https://github.com/rust-lang/polonius/) that might help, but that isn't going to help him now. The slack channel points him at the [ouroboros](https://github.com/joshua-maros/ouroboros) project and he eventually uses it to work around the problem ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=59b2cb72529e58c13ab00eee1e9c0435)). diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/coming_from_java.md b/src/vision/submitted_stories/status_quo/aws_engineer/coming_from_java.md new file mode 100644 index 00000000..109d7cf4 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/coming_from_java.md @@ -0,0 +1,9 @@ +# Status quo of an AWS engineer: Coming from Java + +At first, Alan is trying to write Rust code as if it were Java. He's accustomed to avoiding direct dependencies between types and instead modeling everything with an `interface`, so at first he creates a lot of Rust traits. He quickly learns that `dyn Trait` can be kind of painful to use. + +He also learns that Rust doesn't really permit you to add references willy nilly. It was pretty common in Java to have a class that was threaded everywhere with all kinds of references to various parts of the system. This pattern often leads to borrow check errors in Rust. + +He gets surprised by parallelism. He wants a concurrent hash map but can't find one in the standard library. There are a lot of crates on crates.io but it's not clear which would be best. He decides to use a mutex-protected lock. + +He is surprised because futures in Java correspond to things executed in parallel, but in Rust they don't. It takes him some time to get used to this. Eventually he learns that a Rust future is more akin to a java *callable*. \ No newline at end of file diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/debugging_performance_problems.md b/src/vision/submitted_stories/status_quo/aws_engineer/debugging_performance_problems.md new file mode 100644 index 00000000..bb6fb45d --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/debugging_performance_problems.md @@ -0,0 +1,10 @@ +# Status quo of an AWS engineer: Debugging overall performance loss + +Alan's service is working better and better, but performance is still lagging from where he hoped it would be. It seems to be about 20% slower than the Java version! After [calling in Barbara to help him diagnose the problem](https://rust-lang.github.io/wg-async/vision/status_quo/alan_iteratively_regresses.html), Alan identifies one culprit: Some of the types in Alan's system are really large! The system seems to spend a surprising amount of time just copying bytes. Barbara helped Alan diagnose this by showing him some hidden rustc flags, tinkering with his perf setup, and a few other tricks. + +There is still a performance gap, though, and Alan's not sure where it could be coming from. There are a few candidates: + +* Perhaps they are not using tokio's scheduler optimally. +* Perhaps the memory allocation costs introduced by the `#[async_trait]` are starting to add up. + +Alan tinkers with jemalloc and finds that it does improve performance, so that's interesting, but he'd like to have a better understanding of *why*. diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/ecosystem.md b/src/vision/submitted_stories/status_quo/aws_engineer/ecosystem.md new file mode 100644 index 00000000..f8b800d7 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/ecosystem.md @@ -0,0 +1,11 @@ +# Status quo of an AWS engineer: Exploring the ecosystem + +Alan finds that cargo is a super powerful tool, but he finds it very hard to find crates to use. He doesn't really feel he discovers crates so much as "falls upon" them by chance. For example, he happened to see a stray mention of `cargo bloat` in the internals form, and that turned out to be exactly what he needed. He finds the `async-trait` crate in a similar way. He's happy these tools exist, but he wishes he had more assurance of finding them; he wonders what useful things are out there that he *doesn't* know about. + +In some cases, there are a lot of choices and it's really hard to tell which is best. Alan spent some time evaluating crates that do md5 hashing, for example, and found tons of choices. He does some quick performance testing and finds huge differences: openssl seems to be the fastest, so he takes that, but he is worried he may have missed some crates. + +He had decided to use tokio because it was the thing that everyone else is using. But he gradually learns that there are more runtimes out there. Sometimes, when he adds a crate, he finds that it is bringing in a new set of dependencies that don't seem necessary. + +He also gets confused by the vast array of options. `tokio` seems to have an `AsyncRead` trait, for example, but so does `futures` -- which one should he use? + +He's heard of other runtimes and he might like to be able to try them out, but it would be too much work. Occasionally he winds up with multiple versions of the same crate, which can lead either to compilation or runtime errors. For example, when rusoto upgraded to a new version of tokio, this spilled over into confusing huge error messages from the rusoto builder owing to subtle trait and type mismatches. Fortunately the recent tokio 1.0 release promises to solve some of those problems. diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/encountering_pin.md b/src/vision/submitted_stories/status_quo/aws_engineer/encountering_pin.md new file mode 100644 index 00000000..17d25430 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/encountering_pin.md @@ -0,0 +1,12 @@ +# Status quo of an AWS engineer: Encountering pin + +As Alan is building the server, he encounters a case where he wants to extend a stream of data to track some additional metrics. The stream implements [`AsyncRead`]. He thinks, "Ah, I'll just make a wrapper type that can extend any `AsyncRead`." He opens up the rustdoc, though, and realizes that this may be a bit tricky. "What is this `self: Pin<&mut Self>`?" notation, he thinks. He had vaguely heard of `Pin` when skimming the docs for futures and things but it was never something he had to work with directly before. + +[`AsyncRead`]: https://docs.rs/tokio/1.5.0/tokio/io/trait.AsyncRead.html + +Alan's experiences here are well documented in [Alan hates writing a Stream](https://rust-lang.github.io/wg-async/vision/status_quo/alan_hates_writing_a_stream.html). Suffice to say that, at long last, he does it to work, but he does not feel he really understands what is going on. Talking with his coworkers on slack he notes, "Mostly I just add `Pin` and whatever else the compiler asks for until it works; then I pray it doesn’t crash." :crossed_fingers: + +*References:* + +* [Alan hates writing a Stream](../alan_hates_writing_a_stream.html) +* ["Pin and suffering", by faster-than-lime](https://fasterthanli.me/articles/pin-and-suffering) diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/failure_to_parallelize.md b/src/vision/submitted_stories/status_quo/aws_engineer/failure_to_parallelize.md new file mode 100644 index 00000000..623b84b1 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/failure_to_parallelize.md @@ -0,0 +1,144 @@ +# Status quo of an AWS engineer: Failure to parallelize + +As Alan reads the loop he just built, he realizes that he ought to be able to process each shared independently. He decides to try spawning the tasks in parallel. He starts by trying to create a stream that spawns out tasks: + +```rust= +// Send each chunk from each shared to each host: +while let Some(chunks) = shards.next().await { + tokio::spawn(async move { + let chunk_futures = chunks + .into_iter() + .zip(&mut host_senders) + .map(|(chunk, sender)| sender.send_data(chunk)); + + join_all(chunk_futures) + .await + .into_iter() + .collect::, _>>()?; + }) +} +``` + +But this is giving him errors about the `?` operator again: + +``` +error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `Try`) + --> src/lib.rs:21:13 + | +15 | tokio::spawn(async move { + | _________________________________- +16 | | let chunk_futures = chunks +17 | | .into_iter() +18 | | .zip(&mut host_senders) +... | +21 | /| join_all(chunk_futures) +22 | || .await +23 | || .into_iter() +24 | || .collect::, _>>()?; + | ||________________________________________________^ cannot use the `?` operator in an async block that returns `()` +25 | | }); + | |_________- this function should return `Result` or `Option` to accept `?` + | + = help: the trait `Try` is not implemented for `()` + = note: required by `from_error` +``` + +Annoyed, he decides to convert those to `unwrap` calls temporarily (which will just abort the process on error) just to see if he can get something working: + +```rust= + while let Some(chunks) = shards.next().await { + tokio::spawn(async move { + let chunk_futures = chunks + .into_iter() + .zip(&mut host_senders) + .map(|(chunk, sender)| sender.send_data(chunk)); + + join_all(chunk_futures) + .await + .into_iter() + .collect::, _>>() + .unwrap(); + }); + } +``` + +But now he gets this error ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fd22ae9a8a7ec68cb73b2a10ecd702f4)): + +``` +error[E0382]: use of moved value: `host_senders` + --> src/lib.rs:15:33 + | +12 | let mut host_senders: Vec = vec![]; + | ---------------- move occurs because `host_senders` has type `Vec`, which does not implement the `Copy` trait +... +15 | tokio::spawn(async move { + | _________________________________^ +16 | | let chunk_futures = chunks +17 | | .into_iter() +18 | | .zip(&mut host_senders) + | | ------------ use occurs due to use in generator +... | +24 | | .collect::, _>>().unwrap(); +25 | | }); + | |_________^ value moved here, in previous iteration of loop +``` + +He removes the `move` keyword from `async move`, but then he sees: + +``` +error[E0373]: async block may outlive the current function, but it borrows `host_senders`, which is owned by the current function + --> src/lib.rs:15:28 + | +15 | tokio::spawn(async { + | ____________________________^ +16 | | let chunk_futures = chunks +17 | | .into_iter() +18 | | .zip(&mut host_senders) + | | ------------ `host_senders` is borrowed here +... | +24 | | .collect::, _>>().unwrap(); +25 | | }); + | |_________^ may outlive borrowed value `host_senders` + | + = note: async blocks are not executed immediately and must either take a reference or ownership of outside variables they use +help: to force the async block to take ownership of `host_senders` (and any other referenced variables), use the `move` keyword + | +15 | tokio::spawn(async move { +16 | let chunk_futures = chunks +17 | .into_iter() +18 | .zip(&mut host_senders) +19 | .map(|(chunk, sender)| sender.send_data(chunk)); +20 | + ... + +error[E0499]: cannot borrow `host_senders` as mutable more than once at a time + --> src/lib.rs:15:28 + | +15 | tokio::spawn(async { + | ______________________-_____^ + | |______________________| + | || +16 | || let chunk_futures = chunks +17 | || .into_iter() +18 | || .zip(&mut host_senders) + | || ------------ borrows occur due to use of `host_senders` in generator +... || +24 | || .collect::, _>>().unwrap(); +25 | || }); + | || ^ + | ||_________| + | |__________`host_senders` was mutably borrowed here in the previous iteration of the loop + | argument requires that `host_senders` is borrowed for `'static` +``` + +At this point, he gives up and leaves a `// TODO` comment: + +```rust= +// TODO: This loop should be able to execute in parallel, +// but I can't figure out how to make it work. -Alan +while let Some(chunks) = shards.next().await { + ... +} +``` + +*Editorial comment:* In this case, the channel to which he is sending the data can only receive data from a single sender at a time (it has an `&mut self`). Rust is potentially saving Alan from a nasty data race here. He could have used a mutex around the senders, but he would still hit issues trying to spawn parallel threads because he lacks an API that lets him borrow from the stack. diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/figuring_out_the_best_option.md b/src/vision/submitted_stories/status_quo/aws_engineer/figuring_out_the_best_option.md new file mode 100644 index 00000000..9b72a957 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/figuring_out_the_best_option.md @@ -0,0 +1,3 @@ +# Status quo of an AWS engineer: Figuring out the best option + +Sometime after working on `AsyncRead`, Alan stumbles over the `async-trait` crate. This crate offers a macro that will let him add `async fn` to traits. He's excited about this because it seems like it would allow him to rewrite some of the custom `AsyncRead` impls in a cleaner way. The only problem is that he can't really judge what the implications are going to be -- will it be faster? Slower? It's hard to tell until it's done. He feels like this comes up a lot in Rust: he is forced to make a choice and see it all the way through to the end before he can decide whether he likes it (or if it will work at all: sometimes he encounters a compiler error part of the way through that he just can't figure out how to resolve). It's particularly frustrating in Async Rust where [there seem to be so many options to choose from](https://rust-lang.github.io/wg-async/vision/status_quo/barbara_needs_async_helpers.html). diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/getting_ready_to_deploy.md b/src/vision/submitted_stories/status_quo/aws_engineer/getting_ready_to_deploy.md new file mode 100644 index 00000000..4b6f0214 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/getting_ready_to_deploy.md @@ -0,0 +1,7 @@ +# Status quo of an AWS engineer: Getting ready to deploy the service + +The next morning, Alan is talking to his team. The service is more-or-less working, although there is room to improve performance. It's time to talk about the Operational Readiness Review (ORR). Before any service can be put into production at AWS, it needs to pass an ORR. This is a stringent process where experienced senior engineers grill the team about all kinds of things that could go wrong and how they would handle them. These plans are gathered into a document that can be consulted should the need arise. + +Alan has been through a few ORRs in his time at AWS. They're always stressful, but they're usually not that big a deal. A lot of the worst cases are handled automatically by the Java frameworks that Alan is accustomed to working with: for example, they have connection timeouts, or facilities for logging particular kinds of events. For the stuff that is not automatic, there are known "best practices" that can help. + +For Rust, there are a lot of unknowns. The standard servers don't exist, and Alan's team has had to roll their own. There aren't nearly as many tools for performance monitoring or other sorts of improvements. Alan's team is treading new ground by deploying a Rust-based service, and they know they have to budget extra time to manage that. \ No newline at end of file diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/getting_started_with_rust.md b/src/vision/submitted_stories/status_quo/aws_engineer/getting_started_with_rust.md new file mode 100644 index 00000000..220cb2b6 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/getting_started_with_rust.md @@ -0,0 +1,23 @@ +# Status quo of an AWS engineer: Getting started with Rust + +For his latest project, Alan is rewriting a core component of [DistriData]. They are trying to move on a tight deadline. + +The component that they are rewriting was implemented in Java, but it was having difficulty with high tail latencies and other performance hiccups. The team has an idea for a new architecture that will be more efficient, and they would like to reduce resource usage by adopting Rust. + +Getting started with Rust is a bit different than what he is used to. There's not much infrastructure. They still define their service interface using the same modeling language, but there is no tooling to generate a server from it. + +[DistriData]: ../../projects/DistriData.html + +## IDE setup + +Of course, the very first thing Alan does it to tweak his IDE setup. He's happy to learn that IntelliJ has support for Rust, since he is accustomed to the keybindings and it has great integration with Brazil, AWS's internal build system. + +Still, as he plays around with Rust code, he realizes that the support is not nearly at the level of Java. Autocomplete often gets confused. For example, when there are two traits with the same name but coming from different crates, IntelliJ often picks the wrong one. It also has trouble with macros, which are very common in async code. Some of Alan's colleagues switch to VSCode, which is sometimes better but has many of the same problems; Alan decides to stick with IntelliJ. + +## Building the first server + +Alan asks around the company to learn more about how Async Rust works and he is told to start with the tokio tutorial and the Rust book. He also joins the company slack channel, where he can ask questions. The tokio tutorial is helpful and he is feeling relatively confident. + +## Missing types during Code review + +One problem Alan finds has to do with AWS's internal tooling (although it would be the same in most places). When browsing Rust code in the IDE, there are lots of tips to help in understanding, such as tooltips showing the types of variables and the like. In code reviews, though, there is only the plain text. Rust's type inference is super useful and make the code compact, but it can be hard to tell what's going on when you just read the plain source. diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/juggling_error_handling.md b/src/vision/submitted_stories/status_quo/aws_engineer/juggling_error_handling.md new file mode 100644 index 00000000..7974b19d --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/juggling_error_handling.md @@ -0,0 +1,136 @@ +# Status quo of an AWS engineer: Juggling error handling + +[DistriData]: ../../projects/DistriData.html + +For example, one day Alan is writing a loop. In this particular part of [DistriData], the data is broken into "shards" and each shard has a number of "chunks". He is connected to various backend storage hosts via HTTP, and he needs to send each chunk out to all of them. He starts by writing some code that uses [`hyper::body::channel`](https://docs.rs/hyper/0.14.7/hyper/body/struct.Body.html#method.channel) to generate a pair of a channel where data can be sent and a resulting HTTP body. He then creates a future for each of those HTTP bodies that will send it to the appropriate host once it is complete. He wants those sends to be executing in the background as the data arrives on the channel, so he creates a [`FuturesUnordered`](https://docs.rs/futures/0.3.14/futures/stream/struct.FuturesOrdered.html) to host them: + +```rust +let mut host_senders: Vec = vec![]; +let mut host_futures = FuturesUnordered::new(); +for host in hosts { + let (sender, body) = hyper::body::Body::channel(); + host_senders.push(sender); + host_futures.push(create_future_to_send_request(body)); +} +``` + +Next, he wants to iterate through each of the shards. For each shard, he will send each chunk to each of the hosts: + +```rust +let mut shards = /* generate a stream of Shards */; +while let Some(chunks) = shards.next().await { + let chunk_futures = chunks + .into_iter() + .zip(&mut host_senders) + .map(|(chunk, sender)| sender.send_data(chunk)?); + + futures::join_all(chunk_futures).await; +} +``` + +The last line is giving him a bit of trouble. Each of the requests to send the futures could fail, and he would like to propagate that failure. He's used to writing `?` to propagate an error, but when he puts `?` in `sender.send_data` he gets an error: + +``` +error[E0277]: the `?` operator can only be applied to values that implement `Try` + --> src/lib.rs:18:40 + | +18 | .map(|(chunk, sender)| sender.send_data(chunk)?); + | ^^^^^^^^^^^^^^^^^^^^^^^^ the `?` operator cannot be applied to type `impl futures::Future` + | + = help: the trait `Try` is not implemented for `impl futures::Future` + = note: required by `into_result` +``` + +"Right," Alan thinks, "I need to await the future." He tries to move the `?` to the result of `join_all`: + +```rust +let mut shards = /* generate a stream of Shards */; +while let Some(chunks) = shards.next().await { + let chunk_futures = chunks + .into_iter() + .zip(&mut host_senders) + .map(|(chunk, sender)| sender.send_data(chunk)); + + futures::join_all(chunk_futures).await?; +} +``` + +But now he sees: + +``` +error[E0277]: the `?` operator can only be applied to values that implement `Try` + --> src/lib.rs:20:9 + | +20 | join_all(chunk_futures).await?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `?` operator cannot be applied to type `Vec>` + | + = help: the trait `Try` is not implemented for `Vec>` + = note: required by `into_result` +``` + +"Ah," he says, "of course, I have a vector of potential errors, not a single error." He remembers seeing a trick for dealing with this in his Rust training. Pulling up the slides, he finds the example. It takes him a little bit to get the type annotations just right, but he finally lands on: + +```rust= +while let Some(chunks) = shards.next().await { + let chunk_futures = chunks + .into_iter() + .zip(&mut host_senders) + .map(|(chunk, sender)| sender.send_data(chunk)); + + join_all(chunk_futures) + .await + .into_iter() + .collect::, _>>()?; +} +``` + +[playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=aa16c1901a13603df7cd050ecc47e61e) + +The loop now works: it sends each chunk from each shard to each host, and propagates errors in a reasonable way. The last step is to write for those writes to complete. To do this, he has until all the data has actually been sent, keeping in mind that there could be errors in these sends too. He writes a quick loop to iterate over the stream of sending futures `host_futures` that he created earlier: + +```rust= +loop { + match host_futures.next().await { + Some(Ok(response)) => handle_response(response)?, + Some(Err(e)) => return Err(e)?, + None => return Ok(()), + } +} +``` + +It takes him a few tries to get this loop right too. The `Some(Err(e))` case in particular is a bit finnicky. He tried to just `return Err(e)` but it gave him an error, because the of `e` didn't match the more generic `Box` type that his function returns. He remembered that the `?` operator performs some interconversion, though, and that you can do `Err(e)?` to workaround this particular problem. + +He surveys the final function he has built, feeling a sense of satisfaction that he got it to work. Still, he can't help but think that this was an awful lot of work just to propagate errors. Plus, he knows from experience that the errors in Rust are often less useful for finding problems than the ones he used to get in Java. Rust errors don't capture backtraces, for example. He tried to add some code to capture backtraces at one point but it seemed really slow, taking 20ms or so to snag a backtrace, and he knew that would be a problem in production. + +```rust= +// Prepare the outgoing HTTP requests to each host: +let mut host_senders: Vec = vec![]; +let mut host_futures = FuturesUnordered::new(); +for host in hosts { + let (sender, body) = hyper::body::Body::channel(); + host_senders.push(sender); + host_futures.push(create_future_to_send_request(body)); +} + +// Send each chunk from each shared to each host: +while let Some(chunks) = shards.next().await { + let chunk_futures = chunks + .into_iter() + .zip(&mut host_senders) + .map(|(chunk, sender)| sender.send_data(chunk)); + + join_all(chunk_futures) + .await + .into_iter() + .collect::, _>>()?; +} + +// Wait for all HTTP requests to complete, aborting on error: +loop { + match host_futures.next().await { + Some(Ok(response)) => handle_response(response)?, + Some(Err(e)) => return Err(e)?, + None => return Ok(()), + } +} +``` \ No newline at end of file diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/missed_waker_leads_to_lost_performance.md b/src/vision/submitted_stories/status_quo/aws_engineer/missed_waker_leads_to_lost_performance.md new file mode 100644 index 00000000..bcf6a1f1 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/missed_waker_leads_to_lost_performance.md @@ -0,0 +1,14 @@ +# Status quo of an AWS engineer: Missed `Waker` leads to lost performance + +Once the server is working, Alan starts to benchmark it. He is not really sure what to expect, but he is hoping to see an improvement in performance relative to the baseline service they were using before. To his surprise, it seems to be running slower! + +After trying a few common tricks to improve performance without avail, Alan wishes -- not for the first time -- that he had better tools to understand what was happening. He decides instead to add more metrics and logs in his service, to understand where the bottlenecks are. Alan is used to using a well-supported internal tool (or a mature open source project) to collect metrics, where all he needed to do was pull in the library and set up a few configuration parameters. + +However, in Rust, there is no widely-used, battle-tested library inside and outside his company. Even less so in an async code base! So Alan just used what seemed to be the best options: tracing and metrics crate, but he quickly found that they couldn't do a few of the things he wants to do, and somehow invoking the metrics is causing his service to be even slower. Now, Alan has to debug and profile his metrics implementation before he can even fix his service. (Cue another story on how that's difficult...) + +After a few days of poking at the problem, Alan notices something odd. It seems like there is often a fairly large delay between the completion of a particular event and the execution of the code that is meant to respond to that event. Looking more closely, he realizes that the code for handling that event fails to trigger the `Waker` associated with the future, and hence the future never wakes up. + +As it happens, this problem was hidden from him because that particular future was combined with a number of others. Eventually, the other futures get signalled, and hence the event does get handled -- but less promptly than it should be. He fixes the problem and performance is restored. + +"I'm glad I had a baseline to compare this against!", he thinks. "I doubt I would have noticed this problem otherwise." + diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/solving_a_deadlock.md b/src/vision/submitted_stories/status_quo/aws_engineer/solving_a_deadlock.md new file mode 100644 index 00000000..78706371 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/solving_a_deadlock.md @@ -0,0 +1,90 @@ +# Status quo of an AWS engineer: Solving a deadlock + +Alan logs into work the next morning to see a message in Slack: + +> Alan, I've noticed that the code to replicate the shards across the hosts is sometimes leading to a deadlock. Any idea what's going on? + +This is the same code that Alan tried to parallelize earlier. He pulls up the function, but everything *seems* correct! It's not obvious what the problem could be. + +```rust= +// Prepare the outgoing HTTP requests to each host: +let mut host_senders: Vec = vec![]; +let mut host_futures = FuturesUnordered::new(); +for host in hosts { + let (sender, body) = hyper::body::Body::channel(); + host_senders.push(sender); + host_futures.push(create_future_to_send_request(body)); +} + +// Send each chunk from each shared to each host: +while let Some(chunks) = shards.next().await { + let chunk_futures = chunks + .into_iter() + .zip(&mut host_senders) + .map(|(chunk, sender)| sender.send_data(chunk)); + + join_all(chunk_futures) + .await + .into_iter() + .collect::, _>>()?; +} + +// Wait for all HTTP requests to complete, aborting on error: +loop { + match host_futures.next().await { + Some(Ok(response)) => handle_response(response)?, + Some(Err(e)) => return Err(e).map_err(box_err)?, + None => return Ok(()), + } +} +``` + +He tries to reproduce the deadlock. He is able to reproduce the problem readily enough, but only with larger requests. He had always used small tests before. He connects to the process with the debugger but he can't really make heads or tails of what tasks seem to be stuck (see [Alan tries to debug a hang](https://rust-lang.github.io/wg-async/vision/status_quo/alan_tries_to_debug_a_hang.html) or [Barbara wants async insights](https://rust-lang.github.io/wg-async/vision/status_quo/barbara_wants_async_insights.html)). He resorts to sprinkling logging everywhere. + +At long last, he starts to see a pattern emerging. From the logs, he sees the data from each chunk is being sent to the hyper channel, but it never seems to be sent over the HTTP connection to the backend hosts. He is pretty confused by this -- he thought that the futures he pushed into `host_futures` should be taking care of sending the request body out over the internet. He goes to talk to Barbara -- [who, as it happens, has been through this very problem in the past](https://rust-lang.github.io/wg-async/vision/status_quo/barbara_battles_buffered_streams.html) -- and she explains to him what is wrong. + +"When you push those futures into `FuturesUnordered`", she says, "they will only make progress when you are actually awaiting on the stream. With the way the loop is setup now, the actual sending of data won't start until that third loop. Presumably your deadlock is because the second loop is blocked, waiting for some of the data to be sent." + +"Huh. That's...weird. How can I fix it?", asks Alan. + +"You need to spawn a separate task," says Barbara. "Something like this should work." She modifies the code to spawn a task that is performing the third loop. That task is spawned before the second loop starts: + +```rust= +// Prepare the outgoing HTTP requests to each host: +let mut host_senders: Vec = vec![]; +let mut host_futures = FuturesUnordered::new(); +for host in hosts { + let (sender, body) = hyper::body::Body::channel(); + host_senders.push(sender); + host_futures.push(create_future_to_send_request(body)); +} + +// Make sure this runs in parallel with the loop below! +let send_future = tokio::spawn(async move { + // Wait for all HTTP requests to complete, aborting on error: + loop { + match host_futures.next().await { + Some(Ok(response)) => handle_response(response)?, + Some(Err(e)) => break Err(e)?, + None => break Ok(()), + } + } +}); + +// Send each chunk from each shared to each host: +while let Some(chunks) = shards.next().await { + let chunk_futures = chunks + .into_iter() + .zip(&mut host_senders) + .map(|(chunk, sender)| sender.send_data(chunk)); + + join_all(chunk_futures) + .await + .into_iter() + .collect::, _>>()?; +} + +send_future.await +``` + +"Oof", says Alan, "I'll try to remember that!" diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/testing_the_service.md b/src/vision/submitted_stories/status_quo/aws_engineer/testing_the_service.md new file mode 100644 index 00000000..f6b122e4 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/testing_the_service.md @@ -0,0 +1,25 @@ +# Status quo of an AWS engineer: Testing the service + +At first, Alan is content to test by hand. But once the server is starting to really work, he realizes he needs to do unit testing. He wants to do something like [Mockito](https://site.mockito.org/) in Rust, so he starts searching the internet to find out what the options are. To his surprise, he learns that there doesn't seem to be any comparable framework in Rust. + +One option he considers is making all of his functions generic. For example, he could create a trait to model, for example, the network, so that he can insert artificial pauses and other problems during testing: + +```rust +trait Network { + ... +} +``` + +Writing such a trait is fairly complicated, but even if he wrote it, he would have to make all of his structs and functions generic: + +```rust +struct MyService { + ... +} +``` + +Alan starts threading these parameters through the code and quickly gets overwhelmed. + +He decides instead to test his real code without any mocking. He and his team start building a load-testing framework, they call it "simworld". They need to be able to inject network errors, control timing, and force other unusual situations. + +Building simworld takes a lot of time, but it is very useful, and they start to gain some confidence in their code. diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/using_jni.md b/src/vision/submitted_stories/status_quo/aws_engineer/using_jni.md new file mode 100644 index 00000000..278876ff --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/using_jni.md @@ -0,0 +1,9 @@ +# Status quo of an AWS engineer: Using JNI + +One other problem that Alan's team has encountered is that some of the standard libraries they would use at AWS are only available in Java. After some tinkering, Alan's team decides to stand-up a java server as part of their project. The idea is that the server can accept the connections and then use JNI to invoke the Rust code; having the Rust code in process means it can communicate directly with the underlying file descriptor and avoid copies. + +They stand up the Java side fairly quickly and then spend a bit of time experimenting with different ways to handle the "handoff" to Rust code. The first problem is keeping the tokio runtime alive. Their first attempt to connect using JNI was causing the runtime to get torn down. But they figure out that they can store the Runtime in a static variable. + +Next, they find having Rust code access Java objects is quite expensive; it's cheaper to pass bytebuffers at the boundary using protobuf. They try a few options for serialization and deserialization to find which works best. + +Overall, the integration with the JNI works fairly smoothly for them, but they wish there had been some documented pattern to have just shown them the best way to set things up, rather than them having to discover it. \ No newline at end of file diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/using_the_debugger.md b/src/vision/submitted_stories/status_quo/aws_engineer/using_the_debugger.md new file mode 100644 index 00000000..4929e18b --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/using_the_debugger.md @@ -0,0 +1,7 @@ +# Status quo of an AWS engineer: Using the debugger + +Even though the code is starting to work, they soon uncover a test that is not behaving as it ought to. Alan decides to try loading the Rust code into the debugger. He quickly realizes that the debugger is showing him the raw threads that are used to implement his service, and not the tasks and things that the service uses at a logical level, but that's not a problem for what he's doing right now. He sets a breakpoint on a particular line of code that corresponds to the place where things seem to be going wrong. + +At first, the debugger seems to be working great, but Alan soon realizes that the experiences is a far cry from what he is used to with IntelliJ and Java code. Stepping through the code is unpredictable; it's not always obvious what function the will be stepping into. More than once Alan is confronted with a screen full of assembly. "No thank you," he thinks, and just avoids stepping into that function in the future. He finds that he often cannot print the values of variables ('variable optimized out', says the debugger) or execute code dynamically. Sometimes he is able to print them but instead of seeing something useful, he gets a bunch of random pointer values. + +Alan gives up on the debugger. He starts to thread printfs and logging statements throughout his code. The [`tracing`] crate is pretty useful. Eventually, he is able to find and fix the problem and get his test case passing. \ No newline at end of file diff --git a/src/vision/submitted_stories/status_quo/aws_engineer/writing_a_java_based_service.md b/src/vision/submitted_stories/status_quo/aws_engineer/writing_a_java_based_service.md new file mode 100644 index 00000000..9791b5b1 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/aws_engineer/writing_a_java_based_service.md @@ -0,0 +1,16 @@ +# Status quo of an AWS engineer: Writing a Java-based service + +Alan has been working at AWS for the last six years. He's accustomed to a fairly standard workflow for launching Java-based services: + +* Write a description of the service APIs using a modeling language like [Smithy](https://awslabs.github.io/smithy/). +* Submit the description to a webpage, which gives a standard service implementation based on [netty](https://netty.io/). Each of the API calls in the modeling language has a function with a `/* TODO */` comment to fill in. +* As Alan works with his team to fill in each of those items, he makes use of a number of standard conventions: + * Mocking with projects like [mockito](https://site.mockito.org/) to allow for unit testing of specific components. +* Alan uses a variety of nice tools: + * Advanced IDEs like IntelliJ, which offer him suggestions as he works. + * Full-featured, if standard, debuggers; he can run arbitrary code, mutate state, step into and out of functions with ease. + * Tools for introspecting the VM state to get heap usage information and other profiling data. + * Performance monitoring frameworks +* As Alan is preparing to launch his service, he has to conduct an Operational Readiness Review (ORR): + * This consists of a series of detailed questions covering all kinds of nasty scenarios that have arisen in deployments past. For each one, he has to explain how his service will handle it. + * For most of them, the standard framework has pre-generated code that covers it, or he is able to use standard patterns. diff --git a/src/vision/submitted_stories/status_quo/barbara_anguishes_over_http.md b/src/vision/submitted_stories/status_quo/barbara_anguishes_over_http.md new file mode 100644 index 00000000..d548e490 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_anguishes_over_http.md @@ -0,0 +1,50 @@ +# 😱 Status quo stories: Barbara Anguishes Over HTTP + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect people's experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +Barbara is starting a new project, working together with Alan. They want to write a Rust library and as part of it they will need to make a few HTTP calls to various web services. While HTTP is part of the responsibilities of the library it is by no means the only thing the library will need to do. + +As they are pair programming, they get the part of the library where HTTP will be involved and Alan asks Barbara, "OK, how do I make an HTTP request?". + +As an experienced async Rust developer Barbara has been dreading this question from the start of the project. She's tempted to ask "How long do you have?", but she quickly gathers herself and starts to outline the various considerations. She starts with a relatively simple question: "Should we use an HTTP library with a sync interface or an async interface?". + +Alan, who comes from a JavaScript background, remembers the transition from callbacks to async/await in that language. He assumes Rust is merely making its transition to async/await, and it will eventually be the always preferred choice. He hesitates and asks Barbara: "Isn't async/await always better?". Barbara, who can think of many scenarios where a blocking, sync interface would likely be better, weighs whether going done the rabbit-hole of async vs sync is the right way to spend their time. She decides instead to try to directly get at the question of whether they should use async for this particular project. She knows that bridging sync and async can be difficult, and so there's another question they need to answer first: "Are we going to expose a sync or an async interface to the users of our library?". + +Alan, still confused about when using a sync interface is the right choice, replies as confident as he can: "Everybody wants to use async these days. Let's do that!". He braces for Barbara's answer as he's not even sure what he said is actually true. + +Barbara replies, "If we expose an async API then we need to decide which async HTTP implementation we will use". As she finishes saying this, Barbara feels slightly uneasy. She knows that it is possible to use a sync HTTP library and expose it through an async API, but she fears totally confusing Alan and so decides to not mention this fact. + +Barbara looks over at Alan and sees a blank stare on his face. She repeats the question: "So, which async HTTP implementation should we use?". Alan responds with the only thing that comes to his mind: "which one is the best?" to which Barbara responds "Well, it depends on which async runtime you're using". + +Alan, feeling utterly dejected and hoping that the considerations will soon end tries a new route out of this conversation: "Can we allow the user of the library to decide?". + +Barbara thinks to herself, "Oh boy, we could provide a trait that abstracts over the HTTP request and response and allow the user to provide the implementation for whatever HTTP library they want... BUT, if we ever need any additional functionality that an async runtime needs to expose - like async locks or async timers - we might be forced to pick an actual runtime implementation on behalf of the user... Perhaps, we can put the most popular runtime implementations behind feature flags and let the user chose that way... BUT what if we want to allow plugging in of different runtimes?" + +Alan, having watched Barbara stare off into the distance for what felt like a half-hour, feels bad for his colleague. All he can think to himself is how Rust is so much more complicated that C#. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +* What is a very mundane and simple decision in many other languages, picking an HTTP library, requires users to contemplate *many* different considerations. +* There is no practical way to choose an HTTP library that will serve most of the ecosystem. Sync/Async, competing runtimes, etc. - someone will always be left out. +* HTTP is a small implementation detail of this library, but it is a HUGE decision that will ultimately be the biggest factor in who can adopt their library. + +### **What are the sources for this story?** +Based on the author's personal experience of taking newcomers to Rust through the decision making process of picking an HTTP implementation for a library. + +### **Why did you choose [Barbara][] to tell this story?** +Barbara knows all the considerations and their consequences. A less experienced Rust developer might just make a choice even if that choice isn't the right one for them. + +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.md b/src/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.md new file mode 100644 index 00000000..fe484fc0 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.md @@ -0,0 +1,218 @@ +# 😱 Status quo stories: Barbara battles buffered streams + +[How To Vision: Status Quo]: ../status_quo.md +[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async/master/src/vision/status_quo/template.md +[`status_quo`]: https://github.com/rust-lang/wg-async/tree/master/src/vision/status_quo +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md +[open issues]: https://github.com/rust-lang/wg-async/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas +[open an issue of your own]: https://github.com/rust-lang/wg-async/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +### Mysterious timeouts + +Barbara is working on her [YouBuy] server and is puzzling over a strange bug report. She is encountering users reporting that their browser connection is timing out when they connect to [YouBuy]. Based on the logs, she can see that they are timing out in the `do_select` function: + +```rust +async fn do_select(database: &Database, query: Query) -> Result> { + let conn = database.get_conn().await?; + conn.select_query(query).await +} +``` +This is surprising, because `do_select` doesn't do much - it does a database query to claim a work item from a queue, but isn't expected to handle a lot of data or hit extreme slowdown on the database side. +Some of the time, there is some kind of massive delay in between the `get_conn` method opening a connection and the call to `select_query`. But why? She has metrics that show that the CPU is largely idle, so it's not like the cores are all occupied. + +She looks at the caller of `do_select`, which is a function `do_work`: + +```rust +async fn do_work(database: &Database) { + let work = do_select(database, FIND_WORK_QUERY)?; + stream::iter(work) + .map(|item| do_select(database, work_from_item(item))) + .buffered(5) + .for_each(|work_item| process_work_item(database, work_item)) + .await; +} + +async fn process_work_item(...) { } +``` + +The `do_work` function is invoking `do_select` as part of a stream; it is buffering up a certain number of `do_select` instances and, for each one, invoking `process_work_item`. Everything seems to be in order, and she can see that calls to `process_work_item` are completing in the logs. + +Following a hunch, she adds more logging in and around the `process_work_item` function and waits a few days to accumulate new logs. She notices that shortly after each time out, there is always a log of a `process_work_item` call that takes at least 20 seconds. These calls are not related to the connections that time out, they are for other connections, but they always appear afterwards in time. + +`process_work_item` is expected to be slow sometimes because it can end up handling large items, so this is not immediately surprising to Barbara. She is, however, surprised by the correlation - surely the executor ensures that `process_work_item` can't stop `do_select` from doing its job? + +### Barbara thought she understood how async worked + +Barbara thought she understood futures fairly well. She thought of `async fn` as basically "like a synchronous function with more advanced control flow". She knew that Rust's futures were lazy -- that they didn't start executing until they were awaited -- and she knew that could compose them using utilities like [`join`](https://docs.rs/futures/0.3/futures/future/fn.join.html), [`FuturesUnordered`], or the [`buffered`](https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.buffered) method (as in this example). + +[`FuturesUnordered`]: https://docs.rs/futures/0.3.14/futures/stream/struct.FuturesUnordered.html + +Barbara also knows that every future winds up associated with a task, and that if you have multiple futures on the same task (in this case, the futures in the stream, for example) then they would run concurrently, but not in parallel. Based on this, she thinks perhaps that `process_work_item` is a CPU hog that takes too long to complete, and so she needs to add a call to `spawn_blocking`. But when she looks more closely, she realizes that `process_work_item` is an async function, and those 20 seconds that it spends executing are mostly spent waiting on I/O. Huh, that's confusing, because the task ought to be able to execute other futures in that case -- so why are her connections stalling out without making progress? + +### Barbara goes deep into how poll works + +She goes to read the Rust async book and tries to think about the model, but she can't quite see the problem. Then she asks on the rust-lang Discord and someone explains to her what is going on, with the catchphrase "remember, `async` is about waiting in parallel, not working in parallel". Finally, after reading over what they wrote a few times, and reading some chapters in the async book, she sees the problem. + +It turns out that, to Rust, a task is kind of a black box with a "poll" function. When the executor thinks a task can make progress, it calls poll. The task itself then delegates this call to poll down to all the other futures that are composed together. In the case of her buffered stream of connections, the stream gets woken up and it would then delegate down the various buffered items in its list. + +When it executes `Stream::for_each`, the task is doing something like this: + +```rust +while let Some(work_item) = stream.next().await { + process_work_item(database, work_item).await; +} +``` + +The task can only "wait" on one "await" at a time. It will execute that await until it completes and only then move on to the rest of the function. When the task is blocked on the first `await`, it will process all the futures that are part of the stream, and hence the various buffered connections all make progress. + +But once a work item is produced, the task will block on the *second* `await` -- the one that resulted from `process_work_item`. This means that, until `process_work_item` completes, control will *never return to the first `await`*. As a result, none of the futures in the stream will make progress, even if they could do so! + +### The fix + +Once Barbara understands the problem, she considers the fix. The most obvious fix is to spawn out tasks for the `do_select` calls, like so: + +```rust +async fn do_work(database: &Database) { + let work = do_select(database, FIND_WORK_QUERY)?; + stream::iter(work) + .map(|item| task::spawn(do_select(database, work_from_item(item)))) + .buffered(5) + .for_each(|work_item| process_work_item(database, work_item)) + .await; +} +``` + +Spawning a task will allow the runtime to keep moving those tasks along independently of the `do_work` task. Unfortunately, this change results in a compilation error: + +``` +error[E0759]: `database` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement + --> src/main.rs:8:18 + | +8 | async fn do_work(database: &Database) { + | ^^^^^^^^ --------- this data with an anonymous lifetime `'_`... + | | + | ...is captured here... + | .map(|item| task::spawn(do_select(database, work_from_item(item)))) + | ----------- ...and is required to live as long as `'static` here +``` + +"Ah, right," she says, "spawned tasks can't use borrowed data. I wish I had [rayon] or the scoped threads from [crossbeam]." + +"Let me see," Barbara thinks. "What else could I do?" She has the idea that she doesn't have to process the work items immediately. She could buffer up the work into a [`FuturesUnordered`] and process it after everything is ready: + +```rust +async fn do_work(database: &Database) { + let work = do_select(database, FIND_WORK_QUERY)?; + let mut results = FuturesUnordered::new(); + stream::iter(work) + .map(|item| do_select(database, work_from_item(item))) + .buffered(5) + .for_each(|work_item| { + results.push(process_work_item(database, work_item)); + futures::future::ready(()) + }) + .await; + + while let Some(_) = results.next().await { } +} +``` + +This changes the behavior of her program quite a bit though. The original goal was to have at most 5 `do_select` calls occurring concurrently with exactly one `process_work_item`, but now she has all of the `process_work_item` calls executing at once. Nonetheless, the hack solves her immediate problem. Buffering up work into a `FuturesUnordered` becomes a kind of "fallback" for those cases where can't readily insert a `task::spawn`. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +* Rust's future model is a 'leaky abstraction' that works quite differently from futures in other languages. It is prone to some subtle bugs that require a relatively deep understanding of its inner works to understand and fix. +* "Nested awaits" -- where the task blocks on an inner await while there remains other futures that are still awaiting results -- are easy to do but can cause a lot of trouble. +* Lack of scoped futures makes it hard to spawn items into separate tasks for independent processing sometimes. + +### **What are the sources for this story?** + +This is based on the bug report [Footgun with Future Unordered](https://github.com/rust-lang/futures-rs/issues/2387) but the solution that Barbara came up with is something that was relayed by [farnz](https://github.com/farnz) vision doc writing session. [farnz] mentioned at the time that this pattern was frequently used in their codebase to work around this sort of hazard. + +### **Why did you choose Barbara to tell this story?** + +To illustrate that knowing Rust -- and even having a decent handle on async Rust's basic model -- is not enough to make it clear what is going on in this particular case. + +### **How would this story have played out differently for the other characters?** + +Woe be unto them! Identifying and fixing this bug required a lot of fluency with Rust and the async model. Alan in particular was probably relying on his understanding of async-await from other languages, which works very differently. In those languages, every async function is enqueued automatically for independent execution, so hazards like this do not arise (though this comes at a performance cost). + +### Besides timeouts for clients, what else could go wrong? + +The original bug report mentioned the possibility of deadlock: + +> When using an async friendly semaphore (like Tokio provides), you can deadlock yourself by having the tasks that are waiting in the `FuturesUnordered` owning all the semaphores, while having an item in a `.for_each()` block after `buffer_unordered()` requiring a semaphore. + +### Is there any way for Barbara to both produce and process work items simultaneously? + +Yes, in this case, she could've. For example, she might have written + +```rust +async fn do_work(database: &Database) { + let work = do_select(database, FIND_WORK_QUERY).await?; + + stream::iter(work) + .map(|item| async move { + let work_item = do_select(database, work_from_item(item)).await; + process_work_item(database, work_item).await; + }) + .buffered(5) + .for_each(|()| std::future::ready(())) + .await; +} +``` + +This would however mean that she would have 5 calls to `process_work_item` executing at once. In the actual case that inspired this story, `process_work_item` can take as much as 10 GB of RAM, so having multiple concurrent calls is a problem. + +### Is there any way for Barbara to both produce and process work items simultaneously, without the buffering and so forth? + +Yes, she might use a loop with a `select!`. This would ensure that she is processing *both* the stream that produces work items and the [`FuturesUnordered`] that consumes them: + +```rust +async fn do_work(database: &Database) { + let work = do_select(database, FIND_WORK_QUERY).await?; + + let selects = stream::iter(work) + .map(|item| do_select(database, work_from_item(item))) + .buffered(5) + .fuse(); + tokio::pin!(selects); + + let mut results = FuturesUnordered::new(); + + loop { + tokio::select! { + Some(work_item) = selects.next() => { + results.push(process_work_item(database, work_item)); + }, + Some(()) = results.next() => { /* do nothing */ }, + else => break, + } + } +} +``` + +Note that doing so is producing code that looks quite a bit different than where she started, though. :( This also behaves very differently. There can be a queue of tens of thousands of items that `do_select` grabs from, and this code will potentially pull far too many items out of the queue, which then would have to be requeued on shutdown. The intent of the `buffered(5)` call was to grab 5 work items from the queue at most, so that other hosts could pull out work items and share the load when there's a spike. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[YouBuy]: ../../projects/YouBuy.md +[farnz]: https://github.com/farnz + + diff --git a/src/vision/submitted_stories/status_quo/barbara_benchmarks_async_trait.md b/src/vision/submitted_stories/status_quo/barbara_benchmarks_async_trait.md new file mode 100644 index 00000000..e9353024 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_benchmarks_async_trait.md @@ -0,0 +1,134 @@ +# Barbara begets backpressure and benchmarks async_trait + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +*Write your story here! Feel free to add subsections, citations, links, code examples, whatever you think is best.* + +Barbara is implementing the network stack for an experimental new operating system in Rust. She loves Rust's combination of performance, expressiveness, and safety. She and her team set off implementing the network protocols, using traits to separate protocol layers, break up the work, and make them testable. + +Unlike most operating systems, this network stack is designed to live in a _separate process_ from the driver itself. Barbara eventually realizes a problem: this system architecture will require modeling backpressure explicitly when sending outbound packets. + +She starts looking into how to model backpressure without having to rewrite all of her team's code. +She realizes that async is actually the perfect model for expressing backpressure implicitly. By using async, she can keep most of her code without explicitly propagating backpressure information. + +When she sets off to implement this, Barbara quickly realizes async won't work off the shelf because of the lack of support for `async fn` in traits. + +Barbara is stuck. She has a large codebase that she would like to convert to using async, but core features of the language she was using are not available with async. She starts looking for workarounds. + +Barbara begins by writing out requirements for her use case. She needs to + +- Continue using trait abstractions for core protocol implementations +- Take advantage of the backpressure model implied by async +- Maintain performance target of at most 4 µs per packet on underpowered hardware + +The last requirement is important for sustaining gigabit speeds, a key goal of the network stack and one reason why Rust was chosen. + +Barbara thinks about writing down the name of each Future type, but realizes that this wouldn't work with the `async` keyword. Using Future combinators directly would be extremely verbose and painful. + +Barbara finds the `async_trait` crate. Given her performance constraints, she is wary of the allocations and dynamic dispatch introduced by the crate. + +She decides to write a benchmark to simulate the performance impact of `async_trait` compared to a future where `async fn` is fully supported in traits. Looking at the `async_trait` documentation, she sees that it desugars code like + +```rust +#[async_trait] +impl Trait for Foo { + async fn run(&self) { + // ... + } +} +``` + +to + +```rust +impl Trait for Foo { + fn run<'a>( + &'a self, + ) -> Pin + Send + 'a>> + where + Self: Sync + 'a, + { + async fn run(_self: &Foo) { + // original body + } + Box::pin(run(self)) + } +} +``` + +The benchmark Barbara uses constructs a tree of Futures 5 levels deep, using both `async` blocks and a manual desugaring similar to above. She runs the benchmark on hardware that is representative for her use case and finds that while executing a single native async future takes 639 ns, the manual desugaring using `boxed` takes 1.82 µs. + +Barbara sees that in a real codebase, this performance would not be good enough for writing a network stack capable of sustaining gigabit-level throughput on underpowered hardware. Barbara is disappointed, but knows that support for `async fn` in traits is in the works. + +Barbara looks at her organization's current priorities and decides that 100's of mbps will be an acceptable level of performance for the near term. She decides to adopt `async_trait` with the expectation that the performance penalty will go away in the long term. + +## 🤔 Frequently Asked Questions + +*Here are some standard FAQ to get you started. Feel free to add more!* + +### **What are the morals of the story?** +*Talk about the major takeaways-- what do you see as the biggest problems.* + +* Language features that don't work well together can be a major roadblock in the course of development. Developers expect all of a language's features to be at their disposal, not using one to cut them off from using another. +* Allocation and dynamic dispatch aren't acceptable runtime performance costs for all use cases. + +### **What are the sources for this story?** +*Talk about what the story is based on, ideally with links to blog posts, tweets, or other evidence.* + +This story is based on actual experience implementing the 3rd-generation network stack for the Fuchsia operating system. + +The benchmarks are [implemented here](https://fxrev.dev/528302). + +### **Why do you need to model backpressure?** + +The Linux network stack doesn't do this; instead it drops packets as hardware buffers fill up. + +Because our network stack lives in a separate process from the driver, paying attention to hardware queue depth directly is not an option. There is a communication channel of bounded depth between the network stack and the driver. Dropping packets when this channel fills up would result in an unacceptable level of packet loss. Instead, the network stack must "push" this backpressure up to the applications using the network. This means each layer of the system has to be aware of backpressure. + +### **How would you solve this in other systems languages?** + +In C++ we would probably model this using callbacks which are passed all the way down the stack (through each leayer of the system). + +### **What's nice about async when modelling backpressure?** + +Futures present a uniform mechanism for communicating backpressure through polling. When requests stack up but their handler futures are not being polled, this indicates backpressure. Using this model means we get backpressure "for free" by simply adding `async` and `.await` to our code, at least in theory. + +Async is a viral concern in a codebase, but so is backpressure. You can't have a backpressure aware system when one layer of that system isn't made aware of backpressure in some way. So in this case it's actually _helpful_ that there's not an easy way to call an async fn from a sync fn; if there were, we might accidentally "break the chain" of backpressure awareness. + +### **What was the benchmarking methodology?** + +A macro was used to generate 512 slightly different versions of the same code, to defeat the branch predictor. Each version varied slightly to prevent LLVM from merging duplicate code. + +The leaf futures in the benchmark always returned `Poll::Ready`. The call depth was always 5 async functions deep. + +### **Did you learn anything else from the benchmarks?** + +In one of the benchmarks we compared the `async fn` version to the equivalent synchronous code. This helps us see the impact of the state machine transformation on performance. + +The results: synchronous code took 311.39 ns while the `async fn` code took 433.40 ns. + +### **Why did you choose Barbara to tell this story?** +*Talk about the character you used for the story and why.* + +The implementation work in this story was done by [@joshlf], an experienced Rust developer who was new to async. + +### **How would this story have played out differently for the other characters?** +*In some cases, there are problems that only occur for people from specific backgrounds, or which play out differently. This question can be used to highlight that.* + +Alan might not have done the benchmarking up front, leading to a surprise later on when the performance wasn't up to par with Rust's promise. Grace might have decided to implement async state machines manually, giving up on the expressiveness of async. + +[@joshlf]: https://github.com/joshlf +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/barbara_bridges_sync_and_async.md b/src/vision/submitted_stories/status_quo/barbara_bridges_sync_and_async.md new file mode 100644 index 00000000..28d01ebf --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_bridges_sync_and_async.md @@ -0,0 +1,335 @@ +# 😱 Status quo stories: Barbara bridges sync and async in `perf.rust-lang.org` + +[How To Vision: Status Quo]: ../status_quo.md +[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async/master/src/vision/status_quo/template.md +[`status_quo`]: https://github.com/rust-lang/wg-async/tree/master/src/vision/status_quo +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md +[open issues]: https://github.com/rust-lang/wg-async/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas +[open an issue of your own]: https://github.com/rust-lang/wg-async/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + + +Barbara is working on the code for [perf.rust-lang.org] and she wants to do a web request to load various intermediate results. She has heard that the `reqwest` crate is quite nice, so she decides to give it a try. She writes up an async function that does her web request: + +[perf.rust-lang.org]: https://perf.rust-lang.org/ + +```rust +async fn do_web_request(url: &Url) -> Data { + ... +} +``` + +She needs to apply this async function to a number of urls. She wants to use the iterator map function, like so: + +```rust +async fn do_web_request(url: &Url) -> Data {...} + +fn aggregate(urls: &[Url]) -> Vec { + urls + .iter() + .map(|url| do_web_request(url)) + .collect() +} + +fn main() { + /* do stuff */ + let data = aggregate(); + /* do more stuff */ +} +``` + +Of course, since `do_web_request` is an async fn, she gets a type error from the compiler: + +``` +error[E0277]: a value of type `Vec` cannot be built from an iterator over elements of type `impl Future` + --> src/main.rs:11:14 + | +11 | .collect(); + | ^^^^^^^ value of type `Vec` cannot be built from `std::iter::Iterator` + | + = help: the trait `FromIterator` is not implemented for `Vec` +``` + +"Of course," she thinks, "I can't call an async function from a closure." + +### Introducing `block_on` + +She decides that since she is not overly concerned about performance, so she decides she'll just use a call to [`block_on` from the `futures` crate](https://docs.rs/futures/0.3.14/futures/executor/fn.block_on.html) and execute the function synchronously: + +```rust +async fn do_web_request(url: &Url) -> Data {...} + +fn aggregate(urls: &[Url]) -> Vec { + urls + .iter() + .map(|url| futures::executor::block_on(do_web_request(url))) + .collect() +} + +fn main() { + /* do stuff */ + let data = aggregate(); + /* do more stuff */ +} +``` + +The code compiles, and it seems to work. + +### Switching to async main + +As Barbara works on [perf.rust-lang.org], she realizes that she needs to do more and more async operations. She decides to convert her synchronous `main` function into an `async main`. She's using tokio, so she is able to do this very conveniently with the `#[tokio::main]` decorator: + +```rust +#[tokio::main] +async fn main() { + /* do stuff */ + let data = aggregate(); + /* do more stuff */ +} +``` + +Everything seems to work ok on her laptop, but when she pushes the code to production, it deadlocks immediately. "What's this?" she says. Confused, she runs the code on her laptop a few more times, but it seems to work fine. (There's a faq explaining what's going on. -ed.) + +She decides to try debugging. She fires up a debugger but finds it is isn't really giving her useful information about what is stuck (she has [basically the same problems that Alan has](https://rust-lang.github.io/wg-async/vision/status_quo/alan_tries_to_debug_a_hang.html)). [She wishes she could get insight into tokio's state.](https://rust-lang.github.io/wg-async/vision/status_quo/barbara_wants_async_insights.html) + +Frustrated, she starts reading the tokio docs more closely and she realizes that `tokio` runtimes offer their own `block_on` method. "Maybe using tokio's `block_on` will help?" she thinks, "Worth a try, anyway." She changes the `aggregate` function to use tokio's `block_on`: + +```rust +fn block_on(f: impl Future) -> O { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(f) +} + +fn aggregate(urls: &[Url]) -> Vec { + urls + .iter() + .map(|url| block_on(do_web_request(url))) + .collect() +} +``` + +The good news is that the deadlock is gone. The bad news is that now she is getting a panic: + +> thread 'main' panicked at 'Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks.' + +"Well," she thinks, "I could use the `Handle` API to get the current runtime instead of creating a new one? Maybe that's the problem." + +```rust +fn aggregate(urls: &[&str]) -> Vec { + let handle = tokio::runtime::Handle::current(); + urls.iter() + .map(|url| handle.block_on(do_web_request(url))) + .collect() +} +``` + +But this also seems to panic in the same way. + +### Trying out `spawn_blocking` + +Reading more into this problem, she realizes she is supposed to be using `spawn_blocking`. She tries replacing `block_on` with `tokio::task::spawn_blocking`: + +```rust +fn aggregate(urls: &[Url]) -> Vec { + urls + .iter() + .map(|url| tokio::task::spawn_blocking(move || do_web_request(url))) + .collect() +} +``` + + +but now she gets a type error again: + +``` +error[E0277]: a value of type `Vec` cannot be built from an iterator over elements of type `tokio::task::JoinHandle` + --> src/main.rs:22:14 + | +22 | .collect(); + | ^^^^^^^ value of type `Vec` cannot be built from `std::iter::Iterator>` + | + = help: the trait `FromIterator>` is not implemented for `Vec` +``` + +Of course! `spawn_blocking`, like `map`, just takes a regular closure, not an async closure; it's purpose is to embed some sync code within an async task, so a sync closure makes sense -- and moreover async closures aren't stable -- but it's all rather frustrating nonetheless. "Well," she thinks, "I can use `spawn` to get back into an async context!" So she adds a call to `spawn` inside the `spawn_blocking` closure: + +```rust +fn aggregate(urls: &[Url]) -> Vec { + urls + .iter() + .map(|url| tokio::task::spawn_blocking(move || { + tokio::task::spawn(async move { + do_web_request(url).await + }) + })) + .collect() +} +``` + +But this isn't really helping, as `spawn` still yields a future. She's getting the same errors. + +### Trying out `join_all` + +She remembers now that this whole drama started because she was converting her `main` function to be `async`. Maybe she doesn't have to bridge between sync and async? She starts digging around in the docs and finds `futures::join_all`. Using that, she can change `aggregate` to be an async function too: + +```rust +async fn aggregate(urls: &[Url]) -> Vec { + futures::join_all( + urls + .iter() + .map(|url| do_web_request(url)) + ).await +} +``` + +Things are working again now, so she is happy, although she notes that `join_all` has quadratic time complexity. That's not great. + +### Filtering + +Later on, she would like to apply a filter to the aggregation operation. She realizes that if she wants to use the fetched data when doing the filtering, she has to filter the vector after the join has completed. She wants to write something like + +```rust +async fn aggregate(urls: &[Url]) -> Vec { + futures::join_all( + urls + .iter() + .map(|url| do_web_request(url)) + .filter(|data| test(data)) + ).await +} +``` + +but she can't, because `data` is a future and not the `Data` itself. Instead she has to build the vector first and then post-process it: + +```rust +async fn aggregate(urls: &[Url]) -> Vec { + let mut data: Vec = futures::join_all( + urls + .iter() + .map(|url| do_web_request(url)) + ).await; + data.retain(test); + data +} +``` + +This is annoying, but performance isn't critical, so it's ok. + +### And the cycle begins again + +Later on, she wants to call `aggregate` from another binary. This one doesn't have an `async main`. This context is deep inside of an iterator chain and was previously entirely synchronous. She realizes it would be a lot of work to change all the intervening stack frames to be `async fn`, rewrite the iterators into streams, etc. She decides to just call `block_on` again, even though it make her nervous. + +## 🤔 Frequently Asked Questions + +### What are the morals of the story? + +* Some projects don't care about max performance and just want things to work once the program compiles. + * They would probably be happy with sync but as the most popular libraries for web requests, databases, etc, offer async interfaces, they may still be using async code. +* There are contexts where you can't easily add an `await`. + * For example, inside of an iterator chain. + * Big block of existing code. +* Mixing sync and async code can cause deadlocks that are really painful to diagnose, particularly when you have an async-sync-async sandwich. + +### Why did you choose Barbara to tell this story? + +* Because Mark (who experienced most of it) is a very experienced Rust developer. +* Because you could experience this story regardless of language background or being new to Rust. + +### How would this story have played out differently for the other characters? + +I would expect it would work out fairly similarly, except that the type errors and things might well have been more challenging for people to figure out, assuming they aren't already familiar with Rust. + +### Why did Barbara only get deadlocks in production, and not on her laptop? + +This is because the production instance she was using had only a single core, but her laptop is a multicore machine. The actual cause of the deadlocks is that `block_on` basically "takes over" the tokio worker thread, and hence the tokio scheduler cannot run. If that `block_on` is blocked on another future that will have to execute, then some other thread must take over of completing that future. On Barbara's multicore machine, there were more threads available, so the system did not deadlock. But on the production instance, there was only a single thread. Barbara could have encountered deadlocks on her local machine as well if she had enough instances of `block_on` running at once. + +### Could the runtime have prevented the deadlock? + +One way to resolve this problem would be to have a runtime that creates more threads as needed. This is what was proposed [in this blog post](https://async.rs/blog/stop-worrying-about-blocking-the-new-async-std-runtime/), for example. + +Adapting the number of worker threads has downsides. It requires knowing the right threshold for creating new threads (which is fundamentally unknowable). The result is that the runtime will sometimes observe that some thread seems to be taking a long time and create new threads *just before* that thread was about to finish. These new threads generate overhead and lower the overall performance. It also requires work stealing and other techniques that can lead to work running on mulitple cores and having less locality. Systems tuned for maximal performance tend to prefer a single thread per core for this reason. + +If some runtimes are adaptive, that may also lead to people writing libraries which block without caring. These libraries would then be a performance or deadlock hazard when used on a runtime that is not adaptive. + +### Is there any way to have kept `aggregate` as a synchronous function? + +Yes, Barbara could have written something like this: + +```rust +fn aggregate(urls: &[Url]) -> Vec { + let handle = Handle::current(); + + urls.iter() + .map(|url| handle.block_on(do_web_request(url))) + .collect() +} + +#[tokio::main] +async fn main() { + let data = task::spawn_blocking(move || aggregate(&[Url, Url])) + .await + .unwrap(); + println!("done"); +} +``` + +This `aggregate` function can only safely be invoked from inside a tokio `spawn_blocking` call, however, since `Handle::current` will only work in that context. She could also have used the original futures variant of `block_on`, in that case, and things would also work. + +### Why didn't Barbara just use the sync API for reqwest? + +reqwest does offer a synchronous API, but it's not enabled by default, you have to use an optional feature. Further, not all crates offer synchronous APIs. Finally, Barbara has had some vague poor experience when using synchronous APIs, such as panics, and so she's learned the heuristic of "use the async API unless you're doing something really, really simple". + +Regardless, the synchronous reqwest API is actually itself implemented using `block_on`: so Barbara would have ultimately hit the same issues. Further, not all crates offer synchronous APIs -- some offer only async APIs. In fact, these same issues are probably the sources of those panics that Barbara encountered in the past. + +In general, though, embedded sync within async or vice versa works "ok", once you know the right tricks. Where things become challenging is when you have a "sandwich", with async-sync-async. + +### Do people mix `spawn_blocking` and `spawn` successfully in real code? + +Yes! Here is [some code from perf.rust-lang.org][egsb] doing exactly that. The catch is that it winds up giving you a future in the end, which didn't work for Barbara because her code is embedded within an iterator (and hence she can't make things async "all the way down"). + +[egsb]: https://github.com/rust-lang/rustc-perf/blob/3651aa744ab2a22e1adf96a698851165086ad835/site/src/main.rs#L35-L36 + +### What are other ways people could experience similar problems mixing sync and async? + +* Using `std::Mutex` in async code. +* Calling the blocking version of an asynchronous API. + * For example, `reqwest::blocking`, the synchronous [`zbus`](https://gitlab.freedesktop.org/dbus/zbus/-/blob/main/zbus/src/proxy.rs#L121) and [`rumqtt`](https://github.com/bytebeamio/rumqtt/blob/8de24cbc0484f459246251873aa6c80be8b6e85f/rumqttc/src/client.rs#L224) APIs. + * These are commonly implemented by using some variant of `block_on` internally. + * Therefore they can lead to panics or deadlocks depending on what async runtime they are built from and used with. + +### Why wouldn't Barbara just make everything async from the start? + +There are times when converting synchronous code to async is difficult or even impossible. Here are some of the reasons: + +* [Asynchronous functions cannot appear in trait impls][trait]. +* Asynchronous functions cannot be called from APIs that take closures for callbacks, like `Iterator::map` in this example. +* Sometimes the synchronous functions come from other crates and are not fully under their control. +* It's just a lot of work! + +[trait]: ./alan_needs_async_in_traits.md + +### How many variants of `block_on` are there? + +* the `futures` crate offers a runtime-independent block-on (which can lead to deadlocks, as in this story) +* the `tokio` crate offers a `block_on` method (which will panic if used inside of another tokio runtime, as in this story) +* the [`pollster`](https://crates.io/crates/pollster) crate exists just to offer `block_on` +* the [`futures-lite`](https://docs.rs/futures-lite/1.11.3/futures_lite/future/fn.block_on.html) crate offers a `block_on` +* the [`aysnc-std`](https://docs.rs/async-std/1.9.0/async_std/task/fn.block_on.html) crate offers `block_on` +* the [`async-io`](https://docs.rs/async-std/1.9.0/async_std/task/fn.block_on.html) crate offers `block_on` +* ...there are probably more, but I think you get the point. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/barbara_builds_an_async_executor.md b/src/vision/submitted_stories/status_quo/barbara_builds_an_async_executor.md new file mode 100644 index 00000000..5f3967c7 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_builds_an_async_executor.md @@ -0,0 +1,172 @@ +# 😱 Status quo stories: Barbara builds an async executor + + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +## The story + +Barbara wants to set priorities to the tasks spawned to the executor. However, she finds no existing async executor provides such a feature +She would be more than happy to enhance an existing executor and even intends to do so at some point. At the same time, Barbara understand that the process of getting +changes merged officially into an executor can be long, and for good reason. + +Due to pressure and deadlines at work she needs a first version to be working as soon as possible. She then decides to build her own async executor. + +First, Barbara found [crossbeam-deque](https://crates.io/crates/crossbeam-deque) provides work-stealing deques of good quality. She decides to use it to build task schedulers. She plans for each working thread to have a loop which repeatedly gets a task from the deque and polls it. + +But wait, what should we put into those queues to represent each "task"? + +At first, Barbara thought it must contain the `Future` itself and the additional priority which was used by the scheduler. So she first wrote: + +```rust +pub struct Task { + future: Pin + Send + 'static>>, + priority: u8 +} +``` + +And the working thread loop should run something like: + +```rust +pub fn poll_task(task: Task) { + let waker = todo!(); + let mut cx = Context::from_waker(&waker); + task.future.as_mut().poll(&mut cx); +} +``` + +"How do I create a waker?" Barbara asked herself. Quickly, she found the `Wake` trait. Seeing the `wake` method takes an `Arc`, she realized the task in the scheduler should be stored in an `Arc`. After some thought, she realizes it makes sense because both the deque in the scheduler and the waker may hold a reference to the task. + +To implement `Wake`, the `Task` should contain the sender of the scheduler. She changed the code to something like this: + +```rust +pub struct Task { + future: Pin + Send + 'static>>, + scheduler: SchedulerSender, + priority: u8, +} + +unsafe impl Sync for Task {} + +impl Wake for Task { + fn wake(self: Arc) { + self.scheduler.send(self.clone()); + } +} + +pub fn poll_task(task: Arc) { + let waker = Waker::from(task.clone()); + let mut cx = Context::from_waker(&waker); + task.future.as_mut().poll(&mut cx); +// ^^^^^^^^^^^ cannot borrow as mutable +} +``` + +The code still needed some change because the `future` in the `Arc` became immutable. + +"Okay. I can guarantee `Task` is created from a `Pin>`, and I think the same future won't be polled concurrently in two threads. So let me bypass the safety checks." Barbara changed the future to a raw pointer and confidently used some `unsafe` blocks to make it compile. + +```rust +pub struct Task { + future: *mut (dyn Future + Send + 'static), + ... +} + +unsafe impl Send for Task {} +unsafe impl Sync for Task {} + +pub fn poll_task(task: Arc) { + ... + unsafe { + Pin::new_unchecked(&mut *task.future).poll(&mut cx); + } +} +``` + +Luckily, a colleague of Barbara noticed something wrong. The `wake` method could be called multiple times so multiple copies of the task could exist in the scheduler. The scheduler might not work correctly because of this. What's worse, a more severe problem was that multiple threads might get copies of the same task from the scheduler and cause a race in polling the future. + +Barbara soon got a idea to solve it. She added a state field to the `Task`. By carefully maintaining the state of the task, she could guarantee there are no duplicate tasks in the scheduler and no race can happen when polling the future. + +```rust +const NOTIFIED: u64 = 1; +const IDLE: u64 = 2; +const POLLING: u64 = 3; +const COMPLETED: u64 = 4; + +pub struct Task { + ... + state: AtomicU64, +} + +impl Wake for Task { + fn wake(self: Arc) { + let mut state = self.state.load(Relaxed); + loop { + match state { + // To prevent a task from appearing in the scheduler twice, only send the task + // to the scheduler if the task is not notified nor being polling. + IDLE => match self + .state + .compare_exchange_weak(IDLE, NOTIFIED, AcqRel, Acquire) + { + Ok(_) => self.scheduler.send(self.clone()), + Err(s) => state = s, + }, + POLLING => match self + .state + .compare_exchange_weak(POLLING, NOTIFIED, AcqRel, Acquire) + { + Ok(_) => break, + Err(s) => state = s, + }, + _ => break, + } + } + } +} + +pub fn poll_task(task: Arc) { + let waker = Waker::from(task.clone()); + let mut cx = Context::from_waker(&waker); + loop { + // We needn't read the task state here because the waker prevents the task from + // appearing in the scheduler twice. The state must be NOTIFIED now. + task.state.store(POLLING, Release); + if let Poll::Ready(()) = unsafe { Pin::new_unchecked(&mut *task.future).poll(&mut cx) } { + task.state.store(COMPLETED, Release); + } + match task.state.compare_exchange(POLLING, IDLE, AcqRel, Acquire) { + Ok(_) => break, + Err(NOTIFIED) => continue, + _ => unreachable!(), + } + } +} +``` + +Barbara finished her initial implementation of the async executor. Despite there were a lot more possible optimizations, Barbara already felt it is a bit complex. She was also confused about why she needed to care so much about polling and waking while her initial requirement was just adding additional information to the task for customizing scheduling. + +## 🤔 Frequently Asked Questions + +*Here are some standard FAQ to get you started. Feel free to add more!* + +### **What are the morals of the story?** + * It is difficult to customize any of the current async executors (to my knowledge). To have any bit of special requirement forces building an async executor from scratch. + * It is also not easy to build an async executor. It needs quite some exploration and is error-prone. [`async-task`](https://github.com/smol-rs/async-task) is a good attempt to simplify the process but it could not satisfy all kinds of needs of customizing the executor (it does not give you the chance to extend the task itself). +### **What are the sources for this story?** + * The story was from my own experience about writing a new thread pool supporting futures: https://github.com/tikv/yatp. + * People may feel strange about why we want to set priorities for tasks. Currently, the futures in the thread pool are like user-space threads. They are mostly CPU intensive. But I think people doing async I/O may have the same problem. +### **Why did you choose Barbara to tell this story?** + * At the time of the story, I had written Rust for years but I was new to the concepts for async/await like `Pin` and `Waker`. +### **How would this story have played out differently for the other characters?** + * People with less experience in Rust may be less likely to build their own executor. If they try, I think the story is probably similar. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/status_quo/barbara_carefully_dismisses_embedded_future.md b/src/vision/submitted_stories/status_quo/barbara_carefully_dismisses_embedded_future.md similarity index 97% rename from src/vision/status_quo/barbara_carefully_dismisses_embedded_future.md rename to src/vision/submitted_stories/status_quo/barbara_carefully_dismisses_embedded_future.md index 33cf3fcc..d58d7728 100644 --- a/src/vision/status_quo/barbara_carefully_dismisses_embedded_future.md +++ b/src/vision/submitted_stories/status_quo/barbara_carefully_dismisses_embedded_future.md @@ -36,7 +36,7 @@ kernel does not expose a Future-based interface, so Barbara has to implement `Future` by hand rather than using async/await syntax. She starts with a skeleton: -```rust,ignore +```rust /// Passes `buffer` to the kernel, and prints it to the console. Returns a /// future that returns `buffer` when the print is complete. The caller must /// call kernel_ready_for_callbacks() when it is ready for the future to return. @@ -61,7 +61,7 @@ Note: All error handling is omitted to keep things understandable. Barbara begins to implement `print_buffer`: -```rust,ignore +```rust fn print_buffer(buffer: &'static mut [u8]) -> PrintFuture { kernel_set_print_callback(callback); kernel_start_print(buffer); @@ -76,7 +76,7 @@ extern fn callback() { So far so good. Barbara then works on `poll`: -```rust,ignore +```rust fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { if kernel_is_print_done() { return Poll::Ready(kernel_get_buffer_back()); @@ -92,7 +92,7 @@ somewhere! Barbara puts the `Waker` in a global variable so the callback can find it (this is fine because the app is single threaded and callbacks do NOT interrupt execution the way Unix signals do): -```rust,ignore +```rust static mut PRINT_WAKER: Option = None; extern fn callback() { @@ -104,7 +104,7 @@ extern fn callback() { She then modifies `poll` to set `PRINT_WAKER`: -```rust,ignore +```rust fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { if kernel_is_print_done() { return Poll::Ready(kernel_get_buffer_back()); @@ -320,9 +320,9 @@ instead. Here are some ways in which these APIs are lighter weight than a signals. All kernel callbacks are invoked synchronously, using a method that is functionally equivalent to a function call. -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/barbara_compares_some_cpp_code.md b/src/vision/submitted_stories/status_quo/barbara_compares_some_cpp_code.md new file mode 100644 index 00000000..e9d86ad6 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_compares_some_cpp_code.md @@ -0,0 +1,137 @@ +# 😱 Status quo stories: Barbara compares some code (and has a performance problem) + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +Barbara is recreating some code that has been written in other languages they have some familiarity with. These include C++, but +also GC'd languages like Python. + +This code collates a large number of requests to network services, with each response containing a large amount of data. +To speed this up, Barbara uses `buffer_unordered`, and writes code like this: + +```rust +let mut queries = futures::stream::iter(...) + .map(|query| async move { + let d: Data = self.client.request(&query).await?; + d + }) + .buffer_unordered(32); + +use futures::stream::StreamExt; +let results = queries.collect::>().await; +``` + +Barbara thinks this is similar in function to things she has seen using +Python's [asyncio.wait](https://docs.python.org/3/library/asyncio-task.html#asyncio.wait), +as well as some code her coworkers have written using c++20's `coroutines`, +using [this](https://github.com/facebook/folly/blob/master/folly/experimental/coro/Collect.h#L321): + +```C++ +std::vector> tasks; + for (const auto& query : queries) { + tasks.push_back( + folly::coro::co_invoke([this, &query]() -> folly::coro::Task { + co_return co_await client_->co_request(query); + } + ) +} +auto results = co_await folly:coro::collectAllWindowed( + move(tasks), 32); +``` + +However, *the Rust code performs quite poorly compared to the other impls, +appearing to effectively complete the requests serially, despite on the surface +looking like effectively identical code.* + +While investigating, Barbara looks at `top`, and realises that her coworker's C++20 code sometimes results in her 16 core laptop using 1600% CPU; her Rust async code never exceeds 100% CPU usage. She spends time investigating her runtime setup, but Tokio is configured to use enough worker threads to keep all her CPU cores busy. This feels to her like a bug in `buffer_unordered ` or `tokio`, needing more time to investigate. + +Barbara goes deep into investigating this, spends time reading how `buffer_unordered` is +implemented, how its underlying `FuturesUnordered` is implemented, and even thinks about +how polling and the `tokio` runtime she is using works. She evens tries to figure out if the +upstream service is doing some sort of queueing. + +Eventually Barbara starts reading more about c++20 coroutines, looking closer at the folly +implementation used above, noticing that is works primarily with *tasks*, which are not exactly +equivalent to rust `Future`'s. + +Then it strikes her! `request` is implemented something like this: + +```rust +impl Client { + async fn request(&self) -> Result { + let bytes = self.inner.network_request().await? + Ok(serialization_libary::from_bytes(&bytes)?) + } +} +``` + +The results from the network service are sometimes (but not always) VERY large, and the `BufferedUnordered` stream is contained within 1 tokio task. +**The request future does non-trivial cpu work to deserialize the data. +This causes significant slowdowns in wall-time as the the process CAN BE bounded by the time it takes +the single thread running the tokio-task to deserialize all the data.** +This problem hadn't shown up in test cases, where the results from the mocked network service are always small; many common uses of the network service only ever have small results, so it takes a specific production load to trigger this issue, or a large scale test. + +The solution is to spawn tasks (note this requires `'static` futures): + +```rust +let mut queries = futures::stream::iter(...) + .map(|query| async move { + let d: Data = tokio::spawn( + self.client.request(&query)).await??; + d + }) + .buffer_unordered(32); + +use futures::stream::StreamExt; +let results = queries.collect::>().await; +``` + +Barbara was able to figure this out by reading enough and trying things out, but had that not worked, it +would have probably required figuring out how to use `perf` or some similar tool. + +Later on, Barbara gets surprised by this code again. It's now being used as part of a system that handles a very high number of requests per second, but sometimes the system stalls under load. She enlists Grace to help debug, and the two of them identify via `perf` that all the CPU cores are busy running `serialization_libary::from_bytes`. Barbara revisits this solution, and discovers `tokio::task::block_in_place` which she uses to wrap the calls to `serialization_libary::from_bytes`: +```rust +impl Client { + async fn request(&self) -> Result { + let bytes = self.inner.network_request().await? + Ok(tokio::task::block_in_place(move || serialization_libary::from_bytes(&bytes))?) + } +} +``` + +This resolves the problem as seen in production, but leads to Niklaus's code review suggesting the use of `tokio::task::spawn_blocking` inside `request`, instead of `spawn` inside `buffer_unordered`. This discussion is challenging, because the tradeoffs between `spawn` on a `Future` including `block_in_place` and `spawn_blocking` and then not spawning the containing `Future` are subtle and tricky to explain. Also, either `block_in_place` and `spawn_blocking` are heavyweight and Barbara would prefer to avoid them when the cost of serialization is low, which is usually a runtime-property of the system. + + +## 🤔 Frequently Asked Questions + +### **Are any of these actually the correct solution?** +* Only in part. It may cause other kinds of contention or blocking on the runtime. As mentioned above, the deserialization work probably needs to be wrapped in something like [`block_in_place`](https://docs.rs/tokio/1/tokio/task/fn.block_in_place.html), so that other tasks are not starved on the runtime, or might want to use [`spawn_blocking`](https://docs.rs/tokio/1/tokio/task/fn.spawn_blocking.html). There are some important caveats/details that matter: + * This is dependent on how the runtime works. + * `block_in_place` + `tokio::spawn` might be better if the caller wants to control concurrency, as spawning is heavyweight when the deserialization work happens to be small. However, as mentioned above, this can be complex to reason about, and in some cases, may be as heavyweight as `spawn_blocking` + * `spawn_blocking`, at least in some executors, cannot be cancelled, a departure from the prototypical cancellation story in async Rust. + * "Dependently blocking work" in the context of async programming is a hard problem to solve generally. https://github.com/async-rs/async-std/pull/631 was an attempt but the details are making runtime's agnostic blocking are extremely complex. + * The way this problem manifests may be subtle, and it may be specific production load that triggers it. + * The outlined solutions have tradeoffs that each only make sense for certain kind of workloads. It may be better to expose the io aspect of the request and the deserialization aspect as separate APIs, but that complicates the library's usage, lays the burden of choosing the tradeoff on the callee (which may not be generally possible). +### **What are the morals of the story?** +* Producing concurrent, performant code in Rust async is not always trivial. Debugging performance + issues can be difficult. +* Rust's async model, particularly the blocking nature of `polling`, can be complex to reason about, + and in some cases is different from other languages choices in meaningful ways. +* CPU-bound code can be easily hidden. + +### **What are the sources for this story?** +* This is a issue I personally hit while writing code required for production. + +### **Why did you choose *Barbara* to tell this story?** +That's probably the person in the cast that I am most similar to, but Alan +and to some extent Grace make sense for the story as well. + +### **How would this story have played out differently for the other characters?** +* Alan: May have taken longer to figure out. +* Grace: Likely would have been as interested in the details of how polling works. +* Niklaus: Depends on their experience. diff --git a/src/vision/submitted_stories/status_quo/barbara_gets_burned_by_select.md b/src/vision/submitted_stories/status_quo/barbara_gets_burned_by_select.md new file mode 100644 index 00000000..375b7851 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_gets_burned_by_select.md @@ -0,0 +1,84 @@ +# 😱 Status quo stories: Template + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +Barbara is working on the [YouBuy] server. In one particular part of the story, she has a process that has to load records from a database on the disk. As she receives data from the database, the data is sent into a channel for later processing. She writes an `async fn` that looks something like this: + +```rust +async fn read_send(db: &mut Database, channel: &mut Sender<...>) { + loop { + let data = read_next(db).await; + let items = parse(&data); + for item in items { + channel.send(item).await; + } + } +} +``` + +This database load has to take place while also fielding requests from the user. The routine that invokes `read_send` uses [`select!`](https://docs.rs/futures/0.3.14/futures/macro.select.html) for this purpose. It looks something like this: + +```rust +let mut db = ...; +let mut channel = ...; +loop { + futures::select! { + _ = read_send(&mut file, &mut channel) => {}, + some_data = socket.read_packet() => { + // ... + } + } +} +``` + +This setup seems to work well a lot of the time, but Barbara notices that the data getting processed is sometimes incomplete. It seems to be randomly missing some of the rows from the middle of the database, or individual items from a row. + +### Debugging + +She's not sure what could be going wrong! She starts debugging with print-outs and logging. Eventually she realizes the problem. Whenever a packet arrives on the socket, the `select!` macro will drop the other futures. This can sometime cause the `read_send` function to be canceled in between reading the data from the disk and sending the items over the channel. Ugh! + +Barbara has a hard time figuring out the best way to fix this problem. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +* Cancellation doesn't always cancel the entire task; particularly with `select!`, it sometimes cancels just a small piece of a given task. + * This is in tension with Rust's original design, which was meant to tear down an entire thread or task at once, precisely because of the challenge of writing exception-safe code. +* Cancellation in Async Rust therefore can require fine-grained recovery. + +### **What are the sources for this story?** + +This was based on [tomaka's blog post](https://tomaka.medium.com/a-look-back-at-asynchronous-rust-d54d63934a1c), which also includes a number of possible solutions, all of them quite grungy. + +### **Why did you choose Barbara to tell this story?** + +The problem described here could strike anyone, including veteran Rust users. It's a subtle interaction that is independent of source language. Also, the original person who reported it, tomaka, is a veteran Rust user. + +### **How would this story have played out differently for the other characters?** + +They would likely have a hard time diagnosing the problem. It really depends on how well they have come to understand the semantics of cancellation. This is fairly independent from programming language background; knowing non-async Rust doesn't help in particular, as this concept is specific to async code. + +### What is different between this story and other cancellation stories? + +There is already a story, ["Alan builds a cache"] that covers some of the challenges around cancellation. It is quite plausible that those stories could be combined, but the focus of this story is different. The key moral of this story is that certain combinators, notably `select!`, can cause small pieces of a single task to be torn down and canceled. This cancellation can occur for any reason -- it is not always associated with (for example) clients timing out or closing sockets. It might be (as in this story) the result of clients *sending* data! + +This is one key point that makes cancellation in async Rust rather different than panics in sync Rust. Panics in sync Rust generally occur for bugs, to start, and they are typically not meant to be recovered from except at a coarse-grained level. In contrast, as this story shows, cancellation can require fine-grained recovery and for non-bug events. + + +["Alan builds a cache"]: alan_builds_a_cache.md +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[YouBuy]: ../../projects/YouBuy.md \ No newline at end of file diff --git a/src/vision/status_quo/barbara_makes_their_first_steps_into_async.md b/src/vision/submitted_stories/status_quo/barbara_makes_their_first_steps_into_async.md similarity index 91% rename from src/vision/status_quo/barbara_makes_their_first_steps_into_async.md rename to src/vision/submitted_stories/status_quo/barbara_makes_their_first_steps_into_async.md index c8d0e426..36124cfa 100644 --- a/src/vision/status_quo/barbara_makes_their_first_steps_into_async.md +++ b/src/vision/submitted_stories/status_quo/barbara_makes_their_first_steps_into_async.md @@ -19,7 +19,7 @@ After all, a fast networked application is exactly what they are trying to make. To further solidify the decision of using async, Barbara goes looking for some information and opinions on async in Rust. Doubts created by reading some tweets about how most people should be using threads instead of async for simplicity reasons are quickly washed away by helpful conversations on the Rust discord. ### Learning about Async -Still enarmored with the first edition of the Rust book, they decide to go looking for an updated version, hoping that it will teach them async in the same manner that it taught them so much about the language and design patterns for Rust. Disappointed, they find no mention of async in the book, aside from a note that it exists as a keyword. +Still enamored with the first edition of the Rust book, they decide to go looking for an updated version, hoping that it will teach them async in the same manner that it taught them so much about the language and design patterns for Rust. Disappointed, they find no mention of async in the book, aside from a note that it exists as a keyword. Not to be deterred, they go looking further, and start looking for similarly great documentation about async. After stumbling upon the async book, their disappointment is briefly replaced with relief as the async book does a good job at solidifying what they have already learned in various blog posts about async, why one would use it and even a bit about how it all works under the hood. @@ -67,11 +67,11 @@ Like the author of this story, Barbara had previous experience with Rust. Knowin On the other hand, since Rust's async story is noticeably different from other languages, having async experience in other languages might even be harmful by requiring the user to unlearn certain habits. I don't know if this is actually the case since I don't have any experience with async in other languages. * Characters which are less in touch with Rust's community than Barbara might have had a much worse time, since just skimming over the documentation might leave some lost, and unaware of common pitfalls. On the other hand, not having learned a lot about async through blog posts and other materials, might compel someone to read the documentation more thoroughly. -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/barbara_needs_async_helpers.md b/src/vision/submitted_stories/status_quo/barbara_needs_async_helpers.md new file mode 100644 index 00000000..0123a34a --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_needs_async_helpers.md @@ -0,0 +1,221 @@ +# 😱 Status quo stories: Barbara needs Async Helpers + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +[Barbara], an experienced Rust user, is prototyping an async Rust service for work. To get things working quickly, she decides to prototype in [`tokio`], since it is unclear which runtime her work will use. + +She starts adding warp and [`tokio`] to her dependencies list. She notices that [`warp`] [suggests](https://github.com/seanmonstar/warp/#example) using [`tokio`] with the `full` feature. She's a bit concerned about how this might affect the compile times and also that *all* of tokio is needed for her little project, but she pushes forward. + +As she builds out functionality, she's pleased to see tokio provides a bunch of helpers like [`join!`](https://docs.rs/tokio/1.4.0/tokio/macro.join.html) and async versions of the standard library types like channels and mutexes. + +After completing one endpoint, she moves to a new one which requires streaming http responses to the client. Barbara quickly finds out from [`tokio`] [docs](https://docs.rs/tokio/1.4.0/tokio/stream/index.html), that it does not provide a stream type, and so she adds [`tokio-stream`] to her dependencies. + +Moving on she tries to make some functions generic over the web framework underneath, so she tries to abstract off the functionality to a trait. So she writes an async function inside a trait, just like a normal function. + +```rust +trait Client { + async fn get(); +} +``` + +Then she gets a helpful error message. + +``` +error[E0706]: functions in traits cannot be declared `async` + --> src/lib.rs:2:5 + | +2 | async fn get(); + | -----^^^^^^^^^^ + | | + | `async` because of this + | + = note: `async` trait functions are not currently supported + = note: consider using the `async-trait` crate: https://crates.io/crates/async-trait +``` + +She then realizes that Rust doesn't support async functions in traits yet, so she adds [`async-trait`] to her dependencies. + +Some of her functions are recursive, and she wanted them to be async functions, so she sprinkles some `async/.await` keywords in those functions. + +```rust +async fn sum(n: usize) -> usize { + if n == 0 { + 0 + } else { + n + sum(n - 1).await + } +} +``` + +Then she gets an error message. + +``` +error[E0733]: recursion in an `async fn` requires boxing + --> src/lib.rs:1:27 + | +1 | async fn sum(n: usize) -> usize { + | ^^^^^ recursive `async fn` + | + = note: a recursive `async fn` must be rewritten to return a boxed `dyn Future` +``` + +So to make these functions async she starts boxing her futures the hard way, fighting with the compiler. She knows that `async` keyword is sort of a sugar for `impl Future` so she tries the following at first. + +```rust +fn sum(n: usize) -> Box> { + Box::new(async move { + if n == 0 { + 0 + } else { + n + sum(n - 1).await + } + }) +} +``` + +The compiler gives the following error. + +``` +error[E0277]: `dyn Future` cannot be unpinned + --> src/main.rs:11:17 + | +11 | n + sum(n - 1).await + | ^^^^^^^^^^^^^^^^ the trait `Unpin` is not implemented for `dyn Future` + | + = note: required because of the requirements on the impl of `Future` for `Box>` + = note: required by `poll` +``` + +She then reads about `Unpin` and `Pin`, and finally comes up with a solution. + +```rust +fn sum(n: usize) -> Pin>> { + Box::pin(async move { + if n == 0 { + 0 + } else { + n + sum(n - 1).await + } + }) +} +``` + +The code works! + +She searches online for better methods and finds out the [async-book](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html). She reads about [recursion](https://rust-lang.github.io/async-book/07_workarounds/04_recursion.html) and finds out a cleaner way using the [`futures`] crate. + +```rust +use futures::future::{BoxFuture, FutureExt}; + +fn sum(n: usize) -> BoxFuture<'static, usize> { + async move { + if n == 0 { + 0 + } else { + n + sum(n - 1).await + } + }.boxed() +} +``` + +She also asks one of her peers for a code review asynchronously, and after awaiting their response, she learns about the [`async-recursion`] crate. Then she adds [`async-recursion`] to the dependencies. Now she can write the following, which seems reasonably clean: + +```rust +#[async_recursion] +async fn sum(n: usize) -> usize { + if n == 0 { + 0 + } else { + n + sum(n - 1).await + } +} +``` + +As she is working, she realizes that what she really needs is to write a `Stream` of data. She starts trying to write her `Stream` implementation and spends several hours banging her head against her desk in frustration (her challenges are pretty similar to what [Alan experienced](./alan_hates_writing_a_stream.md)). Ultimately she's stuck trying to figure out why her `&mut self.foo` call is giving her errors: + +``` +error[E0277]: `R` cannot be unpinned + --src/main.rs:52:26 + | +52 | Pin::new(&mut self.reader).poll_read(cx, buf) + | ^^^^^^^^^^^^^^^^ the trait `Unpin` is not implemented for `R` + | + = note: required by `Pin::

::new` +help: consider further restricting this bound + | +40 | R: AsyncRead + Unpin, + | ^^^^^^^ +``` + +Fortunately, that weekend, [@fasterthanlime](https://github.com/fasterthanlime) publishes a [blog post](https://fasterthanli.me/articles/pin-and-suffering) covering the gory details of `Pin`. Reading that post, she learns about [`pin-project`], which she adds as a dependency. She's able to get her code working, but it's kind of a mess. Feeling quite proud of herself, she shows it to a friend, and they suggest that maybe she ought to try the [`async-stream`] crate. Reading that, she realizes she can use this crate to simplify some of her streams, though not all of them fit. + +"Finally!", Barbara says, breathing a sigh of relief. She is done with her prototype, and shows it off at work, but to her dismay, the team decides that they need to use a custom runtime for their use case. They're building an embedded system and it has relatively limited resources. Barbara thinks, "No problem, it should be easy enough to change runtimes, right?" + +So now Barbara starts the journey of replacing tokio with a myriad of off the shelf and custom helpers. She can't use warp so now she has to find an alternative. She also has to find a new channel implementations and there are a few: +* In [`futures`] +* [`async-std`] has one, but it seems to be tied to another runtime so she can't use that. +* [`smol`] has one that is independent. + +This process of "figure out which alternative is an option" is repeated many times. She also tries to use the [`select!`](https://docs.rs/futures/0.3.14/futures/macro.select.html) macro from [`futures`] but it requires more pinning and workarounds (not to mention a [stack overflow](https://rust-lang.github.io/wg-async/vision/status_quo/alan_runs_into_stack_trouble.html) or two). + +But Barbara fights through all of it. In the end, she gets it to work, but she realizes that she has a ton of random dependencies and associated compilation time. She wonders if all that dependencies will have a negative effect on the binary size. She also had to rewrite some bits of functionality on her own. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** +* Functionality is found either in "framework"-like crates (e.g., tokio) *and* spread around many different ecosystem crates. +* It's sometimes difficult to discover where this functionality lives. +* Additionally, the trouble of non runtime-agnostic libraries becomes very apparent. +* Helpers and utilities might have analogues across the ecosystem, but they are different in subtle ways. +* Some patterns are clean if you know the right utility crate and very painful otherwise. + +### **What are the sources for this story?** +[Issue 105](https://github.com/rust-lang/wg-async/issues/105) + +### **What are helper functions/macros?** +They are functions/macros that helps with certain basic pieces of functionality and features. Like to await on multiple futures concurrently (`join!` in tokio), or else race the futures and take the result of the one that finishes first. + +### **Will there be a difference if lifetimes are involved in async recursion functions?** +Lifetimes would make it a bit more difficult. Although for simple functions it shouldn't be much of a problem. +```rust +fn concat<'a>(string: &'a mut String, slice: &'a str) -> Pin + 'a>> { + Box::pin(async move { + if !slice.is_empty() { + string.push_str(&slice[0..1]); + concat(string, &slice[1..]).await; + } + }) +} +``` + +### **Why did you choose [Barbara] to tell this story?** +This particular issue impacts all users of Rust even (and sometimes especially) experienced ones. + +### **How would this story have played out differently for the other characters?** +Other characters may not know all their options and hence might have fewer problems as a result. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade + +[`tokio`]: https://crates.io/crates/tokio/ +[`tokio-stream`]: https://crates.io/crates/tokio-stream/ +[`futures`]: https://crates.io/crates/futures/ +[`async-recursion`]: https://crates.io/crates/async-recursion/ +[`async-trait`]: https://crates.io/crates/async-trait/ +[`async-stream`]: https://crates.io/crates/async-stream/ +[`async-std`]: https://crates.io/crates/async-std/ +[`pin-project`]: https://crates.io/crates/pin-project/ +[`smol`]: https://crates.io/crates/smol/ +[`warp`]: https>//crates.io/crates/warp/ diff --git a/src/vision/status_quo/barbara_plays_with_async.md b/src/vision/submitted_stories/status_quo/barbara_plays_with_async.md similarity index 97% rename from src/vision/status_quo/barbara_plays_with_async.md rename to src/vision/submitted_stories/status_quo/barbara_plays_with_async.md index 5595de83..5146c7b1 100644 --- a/src/vision/status_quo/barbara_plays_with_async.md +++ b/src/vision/submitted_stories/status_quo/barbara_plays_with_async.md @@ -43,7 +43,7 @@ that will wait for a certain duration to elapse before resolving. Borrowing again from the "Hello Tokio" tutorial to make sure she has the correct spelling for the tokio macros, she writes up the following code: -```rust,ignore +```rust #[tokio::main] pub async fn main() { let mut rng = thread_rng(); @@ -72,7 +72,7 @@ seconds doing nothing, and giving no hints about what it's actually doing. So for the next iteration, Barbara wants to have a message printed out when each future is resolved. She tries this code at first: -```rust,ignore +```rust let mut futures = Vec::new(); for _ in 0..10 { let delay = rng.sample(t); @@ -105,7 +105,7 @@ which looks interesting. Indeed, this struct is a future that will resolve instantly, which is what she wants: -```rust,ignore +```rust for _ in 0..10 { let delay = rng.sample(t); futures.push(tokio::time::sleep(Duration::from_millis(delay)).then(|_| { @@ -161,7 +161,7 @@ and remembers that there's a 3rd-party crate called "[futures][futures crate]" on crates.io that she's seen mentioned in some /r/rust posts. She checks the docs and finds the `join_all` function which looks like what she wants: -```rust,ignore +```rust let mut futures = Vec::new(); for _ in 0..10 { let delay = rng.sample(t); @@ -182,7 +182,7 @@ rust futures are lazy, and won't make progress unless you await them. Happy with this success, Barbara continues to expand her toy program by making a few small adjustments: -```rust,ignore +```rust for counter in 0..10 { let delay = rng.sample(t); let delay_future = tokio::time::sleep(Duration::from_millis(delay)); @@ -220,7 +220,7 @@ entire future. She first adds explicit type annotations to the Vec: -```rust,ignore +```rust let mut futures: Vec>> = Vec::new(); ``` @@ -245,7 +245,7 @@ intuition about what this API is for. But she is accustomed to just trying things in rust to see if they work. And indeed, after changing `Box::new` to `Box::pin`: -```rust,ignore +```rust futures.push(Box::pin(delay_future.then(|_| { println!("Done!"); std::future::ready(()) @@ -254,7 +254,7 @@ futures.push(Box::pin(delay_future.then(|_| { and adjusting the type of the Vec: -```rust,ignore +```rust let mut futures: Vec>>> = Vec::new(); ``` @@ -272,7 +272,7 @@ post about async rust a few weeks ago, and has a vague idea of how it looks. She tries writing this: -```rust,ignore +```rust futures.push(Box::pin(async || { tokio::time::sleep(Duration::from_millis(delay)).await; println!("Done after {}ms", delay); @@ -296,7 +296,7 @@ error[E0658]: async closures are unstable Barbara knows that async is stable and using this nightly feature isn't what she wants. So the tries the suggestion made by the compiler and removes the `||` bars: -```rust,ignore +```rust futures.push(Box::pin(async { tokio::time::sleep(Duration::from_millis(delay)).await; println!("Done after {}ms", delay); @@ -318,7 +318,7 @@ to an async block), but Barbara's experience with rust tells her that it's a ver consistent language. Maybe the same keyword used in move closures will work here? She tries it: -```rust,ignore +```rust futures.push(Box::pin(async move { tokio::time::sleep(Duration::from_millis(delay)).await; println!("Done after {}ms", delay); @@ -384,10 +384,10 @@ to resort to an internet search or asking on a rust community. * [Barbara makes their first steps in async] is Barbara in a slightly different universe. * [Alan started trusting the rust compiler][trusts the rust compiler] is a similar story about a different character. -[status quo stories]: ./status_quo.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[status quo stories]: ../status_quo.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade [trusts the rust compiler]: ./alan_started_trusting_the_rust_compiler_but_then_async.md [Barbara makes their first steps in async]: ./barbara_makes_their_first_steps_into_async.md [reddit]: https://www.reddit.com/r/rust diff --git a/src/vision/submitted_stories/status_quo/barbara_polls_a_mutex.md b/src/vision/submitted_stories/status_quo/barbara_polls_a_mutex.md new file mode 100644 index 00000000..457058ca --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_polls_a_mutex.md @@ -0,0 +1,151 @@ +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## Barbara polls a Mutex + +### Brief summary + +[Barbara] is implementing an interpreter for a scripting language. This +language has implicit asynchronicity, so all values in the language can +potentially be futures underneath. + +Barbara wants to store a namespace which maps variable names to their values. She +chooses to use a `HashMap` and finds the `async_lock` crate provides an async +mutex, which she can use for concurrency. She determines she'll need a lock +around the namespace itself to protect concurrent modification. + +For the entries in her map, Barbara decides to implement a two-variant enum. One +variant indicates that there is no implicit asynchronicity to resolve and the +value is stored directly here. The other variant indicates that this value is +being computed asynchronously and polling will be required to resolve it. +Because an asynchronous task might want to change one of these entries from the +asynchronous variant to the ready variant, she'll need to wrap the entries in +an `Arc` and a `Mutex` to allow an asynchronous task to update them. + +Barbara wants to be able to derive a future from the entries in her namespace +that will allow her to wait until the entry becomes ready and read the value. +She decides to implement the `Future` trait directly. She's done this before +for a few simple cases, and is somewhat comfortable with the idea, but she runs +into significant trouble trying to deal with the mutex in the body of her poll +function. Here are her attempts: + +```rust= +use async_lock::Mutex; + +enum Value { + Int(i32), +} + +enum NSEntry { + Ready(Value), + Waiting(Vec), +} + +type Namespace = Mutex>>; + +// Attempt 1: This compiles!! +struct NSValueFuture(Arc>); +impl Future for NSValueFuture { + type Output = Value; + pub fn poll( + self: Pin<&mut Self>, + cx: &mut Context<'_> + ) -> Poll { + let entry = match self.0.lock().poll() { + Poll::Ready(ent) => ent, + + // When this returns, it will drop the future created by lock(), + // which drops our position in the lock's queue. + // You could never wake up. + // Get starved under contention. / Destroy fairness properties of lock. + Poll::Pending => return Poll::Pending, + }; + + ... + } +} + +// Attempt 2 +struct NSValueFuture { + ent: Arc>, + lock_fut: Option>, +} +impl Future for NSValueFuture { + type Output = Value; + pub fn poll( + self: Pin<&mut Self>, + cx: &mut Context<'_> + ) -> Poll { + if self.lock_fut.is_none() { + self.lock_fut = Some(self.ent.lock()), + } + // match self.lock_fut.unwrap().poll(cx) + // Pulled out pin-project, got confused, decided to just use unsafe. + match unsafe { Pin::new_unchecked(&mut self).lock_fut.unwrap() }.poll(cx) { + ... + } + // ??? lifetime for MutexLockFuture ??? + // try async-std, async-lock + } +} + +// Realize `lock_arc()` is a thing +// Realize you need `BoxFuture` to await it, since you can't name the type + +// Working code: +struct NsValueFuture { + target: Arc>, + lock_fut: Option>>, +} + +impl Future for NsValueFuture { + type Output = Value; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.lock_fut.is_none() { + let target = Arc::clone(&self.target); + let lock = async move { target.lock_arc().await }.boxed(); + self.lock_fut = Some(lock) + } + + if let Poll::Ready(mut value) = self.lock_fut.as_mut().unwrap().as_mut().poll(cx) { + self.lock_fut = None; + match &mut *value { + NsValue::Ready(x) => { + Poll::Ready(x.clone()) + } + NsValue::Waiting(w) => { + w.push(cx.waker().clone()); + Poll::Pending + } + } + } else { + Poll::Pending + } + } +} +``` + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** +* Trying to compose futures manually without an enclosing async block/function +is extremely difficult and may even be dangerous. + +### **What are the sources for this story?** +*Talk about what the story is based on, ideally with links to blog posts, tweets, or other evidence.* + +### **Why did you choose *Barbara* to tell this story?** +* It's possible to be fairly comfortable with Rust and even some of the +internals of async and still be stopped in your tracks by this issue. + +### **How would this story have played out differently for the other characters?** +*In some cases, there are problems that only occur for people from specific backgrounds, or which play out differently. This question can be used to highlight that.* + +[status quo stories]: ../status_quo.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../../how_to_vision/status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/status_quo/barbara_tries_async_streams.md b/src/vision/submitted_stories/status_quo/barbara_tries_async_streams.md similarity index 94% rename from src/vision/status_quo/barbara_tries_async_streams.md rename to src/vision/submitted_stories/status_quo/barbara_tries_async_streams.md index fc286315..2b728d56 100644 --- a/src/vision/status_quo/barbara_tries_async_streams.md +++ b/src/vision/submitted_stories/status_quo/barbara_tries_async_streams.md @@ -47,11 +47,11 @@ Barbara is an experienced engineer who may come to async streams and async/await * Grace may have chosen a different core abstraction from the start. She has a good chance of thinking through how she'd like the data processing system to work. It's possible she would have found threads and channels a better fit. This would have had different trade-offs. * Niklaus may have also tried to go down the async stream path. The information available is mixed and hype around async/await is too strong. This makes it shine brighter than it should. Without experience with different systems languages to temper the direction, the most likely path would be to experiment with asynchrony and hope that "underneath the surface it does the right thing." -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/barbara_tries_unix_socket.md b/src/vision/submitted_stories/status_quo/barbara_tries_unix_socket.md new file mode 100644 index 00000000..9f732dbc --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_tries_unix_socket.md @@ -0,0 +1,331 @@ +# 😱 Status quo stories: Barbara tries Unix socket + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +Content of `Cargo.toml` for reproducibility: + +

+ Cargo.toml + +```toml +futures = "0.3.14" +hyper = { version = "0.14.7", features = ["full"] } +pretty_env_logger = "0.4.0" +reqwest = "0.11.3" +tokio = { version = "1.5.0", features = ["macros", "rt-multi-thread"] } +``` +
+ +There is a HTTP server in hyper which Barbara have to query. + +
+ Server code + +```rust +use hyper::server::conn::Http; +use hyper::service::service_fn; +use hyper::{Body, Request, Response}; +use std::convert::Infallible; +use tokio::net::TcpListener; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let listener = TcpListener::bind("127.0.0.1:3000").await?; + + loop { + let (stream, _) = listener.accept().await?; + + tokio::spawn(async move { + let _ = Http::new() + .serve_connection(stream, service_fn(serve)) + .await; + }); + } +} + +async fn serve(_req: Request) -> Result, Infallible> { + let res = Response::builder() + .header("content-type", "text/plain; charset=utf-8") + .body(Body::from("Hello World!")) + .unwrap(); + Ok(res) +} +``` +
+ +## Nice simple query with high-level reqwest + +Barbara do HTTP GET request using TCP socket with reqwest and it works fine, everything is easy. + +```rust +#[tokio::main] +async fn main() -> Result<(), Box> { + let res = reqwest::get("http://127.0.0.1:3000").await?; + println!("{}", res.text().await?); + Ok(()) +} +``` + +## Unix sockets for performance + +One day, Barbara heard that using unix socket can provide a better performance by using unix socket when both the server and client is on the same machine, so Barbara decided to try it out. + +Barbara starts porting the server code to use unix socket, it was a no brainer for Barbara at least. Barbara changed `TcpListener::bind("127.0.0.1:3000").await?` to `UnixListener::bind("/tmp/socket")?` and it works like a charm. + +Barbara search through reqwest doc and github issues to see how to use unix socket for reqwest. Barbara found https://github.com/seanmonstar/reqwest/issues/39#issuecomment-778716774 saying reqwest does not support unix socket but hyper does with an example, which is a lower-level library. Since reqwest is so easy and porting hyper server to use unix socket is easy, Barbara thinks low-level hyper library should be easy too. + +## The screen stares at Barbara + +```rust +use hyper::{body::HttpBody, client::conn, Body, Request}; +use tokio::net::UnixStream; + +#[tokio::main] +async fn main() -> Result<(), Box> { + pretty_env_logger::init(); + let stream = UnixStream::connect("/tmp/socket").await?; + + let (mut request_sender, connection) = conn::handshake(stream).await?; + + let request = Request::get("/").body(Body::empty())?; + let mut response = request_sender.send_request(request).await?; + println!("{:?}", response.body_mut().data().await); + + let request = Request::get("/").body(Body::empty())?; + let mut response = request_sender.send_request(request).await?; + println!("{:?}", response.body_mut().data().await); + + Ok(()) +} +``` + +Barbara wrote some code according to the comments Barbara saw and read some docs like what is `handshake` to roughly know what it does. Barbara compile and it shows a warning, the `connection` variable is not used: +``` +warning: unused variable: `connection` + --> src/main.rs:9:30 + | +9 | let (mut request_sender, connection) = conn::handshake(stream).await?; + | ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_connection` + | + = note: `#[warn(unused_variables)]` on by default +``` + +Barbara then runs the program. Barbara stares at the screen and the screen stares at her. Barbara waited and ... it was stuck. So Barbara decides to look at the logs and run it again with `env RUST_LOG=trace cargo r`, and it was indeed stuck, but not sure where. +``` + TRACE mio::poll > registering event source with poller: token=Token(0), interests=READABLE | WRITABLE +``` + +Barbara try adding `println!` all over the code but it was still stuck, so Barbara try asking for help. Thanks to the welcoming Rust community, Barbara got help quickly in this case. It seemed like Barbara missed the `connection` which is a culprit and it was in the parent module of the docs Barbara read. + +Barbara added the missing piece to `.await` for the `connection`, all the while Barbara thought it will work if it was `.await`-ed but in this case having required to await something else to work is a surprise. Someone suggests to Barbara that she follow the example in the docs to insert a `tokio::spawn`, so she winds up with: + +```rust + let (mut request_sender, connection) = conn::handshake(stream).await?; + + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("error: {}", e); + } + }) + + let request = ... +``` + +Barbara run the code and it works now, yay! Barbara want to try to reuse the connection to send subsequent HTTP request. Barbara duplicates the last block and it runs. + +## Mysterious second request + +Some time later, Barbara was told that the program did not work on second request. Barbara tried it but it works. To double confirm, when Barbara tried it again it did not work. Rather than getting stuck, this time there is a error message, which is somewhat better but Barbara did not understand. + +The second request is so mysterious, it is like the second request playing hide and seek with Barbara. Sometimes it works and sometimes it does not work. + +```rust + TRACE mio::poll > registering event source with poller: token=Token(0), interests=READABLE | WRITABLE +Some(Ok(b"Hello World!")) + TRACE want > signal: Want + TRACE mio::poll > deregistering event source from poller + TRACE want > signal: Closed +Error: hyper::Error(Canceled, "connection was not ready") +``` + +As a typical method of solving asynchronous issue. Barbara add prints to every await boundaries in the source code to understand what is going on. + +```rust +use hyper::{body::HttpBody, client::conn, Body, Request}; +use tokio::net::UnixStream; + +#[tokio::main] +async fn main() -> Result<(), Box> { + pretty_env_logger::init(); + let stream = UnixStream::connect("/tmp/socket").await?; + + let (mut request_sender, connection) = conn::handshake(stream).await?; + println!("connected"); + + tokio::spawn(async move { + if let Err(e) = connection.await { + println!("closed"); + eprintln!("error: {}", e); + } + println!("closed"); + }); + + let request = Request::get("/").body(Body::empty())?; + let mut response = request_sender.send_request(request).await?; + println!("{:?}", response.body_mut().data().await); + + let request = Request::get("/").body(Body::empty())?; + println!("sending 2"); + let mut response = request_sender.send_request(request).await?; + println!("sent 2"); + println!("{:?}", response.body_mut().data().await); + + Ok(()) +} +``` + +The logs are now more detailed. Barbara can see that the connection was closed but why? Barbara had no idea and Barbara had to seek help again. +``` + TRACE mio::poll > registering event source with poller: token=Token(0), interests=READABLE | WRITABLE +connected +Some(Ok(b"Hello World!")) +sending 2 + TRACE want > signal: Want + TRACE mio::poll > deregistering event source from poller + TRACE want > signal: Closed +closed +Error: hyper::Error(Canceled, "connection was not ready") +``` + +This time as well, Barbara was lucky enough to get a quick reply from the welcoming Rust community. Other users said there is a trick for these kind of cases, which is a tracing stream. + +```rust +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + +pub struct TracingStream { + pub inner: S, +} + +impl AsyncRead for TracingStream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let poll_result = Pin::new(&mut self.inner).poll_read(cx, buf); + for line in String::from_utf8_lossy(buf.filled()).into_owned().lines() { + println!("> {}", line); + } + poll_result + } +} + +impl AsyncWrite for TracingStream { + fn poll_flush( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) + } + + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let poll_result = Pin::new(&mut self.inner).poll_write(cx, buf); + for line in String::from_utf8_lossy(buf).into_owned().lines() { + println!("< {}", line); + } + poll_result + } +} +``` + +Barbara happily copy pasted the code and wrap the `stream` within `TracingStream`. Running it with logs gives (same thing, in some cases it works, in some cases it did not work): +``` + TRACE mio::poll > registering event source with poller: token=Token(0), interests=READABLE | WRITABLE +connected +< GET / HTTP/1.1 +< +> HTTP/1.1 200 OK +> content-type: text/plain; charset=utf-8 +> content-length: 12 +> date: Tue, 04 May 2021 17:02:49 GMT +> +> Hello World! +Some(Ok(b"Hello World!")) +sending 2 + TRACE want > signal: Want + TRACE want > signal: Want + TRACE mio::poll > deregistering event source from poller + TRACE want > signal: Closed +closed +Error: hyper::Error(Canceled, "connection was not ready") +``` + +Barbara thought this probably only affects a unix socket but nope, even swapping it back with TCP socket does not work either. Now, not just Barbara was confused, even the other developers who offered help was confused now. + +## The single magical line + +After some time, a developer found a solution, just a single line. Barbara added the line and it works like a charm but it still feels like magic. + +```rust +use futures::future; + + // this new line below was added for second request + future::poll_fn(|cx| request_sender.poll_ready(cx)).await?; + let request = Request::get("/").body(Body::empty())?; + println!("sending 2"); + let mut response = request_sender.send_request(request).await?; + println!("sent 2"); + println!("{:?}", response.body_mut().data().await); +``` + +Barbara still have no idea why it needs to be done this way. But at least it works now. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +Barbara is not able to see the problem right away. Usually missing an `await` is an issue but in this case, not awaiting on another variable or not polling for ready when using a low-level library may the program incorrect, it is also hard to debug and figure out what is the correct solution. + +In a way, some of the fixes "feels like magic". Sometimes polling is required to be done but where? It may make people afraid of using `async/.await` and end up writing safety net code (for example, writing code to do type checking in weakly typed languages in every lines of code to be safe). + +Having these pitfalls in mind, one can easily relate it back to unsafe. If there are unsafe blocks, the user needs to manually audit every specific code block for undefined behaviors. But in the case of async, the situation is someone similar such that the user need to audit the whole async code blocks (which is a lot compared to unsafe) for "undefined behaviors", rather than having when it compiles it works sort of behavior. + +### **What are the sources for this story?** + +pickfire was experimenting with HTTP client over unix socket and faced this issue as he though it is easy, still a lot thanks to Programatik for helping out with a quick and helpful response. + +### **Why did you choose *Barbara* to tell this story?** + +Barbara have some experience with synchronous and high-level asynchronous rust libraries but not with low-level asynchronous libraries. + +### **How would this story have played out differently for the other characters?** + +Most likely everyone could have faced the same issue unless they are lucky. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/status_quo/barbara_trims_a_stacktrace.md b/src/vision/submitted_stories/status_quo/barbara_trims_a_stacktrace.md similarity index 92% rename from src/vision/status_quo/barbara_trims_a_stacktrace.md rename to src/vision/submitted_stories/status_quo/barbara_trims_a_stacktrace.md index 2cb3eeda..44681c73 100644 --- a/src/vision/status_quo/barbara_trims_a_stacktrace.md +++ b/src/vision/submitted_stories/status_quo/barbara_trims_a_stacktrace.md @@ -1,16 +1,16 @@ # 😱 Status quo stories: Barbara trims a stacktrace -[How To Vision: Status Quo]: ../how_to_vision/status_quo.md -[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async-foundations/master/src/vision/status_quo/template.md -[`status_quo`]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/status_quo -[`SUMMARY.md`]: https://github.com/rust-lang/wg-async-foundations/blob/master/src/SUMMARY.md -[open issues]: https://github.com/rust-lang/wg-async-foundations/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas -[open an issue of your own]: https://github.com/rust-lang/wg-async-foundations/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= +[How To Vision: Status Quo]: ../status_quo.md +[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async/master/src/vision/status_quo/template.md +[`status_quo`]: https://github.com/rust-lang/wg-async/tree/master/src/vision/status_quo +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md +[open issues]: https://github.com/rust-lang/wg-async/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas +[open an issue of your own]: https://github.com/rust-lang/wg-async/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= ## 🚧 Warning: Draft status 🚧 -This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! @@ -81,7 +81,7 @@ stack backtrace: 3: slow_rs::process_many::{{closure}} at ./src/main.rs:10:5 5: slow_rs::main::{{closure}}::{{closure}} at ./src/main.rs:4:9 7: slow_rs::main::{{closure}} at ./src/main.rs:3:5 - 13: + 13: 19: slow_rs::main note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. ``` @@ -102,7 +102,7 @@ Fin. * calls to `poll`. ### **What are the sources for this story?** -[Sergey Galich](https://github.com/rust-lang/wg-async-foundations/issues/69#issuecomment-803208049) reported this problem, among many others. +[Sergey Galich](https://github.com/rust-lang/wg-async/issues/69#issuecomment-803208049) reported this problem, among many others. ### **Why did you choose Barbara to tell this story?** She knows about the desugarings that give rise to symbols like `::{closure}`, but she still finds them annoying to deal with in practice. @@ -116,7 +116,7 @@ She knows about the desugarings that give rise to symbols like `::{closure}`, bu * Other languages have developed special tools to connect async functions to their callers, however, which gives them a nice experience. For example, Chrome has a [UI for enabling stacktraces that cross await points](https://www.html5rocks.com/en/tutorials/developertools/async-call-stack/#toc-enable). ### **Why doesn't Barbara view this in a debugger?** -* Because it came in an issue report (or, freqently, as a crash report or email). +* Because it came in an issue report (or, frequently, as a crash report or email). * But also, that isn't necessarily an improvement! Expand below if you would like to see what we mean.
@@ -163,12 +163,12 @@ at lib.rs:102:13 ### **Doesn't Rust have backtrace trimming support?** Yes, this **is** the reduced backtrace. You don't even want to know what the [full one](https://gist.github.com/eminence/0b3e697b7c4e686451ff0d37c169c89d) looks like. Don't click it. Don't! - -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/status_quo/barbara_wants_async_insights.md b/src/vision/submitted_stories/status_quo/barbara_wants_async_insights.md similarity index 90% rename from src/vision/status_quo/barbara_wants_async_insights.md rename to src/vision/submitted_stories/status_quo/barbara_wants_async_insights.md index 7c08dc76..fec6619e 100644 --- a/src/vision/status_quo/barbara_wants_async_insights.md +++ b/src/vision/submitted_stories/status_quo/barbara_wants_async_insights.md @@ -2,7 +2,7 @@ ## 🚧 Warning: Draft status 🚧 -This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! @@ -14,7 +14,7 @@ She does this by sprinkling `async/.await` everywhere, picking an executor, and Once she has the program compiling, she thinks "oh that was easy". She runs it for the first time and surprisingly she finds out that when hitting an endpoint, nothing happens. -Barbara, always prepared, has already added logging to her service and she checks the logs. As she expected, she sees here that the endpoint handler has been invoked but then... nothing. Barbara exclaims, "Oh no! This was not what I was expecting, but let's dig deeper." +Barbara, always prepared, has already added logging to her service and she checks the logs. As she expected, she sees here that the endpoint handler has been invoked but then... nothing. Barbara exclaims, "Oh no! This was not what I was expecting, but let's dig deeper." She checks the code and sees that the endpoint spawns several tasks, but unfortunately those tasks don't have much logging in them. @@ -44,7 +44,7 @@ She thinks, "Anyhow it is working now, let's see if we got some performance gain * Debugging process for non-trivial issues is almost guaranteed to be painful and expensive. ### **What are the sources for this story?** -[Issue 75](https://github.com/rust-lang/wg-async-foundations/issues/75) +[Issue 75](https://github.com/rust-lang/wg-async/issues/75) ### **What are examples of the kinds of things a user might want to have insight into?** * Custom Events - logging/tracing (Per task?) @@ -67,11 +67,11 @@ Depending on what languages he was using before, [Alan] would likely have had ex * Using [Visual Studio](https://devblogs.microsoft.com/visualstudio/how-do-i-debug-async-code-in-visual-studio/) to debug C#. * Debugging async Java using [IntelliJ](https://www.jetbrains.com/help/idea/debug-asynchronous-code.html). -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/barbara_wants_single_threaded_optimizations.md b/src/vision/submitted_stories/status_quo/barbara_wants_single_threaded_optimizations.md new file mode 100644 index 00000000..3be3b257 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_wants_single_threaded_optimizations.md @@ -0,0 +1,126 @@ +# 😱 Status quo stories: Barbara wants single threaded optimizations, but not that much + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +Barbara is working on operating system services, all of which benefit from concurrency, but only some of which benefit from parallelism. In cases where a service does not benefit from parallelism, a single-threaded executor is used which allows spawning `!Send` tasks. + +Barbara has developed a useful feature as a module within one of her system's single-threaded services. The feature allows for the creation of multiple IPC objects to use within concurrent tasks while caching and reusing some of the heavier computation performed. This is implemented with reference counted interior mutability: + +```rust +pub struct IpcHandle { + cache_storage: Rc>, + // ... +} + +struct IpcCache { /* ... */ } +``` + +A colleague asks Barbara if she'd be interested in making this code available to other services with similar needs. After Barbara factors the module out into its own crate, her colleague tries integrating it into their service. This fails because the second service needs to hold `IpcHandle`s across yieldpoints and it uses a multi-threaded executor. The multi-threaded executor requires that all tasks implement `Send` so they can be migrated between threads for work stealing scheduling. + +### Rejected: both single- and multi-threaded versions + +Barbara considers her options to make the crate usable by the multi-threaded system service. She decides against making `IpcHandle` available in both single-threaded and multi-threaded versions. To do this generically would require a lot of boilerplate. For example, it would require manually duplicating APIs which would need to have a `Send` bound in the multi-threaded case: + +```rust +impl LocalIpcHandle { + fn spawn_on_reply(&mut self, to_spawn: impl Fn(IpcReply) -> F) { + // ... + } +} + +impl SendIpcHandle { + fn spawn_on_reply(&mut self, to_spawn: impl Fn(IpcReply) -> F) { + // ... + } +} +``` + +### Accepted: only implement multi-threaded version + +Barbara decides it's not worth the effort to duplicate so much of the crate's functionality, and decides to make the whole library thread-safe: + +```rust +pub struct IpcHandle { + cache_storage: Arc>, + // ... +} + +struct IpcCache { /* ... */ } +``` + +This requires her to migrate her original system service to use multi-threaded types when interacting with the library. Before the change her service uses only single-threaded reference counting and interior mutability: + +```rust +#[derive(Clone)] +struct ClientBroker { + state: Rc>, +} + +impl ClientBroker { + fn start_serving_clients(self) { + let mut ipc_handle = self.make_ipc_handle_for_new_clients(); + ipc_handle.spawn_on_reply(move |reply| shared_state.clone().serve_client(reply)); + LocalExecutor::new().run_singlethreaded(ipc_handle.listen()); + } + + fn make_ipc_handle_for_new_clients(&self) { /* ... */ } + async fn serve_client(self, reply: IpcReply) { /* accesses interior mutability... */ } +} +``` + +In order to be compatible with her own crate, Barbara needs to wrap the shared state of her service behind multi-threaded reference counting and synchronization: + +```rust +#[derive(Clone)] +struct ClientBroker { + state: Arc>, +} + +impl ClientBroker { /* nothing changed */ } +``` + +This incurs some performance overhead when cloning the `Arc` and when accessing the `Mutex`. The former is cheap when uncontended on x86 but will have different performance characteristics on e.g. ARM platforms. The latter's overhead varies depending on the kind of `Mutex` used, e.g. an uncontended `parking_lot::Mutex` may only need a few atomic instructions to acquire it. Acquiring many platforms' `std::sync::Mutex` is much more expensive than a few atomics. This overhead is usually not very high, but it does pollute shared resources like cache lines and is multiplied by the number of single-threaded services which make use of such a library. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +In synchronous Rust, choosing the "`Send`ness" of a crate is basically a choice about the concurrency it can support. In asynchronous Rust, one can write highly concurrent programs that still execute using only a single thread, but it is difficult to achieve maximum performance with reusable code. + +Abstracting over a library's `Send`ness requires being generic over storage/synchronization types *and* APIs which need to accept user-defined types/tasks/callbacks. + +### **What are the sources for this story?** + +As of writing, the [Fuchsia](https://fuchsia.dev) operating system had [over 1,500 invocations][st-invocations] of `LocalExecutor::run_singlethreaded`. There were [less than 500 invocations][mt-invocations] of `SendExecutor::run`.[^fuchsia-methods] As of writing the author could not find any widely used support libraries which were not thread-safe. + +[st-invocations]: https://cs.opensource.google/search?q=file:rs%20run_singlethreaded&sq=&ss=fuchsia%2Ffuchsia +[mt-invocations]: https://cs.opensource.google/search?q=file:rs%20%5C.run%5C(&ss=fuchsia%2Ffuchsia + +`actix-rt`'s [spawn function](https://docs.rs/actix-rt/1.1.1/actix_rt/fn.spawn.html) does not require `Send` for its futures, because each task is polled on the thread that spawned it. However it is very common when using `actix-rt` via `actix-web` to make use of async crates originally designed for `tokio`, whose [spawn function](https://docs.rs/tokio/1.6.1/tokio/fn.spawn.html) does require `Send`. + +Popular crates like `diesel` are still designing async support, and it appears they are [likely to require `Send`](https://github.com/diesel-rs/diesel/issues/399#issuecomment-850826567). + +[^fuchsia-methods]: There are multiple ways to invoke the different Rust executors for Fuchsia. The other searches for each executor yield a handful of results but not enough to change the relative sample sizes here. + +### **Why did you choose *Barbara* to tell this story?** + +As an experienced Rustacean, [Barbara] is more likely to be responsible for designing functionality to share across teams. She's also going to be more aware of the specific performance implications of her change, and will likely find it more frustrating to encounter these boundaries. + +### **How would this story have played out differently for the other characters?** + +A less experienced Rustacean may not even be tempted to define two versions, as the approach Barbara took is pretty close to the "just `.clone()` it" advice often given to beginners. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/barbara_wants_to_use_ghostcell.md b/src/vision/submitted_stories/status_quo/barbara_wants_to_use_ghostcell.md new file mode 100644 index 00000000..433fda5c --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_wants_to_use_ghostcell.md @@ -0,0 +1,414 @@ +# Barbara wants to use GhostCell-like cell borrowing with futures + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the +brainstorming period. It is derived from real-life experiences of +actual Rust users and is meant to reflect some of the challenges that +Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to +the FAQ, feel free to open a PR making edits (but keep in mind that, +as they reflect peoples' experiences, status quo stories [cannot be +wrong], only inaccurate). Alternatively, you may wish to [add your own +status quo story][htvsq]! + +## The story + +Barbara quite likes using statically-checked cell borrowing. "Cell" +in Rust terminology refers to types like `Cell` or `RefCell` that +enable interior mutability, i.e. modifying or mutably borrowing stuff +even if you've only got an immutable reference to it. +Statically-checked cell borrowing is a technique whereby one object +(an "owner") acts as a gatekeeper for borrow-access to a set of other +objects ("cells"). So if you have mutable borrow access to the owner, +you can temporarily transfer that mutable borrow access to a cell in +order to modify it. This is all checked at compile-time, hence +"statically-checked". + +In comparison `RefCell` does borrow-checking, but it is checked at +runtime and it will panic if you make a coding mistake. The advantage +of statically-checked borrowing is that it cannot panic at runtime, +i.e. all your borrowing bugs show up at compile time. The history +goes way back, and the technique has been reinvented at least 2-3 +times as far as Barbara is aware. This is implemented in various +forms in [GhostCell](http://plv.mpi-sws.org/rustbelt/ghostcell/) and +[`qcell`](https://docs.rs/qcell/0.4.1/qcell/). + +Barbara would like to use statically-checked cell borrowing within +futures, but there is no way to get the owner borrow through the +`Future::poll` call, i.e. there is no argument or object that the +runtime could save the borrow in. Mostly this does not cause a +problem, because there are other ways for a runtime to share data, +e.g. data can be incorporated into the future when it is created. +However in this specific case, for the specific technique of +statically-checked cell borrows, we need an active borrow to the owner +to be passed down the call stack through all the poll calls. + +So Barbara is forced to use `RefCell` instead and be very careful not +to cause panics. This seems like a step back. It feels dangerous to +use `RefCell` and to have to manually verify that her cell borrows are +panic-free. + +There are good habits that you can adopt to offset the dangers, of +course. If you are very careful to make sure that you call no other +method or function which might in turn call code which might attempt +to get another borrow on the same cell, then the `RefCell::borrow_mut` +panics can be avoided. However this is easy to overlook, and it is +easy to fail to anticipate what indirect calls will be made by a given +call, and of course this may change later on due to maintenance and +new features. A borrow may stay active longer than expected, so calls +which appear safe might actually panic. Sometimes it's necessary to +manually drop the borrow to be sure. In addition you'll never know +what indirect calls might be made until all the possible code-paths +have been explored, either through testing or through running in +production. + +So Barbara prefers to avoid all these problems, and use +statically-checked cell borrowing where possible. + + +### Example 1: Accessing an object shared outside the runtime + +In this minimized example of code to interface a stream to code +outside of the async/await system, the buffer has to be accessible +from both the stream and the outside code, so it is handled as a +`Rc>>`. + +```rust +pub struct StreamPipe { + buf: Rc>>, + req_more: Rc, +} + +impl Stream for StreamPipe { + type Item = T; + + fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + let mut buf = self.buf.borrow_mut(); + if let Some(item) = buf.value.take() { + return Poll::Ready(Some(item)); + } + if buf.end { + return Poll::Ready(None); + } + (self.req_more)(); // Callback to request more data + Poll::Pending + } +} +``` + +Probably `req_more()` has to schedule some background operation, but +if it doesn't and attempts to modify the shared `buf` immediately then +we get a panic, because `buf` is still borrowed. The real life code +could be a lot more complicated, and the required combination of +conditions might be harder to hit in testing. + +With statically-checked borrowing, the borrow would be something like +`let mut buf = self.buf.rw(cx);`, and the `req_more` call would either +have to take the `cx` as an argument (forcing the previous borrow to +end) or would not take `cx`, meaning that it would always have to +defer the access to the buffer to other code, because without the `cx` +there is no possible way to access the buffer. + + +### Example 2: Shared monitoring data + +In this example, the app keeps tallies of various things in a +`Monitor` structure. This might be data in/out, number of errors +detected, maybe a hashmap of current links, etc. Since it is accessed +from various components, it is kept behind an `Rc>`. + +```rust +// Dependency: futures-lite = "1.11.3" +use std::cell::RefCell; +use std::rc::Rc; + +fn main() { + let monitor0 = Rc::new(RefCell::new(Monitor { count: 0 })); + let monitor1 = monitor0.clone(); + + let fut0 = async move { + let mut borrow = monitor0.borrow_mut(); + borrow.count += 1; + }; + + let fut1 = async move { + let mut borrow = monitor1.borrow_mut(); + borrow.count += 1; + fut0.await; + }; + + futures_lite::future::block_on(fut1); +} + +struct Monitor { + count: usize, +} +``` + +The problem is that this panics with a borrowing error because the +borrow is still active when the `fut0.await` executes and attempts +another borrow. The solution is to remember to drop the borrow before +awaiting. + +In this example code the bug is obvious, but in real life maybe `fut0` +only borrows in rare situations, e.g. when an error is detected. Or +maybe the future that borrows is several calls away down the +callstack. + +With statically-checked borrowing, there is a slight problem in that +currently there is no way to access the poll context from `async {}` +code. But if there was then the borrow would be something like `let +mut borrow = monitor1.rw(cx);`, and since the `fut0.await` implicitly +requires the `cx` in order to poll, the borrow would be forced to end +at that point. + + +## Further investigation by Barbara + +### The mechanism + +Barbara understands that statically-checked cell borrows work by +having an owner held by the runtime, and various instances of a cell +held by things running on top of the runtime (these cells would +typically be behind `Rc` references). A mutable borrow on the owner +is passed down the stack, which enables safe borrows on all the cells, +since a mutable borrow on a cell is enabled by temporarily holding +onto the mutable borrow of the owner, which is all checked at +compile-time. + +So the mutable owner borrow needs to be passed through the `poll` +call, and Barbara realizes that this would require support from the +standard library. + +Right now a `&mut Context<'_>` is passed to `poll`, and so within +`Context` would be the ideal place to hold a borrow on the cell owner. +However as far as Barbara can see there are difficulties with all the +current implementations: + +- GhostCell (or qcell::LCell) may be the best available solution, + because it doesn't have any restrictions on how many runtimes might + be running or how they might be nested. But Rust insists that the + lifetimes `<'id>` on methods and types are explicit, so it seems + like that would force a change to the signature of `poll`, which + would break the ecosystem. + + Here Barbara experiments with a working example of a modified Future + trait and a future implementation that makes use of LCell: + +```rust +// Requires dependency: qcell = "0.4" +use qcell::{LCell, LCellOwner}; +use std::pin::Pin; +use std::rc::Rc; +use std::task::Poll; + +struct Context<'id, 'a> { + cell_owner: &'a mut LCellOwner<'id>, +} + +struct AsyncCell<'id, T>(LCell<'id, T>); +impl<'id, T> AsyncCell<'id, T> { + pub fn new(value: T) -> Self { + Self(LCell::new(value)) + } + pub fn rw<'a, 'b: 'a>(&'a self, cx: &'a mut Context<'id, 'b>) -> &'a mut T { + cx.cell_owner.rw(&self.0) + } +} + +trait Future<'id> { + type Output; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'id, '_>) -> Poll; +} + +struct MyFuture<'id> { + count: Rc>, +} +impl<'id> Future<'id> for MyFuture<'id> { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'id, '_>) -> Poll { + *self.count.rw(cx) += 1; + Poll::Ready(()) + } +} + +fn main() { + LCellOwner::scope(|mut owner| { + let mut cx = Context { cell_owner: &mut owner }; + let count = Rc::new(AsyncCell::new(0_usize)); + let mut fut = Box::pin(MyFuture { count: count.clone() }); + let _ = fut.as_mut().poll(&mut cx); + assert_eq!(1, *count.rw(&mut cx)); + }); +} +``` + +- The other `qcell` types (QCell, TCell and TLCell) have various + restrictions or overheads which might make them unsuitable as a + general-purpose solution in the standard library. However they do + have the positive feature of not requiring any change in the + signature of `poll`. It looks like they could be added to `Context` + without breaking anything. + + Here Barbara tries using `TLCell`, and finds that the signature of + `poll` doesn't need to change: + +```rust +// Requires dependency: qcell = "0.4" +use qcell::{TLCell, TLCellOwner}; +use std::pin::Pin; +use std::rc::Rc; +use std::task::Poll; + +struct AsyncMarker; +struct Context<'a> { + cell_owner: &'a mut TLCellOwner, +} + +struct AsyncCell(TLCell); +impl AsyncCell { + pub fn new(value: T) -> Self { + Self(TLCell::new(value)) + } + pub fn rw<'a, 'b: 'a>(&'a self, cx: &'a mut Context<'b>) -> &'a mut T { + cx.cell_owner.rw(&self.0) + } +} + +trait Future { + type Output; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; +} + +struct MyFuture { + count: Rc>, +} +impl Future for MyFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + *self.count.rw(cx) += 1; + Poll::Ready(()) + } +} + +fn main() { + let mut owner = TLCellOwner::new(); + let mut cx = Context { cell_owner: &mut owner }; + let count = Rc::new(AsyncCell::new(0_usize)); + let mut fut = Box::pin(MyFuture { count: count.clone() }); + let _ = fut.as_mut().poll(&mut cx); + assert_eq!(1, *count.rw(&mut cx)); +} +``` + + (For comparison, `TCell` only allows one owner per marker type in + the whole process. `QCell` allows many owners, but requires a + runtime check to make sure you're using the right owner to access a + cell. `TLCell` allows only one owner per thread per marker type, + but also lets cells migrate between threads and be borrowed locally, + which the others don't -- see [qcell + docs](https://docs.rs/qcell/0.4.1/qcell/).) + +So the choice is GhostCell/LCell and lifetimes everywhere, or various +other cell types that may be too restrictive. + +Right now Barbara thinks that none of these solutions is likely to be +acceptable for the standard library. However still it is a desirable +feature, so maybe someone can think of a way around the problems. Or +maybe someone has a different perspective on what would be acceptable. + +### Proof of concept + +The [Stakker](https://crates.io/crates/stakker) runtime makes use of +qcell-based statically-checked cell borrowing. It uses this to get +zero-cost access to actors, guaranteeing at compile time that no actor +can access any other actor's state. It also uses it to allow +inter-actor [shared +state](https://docs.rs/stakker/0.2.1/stakker/struct.Share.html) to be +accessed safely and zero-cost, without RefCell. + +(For example within a Stakker actor, you can access the contents of a +`Share` via the actor context `cx` as follows: `share.rw(cx)`, +which blocks borrowing or accessing `cx` until that borrow on `share` +has been released. `Share` is effectively a `Rc` and +`cx` has access to an active borrow on the `ShareCellOwner`, just as +in the long examples above.) + +Stakker doesn't use GhostCell (LCell) because of the need for `<'id>` +annotations on methods and types. Instead it uses the other three +cell types according to how many Stakker instances will be run, either +one Stakker instance only, one per thread, or multiple per thread. +This is selected by cargo features. + +Switching implementations like this doesn't seem like an option for +the standard library. + +### Way forward + +Barbara wonders whether there is any way this can be made to work. +For example, could the compiler derive all those `<'id>` annotations +automatically for GhostCell/LCell? + +Or for multi-threaded runtimes, would +[`qcell::TLCell`](https://docs.rs/qcell/0.4.1/qcell/) be acceptable? +This allows a single cell-owner in every thread. So it would not +allow nested runtimes of the same type. However it does allow borrows +to happen at the same time independently in different threads, and it +also allows the migration of cells between threads, which is safe +because that kind of cell isn't `Sync`. + +Or is there some other form of cell-borrowing that could be devised +that would work better for this? + +The interface between cells and `Context` should be straightforward +once a particular cell type is demonstrated to be workable with the +`poll` interface and futures ecosystem. For example copying the API +style of Stakker: + +``` +let rc = Rc::new(AsyncCell::new(1_u32)); +*rc.rw(cx) = 2; +``` + +So logically you obtain read-write access to a cell by naming the +authority by which you claim access, in this case the poll context. +In this case it really is naming rather than accessing since the +checks are done at compile time and the address that `cx` represents +doesn't actually get passed anywhere or evaluated, once inlining and +optimisation is complete. + + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +The main problem is that Barbara has got used to a safer environment +and it feels dangerous to go back to RefCell and have to manually +verify that her cell borrows are panic-free. + +### **What are the sources for this story?** + +The author of Stakker is trying to interface it to async/await and +futures. + +### **Why did you choose Barbara to tell this story?** + +Barbara has enough Rust knowledge to understand the benefits that +GhostCell/qcell-like borrowing might bring. + +### **How would this story have played out differently for the other characters?** + +The other characters perhaps wouldn't have heard of statically-checked +cell borrows so would be unaware of the possibility of making things +safer. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/barbara_wishes_for_easy_runtime_switch.md b/src/vision/submitted_stories/status_quo/barbara_wishes_for_easy_runtime_switch.md new file mode 100644 index 00000000..d05b0150 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_wishes_for_easy_runtime_switch.md @@ -0,0 +1,73 @@ +# 😱 Status quo stories: Barbara wishes for an easy runtime switch + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from +real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async +Rust programmers face today. + +## The story + +[Barbara] has been working on an async codebase for the past 5 years. It is extremely mature and +quite large (in the millions of lines of code). They've been relying on tokio as their async runtime +and the codebase makes heavy use of its rich API. It has served them well over the years and they're +very happy with it. + +Barbara knows about async-std but has never used it. She has wondered for a while how her +application would work and perform if she had used async-std instead. She decides to test it out by +porting her projects from tokio to async-std. + +To their disappointment, they discover many areas, where their choice of runtime permeates the code +base: + +* tokio provides variants of helpers macros and types, like `tokio::select!` and `tokio::Mutex`. + These helpers can be used without the rest of tokio, and there are also alternatives from the + `futures` crate and elsewhere (albeit with subtle differences). +* tokio uses a custom version of `AsyncRead` and `AsyncWrite` traits which differ from the ones used + by other parts of the ecosystem. +* The tokio API is needed to create core runtime operations like timers (`tokio::time::sleep`) and + to launch tasks; there doesn't seem to be a standard way to abstract over those kinds of things in + a runtime-independent way. +* Some of their dependencies (e.g `hyper` and `reqwest`) are tied to tokio. In some cases, there are + configuration options or ways to use those dependencies that don't depend on tokio, but there is + no standard mechanism for that. + +These things aren't specific to tokio. There just doesn't seem to be a lot of consensus in the +ecosystem on how to write "runtime-independent" code and in some cases there aren't any great +options available (e.g., spawning tasks). + +They investigate the possibility of providing some sort of compatibility layer between tokio +and their new runtime of choice but this turns out to not seem like the right way to go as this +compatibility layer would require too much overhead. + +Realizing that the task of porting the entire code base to async-std, will take a lot of effort and +time, Barbara decides to give up. She is very disappointed. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** +* Using a certain executor often means using a certain run-time ecosystem. This often locks the user + into that ecosystem. +* Tying yourself to a certain executor means that you are tied to the priorities of that executor. + You may be happy with the run-time ecosystem, but have special needs that the default executor + does not provide. If the executor doesn't have an extensibility model, you're stuck. **Note:** + It is perfectly reasonable for a general purpose executor to not be able or willing to cater for + specialized needs. +* All of this is made worse by that fact that [run-time agnostic libraries] are difficult and + sometimes even impossible to write. + +### **What are the sources for this story?** + +This story is more of a thought experiment than a recounting of a true story. We just asked +logically what would happen if a team working on code base where it was assumed they could use a +specific runtime decides to use a different runtime. + +### **Why did you choose Barbara to tell this story?** +The story assumes a Rust programmer that has worked for several years on a large and complex Rust +codebase, so Barbara is the natural choice here. + +### **How would this story have played out differently for the other characters?** +It wouldn't. If this story happens them, they're on the same level of Rust expertise as Barbara is. + +[Barbara]: ../../characters/barbara.md +[run-time agnostic libraries]: https://github.com/rust-lang/wg-async/issues/45 diff --git a/src/vision/submitted_stories/status_quo/barbara_writes_a_runtime_agnostic_lib.md b/src/vision/submitted_stories/status_quo/barbara_writes_a_runtime_agnostic_lib.md new file mode 100644 index 00000000..ff04f57a --- /dev/null +++ b/src/vision/submitted_stories/status_quo/barbara_writes_a_runtime_agnostic_lib.md @@ -0,0 +1,127 @@ +# 😱 Status quo stories: Barbara writes a runtime-agnostic library + + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from +real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async +Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR +making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories +[cannot be wrong], only inaccurate). Alternatively, you may wish to +[add your own status quo story][htvsq]! + +## The story + +Barbara and Alan work at AmoolgeSoft, where many teams are switching from Java to Rust. These teams +have many different use cases and various adoption stories. Some teams are happy users of tokio, +others happy users of async-std, and others still are using custom runtimes for highly specialized +use cases. + +Barbara is tasked with writing a library for a custom protocol, SLOW (only in use at AmoogleSoft) +and enlists the help of Alan in doing so. Alan is already aware that [not all libraries in Rust work +with all runtimes][nalirwwar]. Alan and Barbara start by writing a parser which works on +`std::io::Read` and get their tests working with `String`s. After this they contemplate the question +of how to accept a TCP connection. + +### Incompatible `AsyncRead` traits + +Alan asks Barbara what is the async equivalent is of `std::io::Read`, and Barbara sighs and says +that there isn't one. Barbara brings up tokio's and the [futures crate]'s versions of `AsyncRead`. +Barbara decides not to talk about `AsyncBufRead` for now. + +Barbara and Alan decide to use the future's `AsyncRead` for no other reason other than it is +runtime-agnostic. Barbara tells Alan not to worry as they can translate between the two. With +[some](ahwas) [effort](bnah) they convert their parser to using `AsyncRead`. + +Alan, excited about the progress they've made, starts working on hooking this up to actual TCP +streams. Alan looks at async-std and tokio and notices their interfaces for TCP are quite different. +Alan waits for Barbara to save the day. + +Barbara helps abstract over TCP listener and TCP stream (**TODO:** code example). One big hurdle is +that tokio uses `AsyncRead` from their own crate and not the one from `futures` crate. + +### Task spawning + +After getting the TCP handling part working, they now want to spawn tasks for handling each incoming +TCP connection. Again, to their disappointment, they find that there's no runtime-agnostic way to do +that. + +Unsure on how to do this, they do some searching and find the [`agnostik`] crate. They reject it +because this only supports N number of runtimes and their custom runtime is not one of them. +However it gives them the idea to provide a trait for specifying how to spawn tasks on the runtime. +Barbara points out that this has disadvantage of [working against orphan rules] meaning that either +they have to implement the trait for all known runtimes (defeating the purpose of the exercise) or +force the user to use new types. + +They punt on this question by implementing the trait for each of the known runtimes. They're +disappointed that this means their library actually isn't runtime agnostic. + +### The need for timers + +To make things further complicated, they also are in need for a timer API. They could abstract +runtime-specific timer APIs in their existing trait they use for spawning, but they find a +runtime-agnostic library. It works but is pretty heavy in that it spawns an OS thread (from a pool) +every time they want to sleep. They become sadder. + +### Channels + +They need channels as well but after long searches and discussions on help channels, they learn of +a few runtime-agnostic implementations: `async-channel`, `futures-channel`, and trimmed down ( +through feature flags) `async-std`/`tokio`. They pick one and it seems to work well. They become +less sadder. + +### First release + +They get things working but it was a difficult journey to get to the first release. Some of their +users find the APIs harder to use than their runtime-specific libs. + +## 🤔 Frequently Asked Questions + +*Here are some standard FAQ to get you started. Feel free to add more!* + +### **Why did you choose Barbara to tell this story?** +[Barbara] has years of rust experience that she brings to bear in her async learning experiences. + +### **What are the morals of the story?** + +* People have to roll their own implementations which can lead to often subtle differences between + runtimes (For example TCPListeners in `async-std` and `tokio`). +* Orphan rules and no standard traits guarantee that a truly agnostic library is not possible. +* Takes way more time than writing synchronous protocols. +* It's a hard goal to achieve. +* Leads to poorer APIs sometimes (both in ease of use and **performance**). +* More API design considerations need to go into making an generic async library than a generic sync library. + +### **What are the sources for this story?** +Personal experiences of the author from adding async API in [`zbus`] crate, except for `AsyncRead`, +which is based on common knowledge in async Rust community. + +### **How would this story have played out differently for the other characters?** +Alan, Grace, and Niklaus would be overwhelmed and will likely want to give up. + +### What are other related stories? + +**TODO:** + +### What are the downside of using runtime agnostic crates? + +Some things can be implemented very efficiently in a runtime-agnostic way but even then you can't +integrate deeply into the runtime. For example, see tokio’s preemption strategy, which relies on +deep integration with the runtime. + +### What other runtime utilities are generally needed? +* [async-locks][async-locks-story] + +[status quo stories]: ../status_quo.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[ahwas]: https://rust-lang.github.io/wg-async/vision/status_quo/alan_hates_writing_a_stream.html +[bnah]: https://rust-lang.github.io/wg-async/vision/status_quo/barbara_needs_async_helpers.html +[working against orphan rules]: https://github.com/rust-lang/wg-async/issues/180 +[futures crate]: https://crates.io/crates/futures +[nalirwwar]: https://rust-lang.github.io/wg-async/vision/status_quo/alan_picks_web_server.html#first-problem-incompatible-runtimes +[`agnostik`]: https://crates.io/crates/agnostik +[`zbus`]: https://crates.io/crates/zbus/2.0.0-beta.3 +[async-locks-story]: https://rust-lang.github.io/wg-async/vision/status_quo/alan_thinks_he_needs_async_locks.html diff --git a/src/vision/submitted_stories/status_quo/grace_debugs_a_crash_dump.md b/src/vision/submitted_stories/status_quo/grace_debugs_a_crash_dump.md new file mode 100644 index 00000000..32c289ed --- /dev/null +++ b/src/vision/submitted_stories/status_quo/grace_debugs_a_crash_dump.md @@ -0,0 +1,134 @@ +# 😱 Status quo stories: Grace debugs a crash dump + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +[Grace] is an engineer working on a hosted [DistriData] service, similar to [Azure Cosmos DB] or [Amazon DynamoDB]. Sometimes one of the DistriData nodes panics. There is a monitor system that catches these panics, saves a crash dump, and restarts the service. The crash dumps can be analyzed after the fact to try to debug the issue. + +After a recent version push, there has been an increase in the number of panics. This represents a threat to the service's overall reliability, so Grace has been tasked to investigate. Grace is known as one of the team's best debuggers, with years of experience diagnosing tricky issues from crash dumps. With C and C++ code, Grace can see raw hex dumps and decode the underlying data structures in her head. + +Despite this, Grace is relatively new to Rust and is still developing this intuition for Rust code. To get started, Grace hopes her debugger will help her get started. What executors are running? What tasks are running? What state were they in? + +She starts by looking at a backtrace: + +``` +[dbg] bt + 0 0x407e5a7cae11 • syscalls.inc:675 + 1 _zx_port_wait(…) • syscalls.inc:675 + handle = 1569127495 + deadline = 9223372036854775807 + packet = (*)0x1aea3201dc8 + + 2 distridata_zircon::port::Port::wait(…) • src/port.rs:323 + self = (*)0x3f0f481a580 ➔ Port(Handle(…)) + deadline = Time(9223372036854775807) + + 3 λ(…) • default/../../src/lib/distridata-async/src/executor.rs:397 + timer_heap = (*)0x2116e3c3a00 ➔ BinaryHeap[] + + 4 λ(…) • default/../../src/lib/distridata-async/src/executor.rs:316 + e = (*)0x2116e3c39f0 ➔ RefCell{borrow: Cell{…}, value: UnsafeCell{…}} + + 5 std::thread::local::LocalKey<…>::try_with<…>(…) • thread/local.rs:262 + self = (*)0x3816da0c9b0 ➔ LocalKey{inner: &distridata_async::executor::EXECUTOR::__getit} + f = $(closure-0)($(closure-0)((*)0x1aea32022a0)) + + 6 std::thread::local::LocalKey<…>::with<…>(…) + 0x27 (no line info) + self = (*)0x3816da0c9b0 ➔ LocalKey{inner: &distridata_async::executor::EXECUTOR::__getit} + f = $(closure-0)($(closure-0)((*)0x1aea32022a0)) + + 7 distridata_async::executor::with_local_timer_heap<…>(…) + 0x2a (no line info) + f = $(closure-0)((*)0x1aea32022a0 ➔ (*)0x1aea3202758) + +▶ 8 distridata_async::executor::Executor::run_singlethreaded<…>(…) • default/../../src/lib/distridata-async/src/executor.rs:393 + self = (*)0x1aea3202758 ➔ Executor{inner: (*)0x3f0f481a380, next_packet: …} + main_future = GenFuture(Unresumed) + + 9 distridata_pkg_testing_lib_test::serve::tests::test_serve_empty() • serve.rs:345 + + 10 λ(…) • serve.rs:345 + (*)0x1aea3202b80 ➔ $(closure-0) + + 11 core::ops::function::FnOnce::call_once<…>(…) • function.rs:232 + $(closure-0) + +``` + +The backtrace shows a lot of detail about the executor, but not of this is really relevant to Grace's code. She will have to inspect the executor manually in order to find the information she needs. Frame 8 looks promising, so the finds the local variables there and sees one called `main_future`. Inspecting the code, she sees this has a `pointer` field, which might tell her something about the task that's running. She takes a look: + +``` +[dbg] print -t --max-array=2 main_future.pointer +(std::future::GenFuture*) 0x1aea32022a8 ➔ std::future::GenFuture( + (distridata_pkg_testing_lib_test::serve::tests::test_serve_empty::func::generator-0) distridata_pkg_testing_lib_test::serve::tests::test_serve_empty::func::$(generator-0)::Suspend6{ + packages: alloc::vec::Vec[] + bytes: alloc::vec::Vec[123, 34, ] + (alloc::string::String) url: "ht"... + also_bytes: alloc::vec::Vec[123, 34, ] + pinned: std::future::GenFuture( + distridata_pkg_testing_lib_test::serve::get::$(generator-0){ + (alloc::string::String) __0: "ht"... + } + ) + } +) +``` + +This has some more information, but it is still not as helpful as Grace was hoping for. + +Grace quickly realizes her tools are not going to give her as much help as she'd like. She does manage to find the executor in memory, so she starts reading the code to understand how tasks are laid out in memory, etc. Even once she finds the list of tasks, she can only see the opaque contents of the closure. It is hard even to track these back to a line number, or to what operating system resource the task is blocked on (IOCP handle, io_uring event, etc.). + +She realizes this is going to take a lot longer than it would if this were a C++ service, so she gets up to grab another cup and coffee and then settles in for a long debugging session. + +[Azure Cosmos DB]: https://azure.microsoft.com/en-us/services/cosmos-db/ +[Amazon DynamoDB]: https://aws.amazon.com/dynamodb/ + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +While much of the focus for async debugger is on the live debugging case, where a developer is running a build on their own machine, there will also be a need to debug crashes after the fact. For example, an application running on a consumer's device may upload crash dumps automatically, or a service running in a cloud environment may also collect a crash dump before restarting the server. Often the bugs that show up in these scenarios are hard to reproduce on a developer's machine, so the more information it's possible to glean from a crash dump, the better. + +Even just an accurate and complete stack trace can help a lot. Many error reporting systems cluster crashes by stack trace, so having an incomplete stack trace can lead to unrelated crashes being grouped together. + +### **What are the sources for this story?** + +This is inspired by requests from internal teams looking to expand the use of Rust in services they develop. + +This story also includes some input from Fuchsia developers, including a bug they have about getting [async backtraces in the debugger](https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=49435). + +### **Why did you choose Grace to tell this story?** + +Grace is part of a team of experienced systems hackers who have recently migrated to Rust because of its safety guarantees while still maintaining high performance. Grace is used to debugging these kinds of issues in a certain way, and would like to transfer these skills to Rust. + +### **How would this story have played out differently for the other characters?** + +This could happen to [Alan] or [Barbara] as well. In Alan's case, he may be used to C# and Visual Studio's `async` debugger tools. He'd probably miss those tools and wish support for something similar could be added to his IDE. + +In [Niklaus]'s case, he would probably need to ask one of his more experienced team mates to help him debug the issue. With better tooling, he'd probably be able to get further on his own. + +### **What other stories are related to this one?** + +* In [Alan tries to debug a hang](alan_tries_to_debug_a_hang.md), Alan misses some of the strong debugging tools he's used in the past. Grace would enjoy using those same tools if they worked on crash dumps in addition to live processes. +* In [Barbara wants async insights][async-insights], Barbara wants to use a debugger to inspect a running process. Most of the insights Barbara is looking for in that situation would also be relevant to Grace in a post-hoc debugging situation. +* In [Barbara gets burned by select](barbara_gets_burned_by_select.md), Barbara has trouble debugging an issue where not all database updates are processed. Similar debugging tools would help both Barbara and Grace. +* In [Grace deploys her service and hits obstacles](grace_deploys_her_service.md), Grace finds a tricky issue in production that only appears at high load. Because she doesn't have the right tooling to debug, she resorts to ad hoc logging, combined with some operating system tools. She could have benefited from the ability to inspect what is blocking tasks in an executor as well. +* In [Grace waits for `gdb next`](grace_waits_for_gdb_next.md), Grace finds that her usual debugging techniques do not work well with async programs. + +* This is tangentially related to the story [Alan iteratively regresses performance](alan_iteratively_regresses.md), because there Alan was used to applying existing native tools to Rust, even though there is sometimes an impedence mismatch. The mismatch is likely to be even more challenging for async debugging, since this scenario is already not well supported in a lot of existing tools. + + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[DistriData]: ../../projects/DistriData.md +[async-insights]: barbara_wants_async_insights.md \ No newline at end of file diff --git a/src/vision/status_quo/grace_deploys_her_service.md b/src/vision/submitted_stories/status_quo/grace_deploys_her_service.md similarity index 93% rename from src/vision/status_quo/grace_deploys_her_service.md rename to src/vision/submitted_stories/status_quo/grace_deploys_her_service.md index bb37aaef..e2870108 100644 --- a/src/vision/status_quo/grace_deploys_her_service.md +++ b/src/vision/submitted_stories/status_quo/grace_deploys_her_service.md @@ -1,11 +1,11 @@ # 😱 Status quo stories: Grace deploys her service and hits obstacles -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md -[Grace deploys her service and is able to fix problems]: ./shiny_future.md#grace-deploys-her-service-and-is-able-to-fix-problems +[Grace deploys her service and is able to fix problems]: ../shiny_future.md#grace-deploys-her-service-and-is-able-to-fix-problems ## 🚧 Warning: Draft status 🚧 diff --git a/src/vision/status_quo/grace_tries_new_libraries.md b/src/vision/submitted_stories/status_quo/grace_tries_new_libraries.md similarity index 96% rename from src/vision/status_quo/grace_tries_new_libraries.md rename to src/vision/submitted_stories/status_quo/grace_tries_new_libraries.md index 68ae6baf..762af367 100644 --- a/src/vision/status_quo/grace_tries_new_libraries.md +++ b/src/vision/submitted_stories/status_quo/grace_tries_new_libraries.md @@ -1,9 +1,9 @@ # 😱 Status quo stories: Grace tries new libraries -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md ## 🚧 Warning: Draft status 🚧 @@ -102,8 +102,6 @@ From her background she has an understanding what could go wrong. So she remembe ## 🤔 Frequently Asked Questions -## 🤔 Frequently Asked Questions - ### **What are the morals of the story?** * Working with async can give huge errors from fairly common place transforms, and requires knowing some "not entirely obvious" workarounds. @@ -121,7 +119,7 @@ From her background she has an understanding what could go wrong. So she remembe * Ultimately the only way to know how to solve this problem is to have seen it before and learned how to solve it. The compiler doesn't help and the result is not obvious. * So it probably doesn't matter that much which character is used, except that Barbara may be more likely to have seen how to solve it. -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/grace_waits_for_gdb_next.md b/src/vision/submitted_stories/status_quo/grace_waits_for_gdb_next.md similarity index 94% rename from src/vision/grace_waits_for_gdb_next.md rename to src/vision/submitted_stories/status_quo/grace_waits_for_gdb_next.md index 1228cb24..d765fb91 100644 --- a/src/vision/grace_waits_for_gdb_next.md +++ b/src/vision/submitted_stories/status_quo/grace_waits_for_gdb_next.md @@ -1,3 +1,5 @@ +# Status quo: Grace waits for gdb next + ## 🚧 Warning: Draft status 🚧 This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. @@ -90,14 +92,14 @@ I needed someone who, like me, would actually be tempted to use `gdb` even when ### **How would this story have played out differently for the other characters?** * Alan might have used whatever debugger is offered by his IDE, which might have the same problem (via a toolbar button that has the same semantics as `next`); but many people using IDE's to debugger just naturally set breakpoints by hand on the lines in their IDE editor, and thus will not run into this. - * Most characters would probably have abandoned using gdb much sooner. E.g. Grace may have started out by adding `println` or `tracing` instrumention to the code, rather than trying to open it up in a debugger. + * Most characters would probably have abandoned using gdb much sooner. E.g. Grace may have started out by adding `println` or `tracing` instrumentation to the code, rather than trying to open it up in a debugger. -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/grace_wants_a_zero_copy_api.md b/src/vision/submitted_stories/status_quo/grace_wants_a_zero_copy_api.md new file mode 100644 index 00000000..b00cc5a7 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/grace_wants_a_zero_copy_api.md @@ -0,0 +1,78 @@ +# 😱 Status quo stories: Grace wants a zero-copy API + +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +## The story + +Grace had written lots of operating system code in the past, and up until recently was working on a project using [DPDK](https://www.dpdk.org/) for zero-copy networking. The vast majority of the bugs +that Grace found were related to memory (mis)management, so she is excited for the prospect of trying Rust as part of her new job. + +However, Grace has a hard time getting this to work without heavily resorting to `unsafe` constructs. As she evolves her undertanding of Rust, she looks hopefully at the signature of `poll_read`: + +```rust + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context, + buf: &mut [u8] + ) -> Poll> +``` + +She notices that the buffer is always passed to the invocation, but she can't pass it down to the operating system: because in rust-async tasks can be canceled at any time, which would +free the buffer, those buffers are not guaranteed to be alive throughout the entire operation and there is no good way to extend their lifetime. There needs to be at least one copy! + +Grace hears from her coworkers that they are all using Tokio anyway. But the Tokio traits, although different from the standard traits, are not much better: + +```rust + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_> + ) -> Poll>; +``` + +There's a specialized type for the buffer, but its management and lifetime are still not suitable for zero-copy I/O. + +Grace then came across a famous [blog post](https://boats.gitlab.io/blog/post/io-uring/) from a seasoned developer that mentions +another trait, `AsyncBufRead`, but she immediately identifies two issues with that: + +* There is not a similar trait for writes, which suffer from much the same problem +* Grace's team is already using a plethora of convenience traits built upon these base traits, including `AsyncReadExt` and `AsyncBufReadExt`, + and they all pass a buffer, forcing a copy. + +Grace now has no good choices: she can live with the performance penalty of the copies, which lets her down since she how has the feeling she +could do more with C++, or she can come up with her own specialized traits, which will make her work harder to consume by her team. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** + +* The cancellation problem and buffer lifetimes make it impossible to keep a user-provided buffer alive. That makes zero-copy I/O much harder +than it could be. + +### **What are the sources for this story?** + +* Personal experience. + +### **Why did you choose Grace to tell this story?** + +* Grace has experience with C/C++, which is still the de-facto language for very low level things like zero-copy. The author had a similar experience +when trying to expose zero-copy APIs. + +### **How would this story have played out differently for the other characters?** + +* Zero-copy I/O is an important, but fairly niche use case that requires specialized prior knowledge that usually is only found among system-level + programmers. +* That is usually done in C/C++, and Grace is the only one that is very likely to have this experience. +* There is a chance Barbara would have ventured into similar problems. She would likely have had a similar experience than Grace. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/status_quo/grace_wants_to_integrate_c_api.md b/src/vision/submitted_stories/status_quo/grace_wants_to_integrate_c_api.md similarity index 98% rename from src/vision/status_quo/grace_wants_to_integrate_c_api.md rename to src/vision/submitted_stories/status_quo/grace_wants_to_integrate_c_api.md index 372328b9..05250f74 100644 --- a/src/vision/status_quo/grace_wants_to_integrate_c_api.md +++ b/src/vision/submitted_stories/status_quo/grace_wants_to_integrate_c_api.md @@ -1,9 +1,9 @@ # 😱 Status quo stories: Grace wants to integrate a C-API -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md [bindgen]: //docs.rs/bindgen/ [`stream::unfold`]: //docs.rs/futures/0.1.17/futures/stream/fn.unfold.html @@ -67,7 +67,7 @@ Something Grace and her team learn to love immediately about Rust is that writin team to write their own execution environment. In fact, the future can be entirely written independently of the execution environment. She quickly writes an async method to represent the polling process: -```rust,ignore +```rust /// Gets the next frame from the camera, waiting `retry_after` time until polling again if it fails. /// /// Returns Some(frame) if a frame is found, or None if the camera is disconnected or goes down before a frame is @@ -100,7 +100,7 @@ unspecified lengths of time. Grace spends some time searching, and realizes that of some kind. `Stream` objects are the asynchronous equivalent of iterators, and her team wants to eventually write something akin to: -```rust,ignore +```rust let frame_stream = stream_from_camera(camera, Duration::from_millis(5)); while let Some(frame) = frame_stream.next().await { @@ -115,7 +115,7 @@ executed many times. The only available option to transform a future into a seri which seems to do exactly what Grace is looking for. Grace begins by adding a small intermediate type, and then plugging in the remaining holes: -```rust,ignore +```rust struct StreamState { camera: Camera, retry_after: Duration, @@ -142,7 +142,7 @@ Grace spends a lot of time trying to figure out how she might find those types! way to get around this in Rust would be. Barbara explains again how closures don't have concrete types, and that the only way to do this will be to use the `impl` keyword. -```rust,ignore +```rust fn stream_from_camera(camera: Camera, retry_after: Duration) -> impl Stream { // same as before } diff --git a/src/vision/submitted_stories/status_quo/grace_writes_a_new_fb_service.md b/src/vision/submitted_stories/status_quo/grace_writes_a_new_fb_service.md new file mode 100644 index 00000000..0a37c33c --- /dev/null +++ b/src/vision/submitted_stories/status_quo/grace_writes_a_new_fb_service.md @@ -0,0 +1,72 @@ +# 😱 Status quo stories: Template + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +This tells the story of Grace, an engineer working at Facebook on C++ +services. + +* Grace writes C++ services at Facebook, built upon many libraries and support + infrastructure +* Grace's last project had several bad bugs related to memory safety, and she + is motivated to give Rust a shot on a new service she's writing +* First, she must determine if there are Rust bindings to the other FB + services her new service will depend on +* She determines that she'll need to write a binding to the FooDB service + using cxx +* She also determines that several crates she'll need from crates.io aren't + vendored in the FB monorepo, so she'll need to get them and their + dependencies imported. She'll need to address any version conflicts and + special build rules since FB uses Buck and not Cargo to build all code +* While developing her service, Grace discovers that IDE features she's used + to in VS Code don't always work for Rust code +* Grace writes up the performance and safety benefits of her new service after + it's first month of deployment. Despite the tooling issues, the end result + is a success + +## 🤔 Frequently Asked Questions + +*Here are some standard FAQ to get you started. Feel free to add more!* + +### **What are the morals of the story?** + +* Building successful Rust services in a company that has lots of existing + tooling and infrastructure can be difficult, as Grace must do extra work + when new ground is tread + * Big companies like Facebook have large monorepos and custom build systems + and the standard Rust tooling may not be useable in that environment + * Facebook has a large team making developer's lives easier, but it is + focused around the most common workflows, and Grace must work a little + harder for now as Rust support is in its early days + * Integrating with existing C++ code is quite important as Grace cannot + rewrite existing services + +### **What are the sources for this story?** + +This story is compiled from internal discussions with Facebook engineers and +from internal reports of successful Rust projects. + +### **Why did you choose Grace to tell this story?** + +Both Alan or Grace could be appropriate, but I chose Grace in order to focus +on tooling and C++ service integration issues. + +### **How would this story have played out differently for the other characters?** + +Had I chosen Alan, a Python programmer at Facebook, there is probably a lot +more learning curve with Rust's async mechanics. Python programmers using +async don't necessarily have analogs for things like `Pin` for example. + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/submitted_stories/status_quo/niklaus_simulates_hydrodynamics.md b/src/vision/submitted_stories/status_quo/niklaus_simulates_hydrodynamics.md new file mode 100644 index 00000000..7a24d6b1 --- /dev/null +++ b/src/vision/submitted_stories/status_quo/niklaus_simulates_hydrodynamics.md @@ -0,0 +1,72 @@ +# 😱 Status quo stories: Niklaus Builds a Hydrodynamics Simulator + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story +### Problem +Niklaus is a professor of physics at the University of Rustville. He needed to build a tool to solve hydrodynamics simulations; there is a common method for this that subdivides a region into a grid and computes the solution for each grid patch. All the patches in a grid for a point in time are independent and can be computed in parallel, but they are dependent on neighboring patches in the previously computed frame in time. This is a well known computational model and the patterns for basic parallelization are well established. + +Niklaus wanted to write a performant tool to compute the solutions to the simulations of his research. He chose Rust because he needed high performance but he also wanted something that could be maintained by his students, who are not professional programmers. Rust's safety guarantees giver him confidence that his results are not going to be corrupted by data races or other programming errors. After implementing the core mathematical formulas, Niklaus began implementing the parallelization architecture. + +His first attempt to was to emulate a common CFD design pattern: using message passing to communicate between processes that are each assigned a specific patch in the grid. So he assign one thread to each patch and used messages to communicate solution state to dependent patches. With one thread per patch this usually meant that there were 5-10x more threads than CPU cores. + +This solution worked, but Niklaus had two problems with it. First, it gave him no control over CPU usage so the solution would greedily use all available CPU resources. Second, using messages to communicate solution values between patches did not scale when his team added a new feature (tracer particles) the additional messages caused by this change created so much overhead that parallel processing was no faster than serial. So, Niklaus decided to find a better solution. + +### Solution Path +To address the first problem: Niklaus' new design decoupled the work that needed to be done (solving physics equations for each patch in the grid) from the workers (threads), this would allow him to set the number of threads and not use all the CPU resources. So, he began looking for a tool in Rust that would meet this design pattern. When he read about `async` and how it allowed the user to define units of work and send those to an executor which would manage the execution of those tasks across a set of workers, he thought he'd found exactly what he needed. He also thought that the `.await` semantics would give a much better way of coordinating dependencies between patches. Further reading indicated that `tokio` was the runtime of choice for `async` in the community and, so, he began building a new CFD solver with `async` and `tokio`. + +After making some progress, Niklaus ran into his first problem. Niklaus had been under a false impression about what `async` executors do. He had assumed that a multi-threaded executor could automatically move the execution of an `async` block to a worker thread. When this turned out to wrong, he went to Stackoverflow and learned that async tasks must be explicitly spawned into a thread pool if they are to be executed on a worker thread. This meant that the algorithm to be parallelized became strongly coupled to both the spawner and the executor. Code that used to cleanly express a physics algorithm now had interspersed references to the task spawner, not only making it harder to understand, but also making it impossible to try different execution strategies, since with Tokio the spawner and executor are the same object (the Tokio runtime). Niklaus felt that a better design for data parallelism would enable better separation of concerns: a group of interdependent compute tasks, and a strategy to execute them in parallel. + +Niklaus second problem came as he tried to fully replace the message passing from the first design: sharing data between tasks. He used the `async` API to coordinate computation of patches so that a patch would only go to a worker when all its dependencies had completed. But he also needed to account for the solution data which was passed in the messages. He setup a shared data structure to track the solutions for each patch now that messages would not be passing that data. Learning how to properly use shared data with `async` was a new challenge. The initial design: +```rust + let mut stage_primitive_and_scalar = |index: BlockIndex, state: BlockState, hydro: H, geometry: GridGeometry| { + let stage = async move { + let p = state.try_to_primitive(&hydro, &geometry)?; + let s = state.scalar_mass / &geometry.cell_volumes / p.map(P::lorentz_factor); + Ok::<_, HydroError>( ( p.to_shared(), s.to_shared() ) ) + }; + stage_map.insert(index, runtime.spawn(stage).map(|f| f.unwrap()).shared()); + }; +``` +lacked performance because he needed to clone the value for every task. So, Niklaus switched over to using `Arc` to keep a thread safe RC to the shared data. But this change introduced a lot of `.map` and `.unwrap` function calls, making the code much harder to read. He realized that managing the dependency graph was not intuitive when using `async` for concurrency. + +As the program matured, a new problem arose: a steep learning curve with error handling. The initial version of his design used `panic!`s to fail the program if an error was encountered, but the stack traces were almost unreadable. He asked his teammate Grace to migrate over to using the `Result` idiom for error handling and Grace found a major inconvenience. The Rust type inference inconsistently breaks when propagating `Result` in `async` blocks. Grace frequently found that she had to specify the type of the error when creating a result value: +```rust +Ok::<_, HydroError>( ( p.to_shared(), s.to_shared() ) ) +``` +And she could not figure out why she had to add the `::<_, HydroError>` to some of the `Result` values. + +Finally, once Niklaus' team began using the new `async` design for their simulations, they noticed an important issue that impacted productivity: compilation time had now increased to between 30 and 60 seconds. The nature of their work requires frequent changes to code and recompilation and 30-60 seconds is long enough to have a noticeable impact on their quality of life. What he and his team want is for compilation to be 2 to 3 seconds. Niklaus believes that the use of `async` is a major contributor to the long compilation times. + +This new solution works, but Niklaus is not satisfied with how complex his code became after the move to `async` and that compilation time is now 30-60 seconds. The state sharing adding a large amount of cruft with `Arc` and `async` is not well suited for using a dependency graph to schedule tasks so implementing this solution created a key component of his program that was difficult to understand and pervasive. Ultimately, his conclusion was that `async` is not appropriate for parallelizing computational tasks. He will be trying a new design based upon Rayon in the next version of her application. + +## 🤔 Frequently Asked Questions + +### **What are the morals of the story?** +- `async` looks to be the wrong choice for parallelizing compute bound/computational work +- There is a lack of guidance to help people solving such problems get started on the right foot +- Quality of Life issues (compilation time, type inference on `Result`) can create a drag on users ability to focus on their domain problem + +### **What are the sources for this story?** +This story is based on the experience of building the [kilonova](https://github.com/clemson-cal/app-kilonova) hydrodynamics simulation solver. + +### **Why did you choose Niklaus and Grace to tell this story?** +I chose Niklaus as the primary character in this story because this work was driven by someone who only uses programming for a small part of their work. Grace was chosen as a supporting character because of that persons experience with C/C++ programming and to avoid repeating characters. + +### **How would this story have played out differently for the other characters?** +- Alan: there's a good chance he would have already had experience working with either async workflows in another language or doing parallelization of compute bound tasks; and so would already know from experience that `async` was not the right place to start. +- Grace: likewise, might already have experience with problems like this and would know what to look for when searching for tools. +- Barbara: the experience would likely be fairly similar, since the actual subject of this story is quite familiar with Rust by now + +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/status_quo/niklaus_wants_to_share_knowledge.md b/src/vision/submitted_stories/status_quo/niklaus_wants_to_share_knowledge.md similarity index 85% rename from src/vision/status_quo/niklaus_wants_to_share_knowledge.md rename to src/vision/submitted_stories/status_quo/niklaus_wants_to_share_knowledge.md index fcec6ec8..f9107d2d 100644 --- a/src/vision/status_quo/niklaus_wants_to_share_knowledge.md +++ b/src/vision/submitted_stories/status_quo/niklaus_wants_to_share_knowledge.md @@ -3,7 +3,7 @@ ## 🚧 Warning: Draft status 🚧 -This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! @@ -18,17 +18,17 @@ But Niklaus would really really like to document async to avoid disappointing [p Niklaus was excited about [the RFC proposing that `block_on` be added to the stdlib][block-on-rfc], because it seemed like that would solve Niklaus' problems. Niklaus would really like to include `async` in a big update to the documentation. No pressure. [trpl]: https://doc.rust-lang.org/stable/book/ -[people like Barbara]: https://github.com/rust-lang/wg-async-foundations/blame/5ce418ac4076850f515034010cc51b707441f695/src/vision/status_quo/barbara_makes_their_first_steps_into_async.md#L22 +[people like Barbara]: https://github.com/rust-lang/wg-async/blame/5ce418ac4076850f515034010cc51b707441f695/src/vision/status_quo/barbara_makes_their_first_steps_into_async.md#L22 [block-on-rfc]: https://github.com/rust-lang/rust/pull/65875 -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade ## 🤔 Frequently Asked Questions ### **What are the morals of the story?** Writing documentation to go with the language/stdlib for something that is half in the language/stdlib and half in the ecosystem is hard. -This is related to [Barbara's story](https://rust-lang.github.io/wg-async-foundations/vision/status_quo/barbara_makes_their_first_steps_into_async.html) about wanting to get started without needing to pick an executor. +This is related to [Barbara's story](https://rust-lang.github.io/wg-async/vision/status_quo/barbara_makes_their_first_steps_into_async.html) about wanting to get started without needing to pick an executor. There are topics of async that apply no matter what executor you pick, but it's hard to explain those topics without picking an executor to demonstrate with. We all have too much work to do and not enough time. diff --git a/src/vision/status_quo/template.md b/src/vision/submitted_stories/status_quo/template.md similarity index 69% rename from src/vision/status_quo/template.md rename to src/vision/submitted_stories/status_quo/template.md index a204cd19..25a8838e 100644 --- a/src/vision/status_quo/template.md +++ b/src/vision/submitted_stories/status_quo/template.md @@ -9,17 +9,17 @@ *If you're looking for ideas of what to write about, take a look at the [open issues]. You can also [open an issue of your own] to throw out an idea for others.* -[How To Vision: Status Quo]: ../how_to_vision/status_quo.md -[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async-foundations/master/src/vision/status_quo/template.md -[`status_quo`]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/status_quo -[`SUMMARY.md`]: https://github.com/rust-lang/wg-async-foundations/blob/master/src/SUMMARY.md -[open issues]: https://github.com/rust-lang/wg-async-foundations/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas -[open an issue of your own]: https://github.com/rust-lang/wg-async-foundations/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= +[How To Vision: Status Quo]: ../status_quo.md +[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async/master/src/vision/status_quo/template.md +[`status_quo`]: https://github.com/rust-lang/wg-async/tree/master/src/vision/status_quo +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async/blob/master/src/SUMMARY.md +[open issues]: https://github.com/rust-lang/wg-async/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas +[open an issue of your own]: https://github.com/rust-lang/wg-async/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= ## 🚧 Warning: Draft status 🚧 -This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! @@ -43,11 +43,11 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee ### **How would this story have played out differently for the other characters?** *In some cases, there are problems that only occur for people from specific backgrounds, or which play out differently. This question can be used to highlight that.* -[character]: ../characters.md -[status quo stories]: ./status_quo.md -[Alan]: ../characters/alan.md -[Grace]: ../characters/grace.md -[Niklaus]: ../characters/niklaus.md -[Barbara]: ../characters/barbara.md -[htvsq]: ../how_to_vision/status_quo.md -[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade +[character]: ../../characters.md +[status quo stories]: ../status_quo.md +[Alan]: ../../characters/alan.md +[Grace]: ../../characters/grace.md +[Niklaus]: ../../characters/niklaus.md +[Barbara]: ../../characters/barbara.md +[htvsq]: ../status_quo.md +[cannot be wrong]: ../../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade diff --git a/src/vision/tenets.md b/src/vision/tenets.md deleted file mode 100644 index 91cd8a11..00000000 --- a/src/vision/tenets.md +++ /dev/null @@ -1,27 +0,0 @@ -# ✏️ Design tenets for async - -| Status | Owner | -| --- | --- | -| ⚠️ Draft ⚠️ | nikomatsakis | - -**Draft status.** These tenets are a first draft. nikomatsakis plans to incorporate feedback and revise them before they are finalized. - -The design tenets describe the key principles that drive our work on async. Hopefully, we are able to achieve and honor all of them all of the time. Sometimes, though, they come into conflict, and we have to pick -- in that case, we prefer the tenet earlier in the list. - -1. **Minimal overhead.** Rust Async I/O performance should compare favourably with any other language. In the extreme case, it should be possible to use async/await without any required allocation, although this is unlikely to be a common case in production systems. -2. **Easy to get started, but able to do anything you want.** We should make it simple to write Async I/O code and get things that work reasonably well, but it should be possible for people to obtain fine-grained control as needed. -3. **Async is like sync, but with blocking points clearly identified.** At the highest level, writing a simple program using asynchronous I/O in Rust should be analogous to writing one that uses synchronous I/O, except that one adds `async` in front of function declarations and adds `.await` after each call. We should aim for analogous design between synchronous and asynchronous equivalents. Similarly, streams should be like asynchronous iterators. One should be able to use the same sort of combinators with streams and to iterate over them in analogous ways. -4. **No one true runtime.** We need to be able to hook into existing runtimes in different environments, from embedded environments to runtimes like node.js. Specialized systems need specialized runtimes. -5. **Library ecosystem is key.** We want to have a strong ecosystem of async crates, utilities, and frameworks. This will require mechanisms to write libraries/utilities/frameworks that are generic and interoperable across runtimes. - -## Stress tests - -"Stress tests" are important use cases that tend to "stretch" the design. When we are contemplating changes, it's important to look over the stress tests and make sure that they all still work: - -* **Single-threaded executors:** Some systems tie each task to a single thread; such tasks should be able to access data that is not `Send` or `Sync`, and the executor for those tasks should be able to be fully optimized to avoid atomic accesses, etc. -* **Multi-threaded executors:** Many systems migrate tasks between threads transparently, and that should be supported as well, though tasks will be required to be `Send`. -* **"Bring your own runtime":** The Rust language itself should not require that you start threads, use epoll, or do any other particular thing. -* **Zero allocation, single task:** Embedded systems might want to be able to have a single task that is polled to completion and which does no allocation whatsoever. -* **Multiple runtimes in one process:** Sometimes people have to combine systems, each of which come with their own event loop. We should avoid assuming there is one global event loop in the system. -* **Non-Rust based runtimes:** Sometimes people want to integrate into event loops from other, non-Rust-based systems. -* **WebAssembly in the browser:** We want to integrate with WebAssembly. diff --git a/src/vision/unresolved_questions.md b/src/vision/unresolved_questions.md new file mode 100644 index 00000000..34a6692b --- /dev/null +++ b/src/vision/unresolved_questions.md @@ -0,0 +1,8 @@ +# Major unresolved questions or controveries + +This section contains places where there remains significant design work to be done. +It also contains some points of major controversy, where the path is clear, but many +people disagree on whether to take it. These are places where further input can be useful. + +The page for each controversy attempts to summarize the various options available and some of +the tradeoffs involved. diff --git a/src/vision/unresolved_questions/async_fn.md b/src/vision/unresolved_questions/async_fn.md new file mode 100644 index 00000000..83a9faaa --- /dev/null +++ b/src/vision/unresolved_questions/async_fn.md @@ -0,0 +1,3 @@ +# How to represent the AsyncFn traits? + +As noted in the [async fn page](https://rust-lang.github.io/async-fundamentals-initiative/), the ["inline async fn"](https://rust-lang.github.io/async-fundamentals-initiative/design-discussions/dyn_async_trait.html) technique cannot represent async closures. diff --git a/src/vision/unresolved_questions/await_or_not.md b/src/vision/unresolved_questions/await_or_not.md new file mode 100644 index 00000000..fcfac664 --- /dev/null +++ b/src/vision/unresolved_questions/await_or_not.md @@ -0,0 +1,32 @@ +# To await or not to await? + +Should we require you to use `.await`? After the epic syntax debates we had, wouldn't it be ironic if we got rid of it altogether, as [carllerche has proposed](https://carllerche.com/2021/06/17/six-ways-to-make-async-rust-easier/)? + +Basic idea: + +- When you invoke an async function, it could await by default. +- You would write `async foo()` to create an "async expression" -- i.e., to get a `impl Async`. + - You might instead write `async || foo()`, i.e., create an async closure. + +Appealing characteristics: + +- **More analogous to sync code.** In sync code, if you want to defer immediately executing something, you make a closure. Same in async code, but it's an async closure. +- **Consistency around async-drop.** If we adopt an [async drop](https://rust-lang.github.io/async-fundamentals-initiative/design-discussions/async_drop.html) proposal, that implies that there will be "awaits" that occur as you exit a block (or perhaps from the control-flow of a `break` or `?`). These will not be signaled with a `.await`. So you can no longer rely on _every_ await point being visible with a keyword. +- **No confusion around remembering to await.** Right now the compiler has to go to some lengths to offer you messages suggesting you insert `.await`. It'd be nice if you just didn't have to remember. +- **Room for optimization.** When you first invoke an async function, it can immediately start executing; it only needs to create a future in the event that it suspends. This may also make closures somewhat smaller. + - This could be partially achieved by adding an optional method on the trait that compiles a version of the fn meant to be used when it is _immediately awaited_. + +But there are some downsides: + +- **Churn.** Introducing a new future trait is largely invisible to users except in that it manifests as version mismatches. Removing the await keyword is a much more visible change. +- **Await points are less visible.** There may be opportunity to introduce concurrency and so forth that is harder to spot when reading the code, particularly outside of an IDE. (In Kotlin, which adopts this model, suspend points are visible in the "gutter" of the editor, but this is not visible when reviewing patches on github.) + - Await points today also indicate where a live `Send` or `Sync` value will affect if the future is send or sync (but with async-drop, this would no longer be true). +- **Async becomes an effect.** In today's Rust, an "async function" desugars into a traditional function that returns a future. This function is called like any other, and hence it can implement the `Fn` traits and so forth. In this "await-less" Rust, an async function is called differently from other functions, because it induces an await. This means that we need to consider `async` as a kind of "effect" (like `unsafe`) in a way that is not today. + - Similarly, how do we handle the case of `fn foo() -> impl Future`? Does that auto-await, or does it require an explicit `await` keyword? + - What happens when you invoke an `async fn` in a sync environment? + +## Frequently asked questions + +### How could you do this anyway? Wouldn't it be a massive breaking change? + +It would have to take place over an edition. diff --git a/src/vision/unresolved_questions/cancellation.md b/src/vision/unresolved_questions/cancellation.md new file mode 100644 index 00000000..ac40e169 --- /dev/null +++ b/src/vision/unresolved_questions/cancellation.md @@ -0,0 +1 @@ +# How best to integrate voluntary cancellation? diff --git a/src/vision/unresolved_questions/default_runtime.md b/src/vision/unresolved_questions/default_runtime.md new file mode 100644 index 00000000..b6ee82a5 --- /dev/null +++ b/src/vision/unresolved_questions/default_runtime.md @@ -0,0 +1,7 @@ +# Default runtime? + +The [User's Manual of the future](../shiny_future/users_manual.md) suggests that one must still pick a runtime upfront and use a decorator like `#[runtime::main]`. This is "accidental complexity" for people learning async Rust: the choice of runtime is something they are not yet equipped to make. It would be better for users if they could just write `async fn main` and not choose a runtime yet (and then, later, once they are equipped to make the choice, opt for other runtimes). + +However, we also wish to avoid shipping and maintaining a runtime in the Rust stdlib. We want runtimes to live in the ecosystem and evolve over time. If we were to pick a "default runtime", that might favor one runtime at the expense of others. + +Should we pick a default runtime? If so, what criteria do we use to pick one, and how do we manage the technical side of things (e.g., we need to either ship the runtime with rustup or else insert some kind of implicit cargo dependency). diff --git a/src/vision/unresolved_questions/portable_without_generics.md b/src/vision/unresolved_questions/portable_without_generics.md new file mode 100644 index 00000000..660e63c9 --- /dev/null +++ b/src/vision/unresolved_questions/portable_without_generics.md @@ -0,0 +1 @@ +# Extend stdlib to permit portable async without generics? diff --git a/src/welcome.md b/src/welcome.md index 32235c98..0e182dcf 100644 --- a/src/welcome.md +++ b/src/welcome.md @@ -1,44 +1,38 @@ # 👋🏽 Welcome -Welcome to the wg-async-foundations website! +Welcome to the home of wg-async! This working group is focused +around implementation/design of the “foundations” for Async I/O. -## Leads +You can learn more by reading our [charter]. -The leads of this working group are [@tmandry] and [@nikomatsakis]. Both of them can be found on [Zulip]. +[charter]: ./CHARTER.md -[@tmandry]: https://github.com/tmandry -[@nikomatsakis]: https://github.com/nikomatsakis +## 🛠️ Getting involved -## Getting involved - -There is a weekly [triage meeting] that takes place in our [Zulip stream][zulip]. Feel free to stop by then (or any time!) to introduce yourself. +We have several [meetings] throughout the month. Feel free to stop by then (or any time!) to introduce yourself. We take meeting notes and keep them on our [HackMD](https://hackmd.io/@wg-async). If you're interested in fixing bugs, though, there is no need to wait for the meeting! [Take a look at the instructions here.][fix-bugs] -[triage meeting]: ./triage.md -[fix-bugs]: ./triage.md#so-you-want-to-fix-a-bug - -## What is the goal of this working group? - -This working group is focused around implementation/design of the “foundations” for Async I/O. This means that we are focused on designing and implementing extensions to the language, standard library, and other "core" bits of support offered by the Rust organization. We do not directly work on external projects like [tokio], [async-std], [smol], [embassy] and so forth, although we definitely discuss ideas and coordinate with them where appropriate. +We are actively working on bringing the [async vision] to reality, so there are lots of ways to help. +Check out the [Roadmap] to see the various things we are working on. +Each of the high level goals should have further instructions for how to get starting helping with that goal in particular. +Look for the 🛠️ icon, which highlights areas where further how to help resources are available. -[tokio]: https://tokio.rs/ -[async-std]: https://async.rs/ -[smol]: https://github.com/smol-rs/smol/ -[embassy]: https://github.com/akiles/embassy +[meetings]: ./meetings.md +[fix-bugs]: ./triage.md#so-you-want-to-fix-a-bug +[async vision]: ./vision.md +[Roadmap]: ./vision/roadmap.md ## Zulip -[zulip]: #zulip - -We hold discussions on [the `#wg-async-foundations` stream in Zulip](https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async-foundations) +We hold discussions on [the `#wg-async` stream in Zulip](https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async) ## License Licensed under either of - * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. @@ -47,4 +41,3 @@ at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. - diff --git a/tools/fixlinks/.gitignore b/tools/fixlinks/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/tools/fixlinks/.gitignore @@ -0,0 +1 @@ +/target diff --git a/tools/fixlinks/Cargo.toml b/tools/fixlinks/Cargo.toml new file mode 100644 index 00000000..e20a603b --- /dev/null +++ b/tools/fixlinks/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "fixlinks" +version = "0.1.0" +authors = ["Tyler Mandry "] +edition = "2018" + +[dependencies] +camino = "1.0" +regex = "1.5" +pathdiff = "0.2" diff --git a/tools/fixlinks/src/main.rs b/tools/fixlinks/src/main.rs new file mode 100644 index 00000000..8501d682 --- /dev/null +++ b/tools/fixlinks/src/main.rs @@ -0,0 +1,186 @@ +//! Searches for broken local paths in markdown files and attempts to repair them. +//! +//! Usage: cargo run --bin=fixlinks ./**/*.md + +use std::{collections::{BTreeSet, HashMap}, fs, ops::Range, path::Component}; +use camino::*; +use pathdiff::diff_paths; +use regex::{Match, Regex}; + +#[allow(unused)] +struct FilenameEntry { + file: Utf8PathBuf, + reported: bool, +} + +fn main() { + let files: Vec = std::env::args().skip(1).map(Into::into).collect(); + let mut filenames = HashMap::>::new(); + for file in &files { + let name = file.file_name().unwrap(); + filenames.entry(name.to_owned()).or_default().push(file.clone()); + } + + for (name, paths) in &filenames { + if paths.len() > 1 { + eprintln!("Note: Duplicate filename: {}", name); + for path in paths { + eprintln!("- {}", path); + } + } + } + + let mut cx = Context::new(filenames); + + for file in &files { + let contents = fs::read_to_string(&file).unwrap(); + cx.check_file(file, &contents); + } + + eprintln!("{:#?}", cx.count); +} + +struct Context { + filenames: HashMap::>, + count: Counts, +} + +#[derive(Default, Debug)] +struct Counts { + urls: usize, + paths: usize, + missing: usize, + matches: usize, + ambiguous: usize, + ties: usize, +} + +impl Context { + fn new(filenames: HashMap::>) -> Self { + Context { + filenames, + count: Default::default(), + } + } + + fn check_file<'c>(&mut self, file: &Utf8Path, contents: &'c str) { + let re_linkref = Regex::new(r"^\[(.*?)\]: (.*)").unwrap(); + let re_inline = Regex::new(r"\[([^\[\]]*)\]\((.*?)\)").unwrap(); + + struct UrlMatch { + line_no: usize, + source_range: Range, + } + let mut matches = vec![]; + let mut add_match = |line: &'c str, line_no, mat: Match| { + // Safety: line and contents are from the same allocation. + let line_start_idx = unsafe { line.as_ptr().offset_from(contents.as_ptr()) } as usize; + let source_range = (line_start_idx + mat.start())..(line_start_idx + mat.end()); + matches.push(UrlMatch { line_no, source_range }); + }; + + // Match against linkrefs, then inline links. + let mut linkrefs = BTreeSet::new(); + for (idx, line) in contents.lines().enumerate() { + let line_no = idx + 1; + for cap in re_linkref.captures_iter(line) { + add_match(line, line_no, cap.get(2).unwrap()); + linkrefs.insert(cap.get(1).unwrap().as_str()); + } + } + for (idx, line) in contents.lines().enumerate() { + let line_no = idx + 1; + for cap in re_inline.captures_iter(line) { + if let Some(url) = cap.get(2) { + if !linkrefs.contains(url.as_str()) { + add_match(line, line_no, url); + } + } + } + } + + // We need to process through matches in order so we can build the + // modified contents sequentially. + matches.sort_by_key(|um| um.source_range.start); + + let mut modified = String::new(); + let mut source_bytes_written = 0; + for UrlMatch { line_no, source_range } in matches { + if let Some(replacement) = self.check_url(file, line_no, &contents[source_range.clone()]) { + modified.push_str(&contents[source_bytes_written..source_range.start]); + modified.push_str(&replacement); + source_bytes_written = source_range.end; + } + }; + + if !modified.is_empty() { + modified.push_str(&contents[source_bytes_written..]); + fs::write(file, modified).unwrap(); + } + } + + fn check_url(&mut self, file: &Utf8Path, line_no: usize, path: &str) -> Option { + self.count.urls += 1; + if path.starts_with("http:") || path.starts_with("https:") || path.starts_with("#") { + return None; + } + let path = path.split('#').next().unwrap(); + + self.count.paths += 1; + + let resolved = file.parent().unwrap().join(Utf8Path::new(path)); + if !resolved.exists() { + self.count.missing += 1; + // println!("{}:{}: {}", file, line_no, path); + + let filenames = &self.filenames; + if let Some(names) = resolved.file_name().and_then(|f| filenames.get(f)) { + self.count.matches += 1; + if names.len() > 1 { + self.count.ambiguous += 1; + } + if let Some(mut replacement) = Self::rank_names(file, names, &mut self.count) { + // mdBook doesn't seem to like "raw" filenames. + if !replacement.starts_with("../") { + replacement.insert_str(0, "./"); + } + // println!("- Replacing with: {}", replacement); + return Some(replacement); + } + } else { + eprintln!("Warning: Unable to resolve at {}:{}: {}", file, line_no, path); + } + } + None + } + + fn rank_names(file: &Utf8Path, names: &[Utf8PathBuf], count: &mut Counts) -> Option { + let parent_dir = file.parent().unwrap(); + let mut best_score = usize::MAX; + let mut best_path = None; + let mut ties = 0; + for candidate in names { + // println!("- Could be: {}", candidate); + + // Candidates are scored by how many differing path components they have. + if let Some(relpath) = diff_paths(candidate, parent_dir) { + let score = relpath.components().count() + + // Triple count ../ components (prefer candidates in the same directory). + relpath.components().take_while(|c| c == &Component::ParentDir).count() * 2; + if score < best_score { + best_score = score; + best_path = Some(relpath.into_os_string().into_string().unwrap()); + ties = 0; + } else if score == best_score { + ties += 1; + } + }; + } + + if best_path.is_some() && ties > 0 { + count.ties += 1; + } + + best_path + } +}