diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 5ecd92532..000000000 --- a/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -_drafts/ -_site/ -.DS_Store -*.swp -Gemfile.lock -.sass-cache/ -.ruby-version \ No newline at end of file diff --git a/Gemfile b/Gemfile deleted file mode 100644 index c1033afa7..000000000 --- a/Gemfile +++ /dev/null @@ -1,33 +0,0 @@ -source "https://rubygems.org" - -# Hello! This is where you manage which Jekyll version is used to run. -# When you want to use a different version, change it below, save the -# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: -# -# bundle exec jekyll serve -# -# This will help ensure the proper Jekyll version is running. -# Happy Jekylling! -gem "jekyll", "~> 3.8.3" - -# This is the default theme for new Jekyll sites. You may change this to anything you like. -gem "minima", "~> 2.0" - -# If you want to use GitHub Pages, remove the "gem "jekyll"" above and -# uncomment the line below. To upgrade, run `bundle update github-pages`. -# gem "github-pages", group: :jekyll_plugins - -# If you have any plugins, put them here! -group :jekyll_plugins do - gem "jekyll-feed", "~> 0.6" - gem 'jekyll-sitemap' - gem 'jekyll-email-protect' - gem 'jekyll-seo-tag' - gem 'jekyll-paginate' -end - -# Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] - -# Performance-booster for watching directories on Windows -gem "wdm", "~> 0.1.0" if Gem.win_platform? diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 680cc6efb..000000000 --- a/_config.yml +++ /dev/null @@ -1,57 +0,0 @@ -# Jekyll configuration - -title: The C++ Alliance -email: info@cppalliance.org -url: http://cppalliance.org # the base hostname & protocol for your site, e.g. http://example.com -# baseurl: /subpath # the subpath of your site, e.g. /blog -description: >- # this means to ignore newlines until "baseurl:" - The C++ Alliance is dedicated to helping the C++ programming language - evolve. We see it developing as an ecosystem of open source libraries - and as a growing community of those who contribute to those libraries.. -excerpt_separator: "" -github_username: CPPAlliance -twitter: - username: CPPAlliance - card: summary - site: '@CPPAlliance' - -# Build settings -markdown: kramdown - -# GitHub Flavored Markdown (GFM) settings -kramdown: - input: GFM - auto_ids: true - hard_wrap: false - syntax_highlighter: none - # syntax_highlighter: rouge - -theme: minima -plugins: - - jekyll-feed - - jekyll-sitemap - - jekyll-email-protect - - jekyll-seo-tag - - jekyll-paginate - -feed: - posts_limit: 15 - -# Pagination settings -paginate: 5 -paginate_path: "/news/page:num/" - -# Twitter Card Setting -title_image: "https://cppalliance.org/images/logo.png" - -# Exclude from processing. -# The following items will not be processed, by default. Create a custom list -# to override the default setting. -# exclude: -# - Gemfile -# - Gemfile.lock -# - node_modules -# - vendor/bundle/ -# - vendor/cache/ -# - vendor/gems/ -# - vendor/ruby/ diff --git a/_includes/contact.html b/_includes/contact.html deleted file mode 100644 index 0ae49db1b..000000000 --- a/_includes/contact.html +++ /dev/null @@ -1,40 +0,0 @@ - -
-
-
-

- - Contact -

-

Contact

-
- -
-
diff --git a/_includes/favicon.html b/_includes/favicon.html deleted file mode 100644 index 8429f6b9c..000000000 --- a/_includes/favicon.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/_includes/paginator.html b/_includes/paginator.html deleted file mode 100644 index bffa07946..000000000 --- a/_includes/paginator.html +++ /dev/null @@ -1,69 +0,0 @@ -{% if paginator.total_pages > 1 %} - -{% endif %} diff --git a/_includes/team-bio.html b/_includes/team-bio.html deleted file mode 100644 index e9ce883b0..000000000 --- a/_includes/team-bio.html +++ /dev/null @@ -1,25 +0,0 @@ - -
- {% if page.email %} - - {% endif %} - {% if page.site %} - - {% endif %} - {% if page.resume %} - - {% endif %} - {% if page.stack-overflow %} - - {% endif %} - {% if page.github %} - - {% endif %} - {% if page.linkedin %} - - {% endif %} - {% if page.twitter %} - - {% endif %} -
- diff --git a/_includes/team-blog.html b/_includes/team-blog.html deleted file mode 100644 index 123ad777e..000000000 --- a/_includes/team-blog.html +++ /dev/null @@ -1,18 +0,0 @@ - -{% assign n = site.categories[{{page.id}}] | size %} -{% if n != 0 %} -
-
-

Blog

-

Blog

-
- -
-{% endif %} diff --git a/_includes/top-title.html b/_includes/top-title.html deleted file mode 100644 index 01a20e457..000000000 --- a/_includes/top-title.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
-

{{ include.header }}

-

{{ include.subheader }}

-
-
diff --git a/_layouts/alumni.html b/_layouts/alumni.html deleted file mode 100644 index 1adaee82b..000000000 --- a/_layouts/alumni.html +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: default -type: website ---- -
-
-
-

Alumni

-

Alumni

-
-
- - -
- {{ page.member-name }} -
- -
-

{{ page.member-name }}

- {{ page.position }} - - {% include team-bio.html %} - - -
{{ content }}
- - View Full Team... -
-
-
- - {% include team-blog.html %} - -
diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index d139a34ac..000000000 --- a/_layouts/default.html +++ /dev/null @@ -1,135 +0,0 @@ ---- -type: website ---- - - - - - -{% if page.layout == 'post' and page.title %}{{page.title}} | The C++ Alliance{% else %}{{ page.title }}{% endif %} - - - -{% include favicon.html %} -{% seo %} - -{% if page.layout == 'post' %} - -{% endif %} - - - - - -{% if page.summary %} - -{% endif %} - -{% if page.large-image %} - - -{% else %} - {% if page.image %} - - - {% else %} - - {% endif %} -{% endif %} - - - - - - - - - - - - - - - {{ content }} - - - - - - {% if page.layout == 'post' %} - - {% endif %} - - - - - diff --git a/_layouts/post.html b/_layouts/post.html deleted file mode 100644 index a7cb71628..000000000 --- a/_layouts/post.html +++ /dev/null @@ -1,77 +0,0 @@ ---- -layout: default -type: article ---- - -
-
- {% if page.hero-image %} - - {% endif %} - -
- {% if page.caption %} - {{ page.caption }} - {% endif %} - -
-
-

{{ page.title }}

- - {% if page.author-id %} - {% assign people-pages = site.pages %} - {% assign page_author-id = page.author-id %} - {% for people in people-pages %} - {% if people.id == page_author-id %} - {% assign author_name = people.member-name %} - {% break %} - {% endif %} - {% endfor %} - {% endif %} - - {% if author_name %} -
- By - - {{author_name}} - on - -
- {% elsif page.author-name %} -
- By - {{page.author-name}} on - -
- {% endif %} - {{ page.date | date: "%b %-d, %Y"}} -
-
- {{ content }} -
-
-
-
- -
-
-

All Posts by This Author

-
-
-
    - {% for post in site.posts %} - {% if post.categories == page.categories %} -
  • - {{ post.date | date: "%m/%d/%Y"}} - {{ post.title }} -
  • - {% endif %} - {% endfor %} -
  • - View All Posts... -
  • -
-
-
- -
diff --git a/_layouts/team.html b/_layouts/team.html deleted file mode 100644 index 40855c2c8..000000000 --- a/_layouts/team.html +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: default -type: website ---- -
-
-
-

Team

-

Team

-
-
- - -
- {{ page.member-name }} -
- -
-

{{ page.member-name }}

- {{ page.position }} - - {% include team-bio.html %} - - -
{{ content }}
- - View Full Team... -
-
-
- - {% include team-blog.html %} - -
diff --git a/_posts/2017-07-07-Beast-is-accepted-into-Boost.md b/_posts/2017-07-07-Beast-is-accepted-into-Boost.md deleted file mode 100644 index 5cac15c05..000000000 --- a/_posts/2017-07-07-Beast-is-accepted-into-Boost.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: post -nav-class: dark -categories: vinnie -author-id: vinnie ---- -Beast, an HTTP and -WebSocket protocol library written in C++11, becomes part of the -Boost library collection. diff --git a/_posts/2017-08-17-The-C++-Alliance-incorporates-In-California.md b/_posts/2017-08-17-The-C++-Alliance-incorporates-In-California.md deleted file mode 100644 index 91168d488..000000000 --- a/_posts/2017-08-17-The-C++-Alliance-incorporates-In-California.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, vinnie ---- -The C++ Alliance, Inc. officially incorporates as a California -501(c)(3) non-profit organization. The company is administered -entirely as a virtual entity with no physical office. diff --git a/_posts/2017-10-01-Louis-Tatta-joins-as-CEO.md b/_posts/2017-10-01-Louis-Tatta-joins-as-CEO.md deleted file mode 100644 index 3f2ad1e6b..000000000 --- a/_posts/2017-10-01-Louis-Tatta-joins-as-CEO.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -title: "Louis Tatta joins as CEO" ---- -Louis Tatta joins the Alliance in the role of Chief Executive Officer, -to oversee and administer the day to day operations of the company and -carry out the mission. diff --git a/_posts/2017-12-05-Foundation-Group-is-engaged-for-registration-service.md b/_posts/2017-12-05-Foundation-Group-is-engaged-for-registration-service.md deleted file mode 100644 index 693f8ffb5..000000000 --- a/_posts/2017-12-05-Foundation-Group-is-engaged-for-registration-service.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis ---- -The Alliance engages -Foundation Group, -a non-profit formation and compliance services company. Foundation -Group delivers a comprehensive 501(c)(3) registration service with -a 100% IRS approval rate. diff --git a/_posts/2017-12-10-Incorp-engaged-as-registered-agent.md b/_posts/2017-12-10-Incorp-engaged-as-registered-agent.md deleted file mode 100644 index a6048235f..000000000 --- a/_posts/2017-12-10-Incorp-engaged-as-registered-agent.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis ---- -The Alliance engages -InCorp Services, Inc. -as the registered agent. InCorp provides National Registered -Agent services in all 50 states and Washington, D.C. diff --git a/_posts/2018-01-05-Administrate-Cpplang-slack-workspace.md b/_posts/2018-01-05-Administrate-Cpplang-slack-workspace.md deleted file mode 100644 index c5fc0d5f8..000000000 --- a/_posts/2018-01-05-Administrate-Cpplang-slack-workspace.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis ---- -

-The Alliance is now the owner and administrator of the -Cpplang Slack Workspace. This -workspace is the premiere and most popular community of C++ enthusiasts -and professionals from around the globe. -

diff --git a/_posts/2018-01-09-Technical-committee-established.md b/_posts/2018-01-09-Technical-committee-established.md deleted file mode 100644 index 6a995b0ff..000000000 --- a/_posts/2018-01-09-Technical-committee-established.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis ---- -The board of directors establishes the Technical Committee, whose job -is to inform the CEO and board on all technical matters such as code -review, resume review, the quality of papers, and other ongoing work. diff --git a/_posts/2018-01-10-Glen-Fernandes-Joins-The-Technical-Committee.md b/_posts/2018-01-10-Glen-Fernandes-Joins-The-Technical-Committee.md deleted file mode 100644 index 48f18b559..000000000 --- a/_posts/2018-01-10-Glen-Fernandes-Joins-The-Technical-Committee.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, glen ---- -Glen Joseph Fernandes, a Boost C++ library author, contributor, maintainer, -joins as a technical advisor. diff --git a/_posts/2018-01-16-Jon-Kalb-joins-the-board.md b/_posts/2018-01-16-Jon-Kalb-joins-the-board.md deleted file mode 100644 index d3cf6a7ab..000000000 --- a/_posts/2018-01-16-Jon-Kalb-joins-the-board.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, jon ---- -Jon Kalb joins the Alliance board of directors as treasurer. diff --git "a/_posts/2018-01-16-Ren\303\251-Rivera-joins-the-board.md" "b/_posts/2018-01-16-Ren\303\251-Rivera-joins-the-board.md" deleted file mode 100644 index 6e3173c8a..000000000 --- "a/_posts/2018-01-16-Ren\303\251-Rivera-joins-the-board.md" +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, rene ---- -René Rivera joins the Alliance board of directors as secretary. diff --git a/_posts/2018-01-16-Vinnie-Falco-joins-the-board.md b/_posts/2018-01-16-Vinnie-Falco-joins-the-board.md deleted file mode 100644 index fb0b6d51b..000000000 --- a/_posts/2018-01-16-Vinnie-Falco-joins-the-board.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, vinnie ---- -Vinnie Falco joins the Alliance board of directors as president. diff --git a/_posts/2018-02-22-Corporate-logo-is-adopted.md b/_posts/2018-02-22-Corporate-logo-is-adopted.md deleted file mode 100644 index bbb9b7c34..000000000 --- a/_posts/2018-02-22-Corporate-logo-is-adopted.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis -title: Corporate Logo Is Adopted -hero-image: 2018-02-22-Corporate-logo-is-adopted.png ---- -

-A new corporate logo is adopted from the conclusion of a contest on -Designhill: -

-

- -

diff --git a/_posts/2018-05-06-Gold-sponsor-of-C++-Now.md b/_posts/2018-05-06-Gold-sponsor-of-C++-Now.md deleted file mode 100644 index 0ee82add4..000000000 --- a/_posts/2018-05-06-Gold-sponsor-of-C++-Now.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis -title: Gold sponsor of C++Now ---- -The Alliance is a Gold sponsor for -C++Now 2018. This -conference is a gathering of C++ experts and enthusiasts from around -the world in beautiful Aspen, Colorado. diff --git a/_posts/2018-05-30-Member-of-the-international-committee-for-information-technology-standards.md b/_posts/2018-05-30-Member-of-the-international-committee-for-information-technology-standards.md deleted file mode 100644 index e1e68c03a..000000000 --- a/_posts/2018-05-30-Member-of-the-international-committee-for-information-technology-standards.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis ---- -The Alliance is member of the -International Committee for Information Technology Standards. -INCITS is the central U.S. forum dedicated to creating technology standards -for the next generation of innovation. INCITS members combine their expertise -to create the building blocks for globally transformative technologies. From -cloud computing to communications, from transportation to health care -technologies, INCITS is the place where innovation begins. Membership in -INCITS allows voting in official WG21 meetings. diff --git a/_posts/2018-08-06-The-Law-Firm-for-Non-Profits-engaged.md b/_posts/2018-08-06-The-Law-Firm-for-Non-Profits-engaged.md deleted file mode 100644 index 5a74dd9b6..000000000 --- a/_posts/2018-08-06-The-Law-Firm-for-Non-Profits-engaged.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis -title: The Law Firm for Non-Profits engaged ---- -The Alliance engages -The Law Firm for Non-Profits -for legal representation and services. They are passionate about -supporting, advocating for and partnering with non-profits and the -people behind them. For more than three decades, those looking for -assistance with non-profit law throughout the United States and -around the world have relied on the attorneys of The Law Firm -for Non-Profits for superior legal and business guidance. diff --git a/_posts/2018-08-13-Marshall-Clow-joins-as-staff-engineer.md b/_posts/2018-08-13-Marshall-Clow-joins-as-staff-engineer.md deleted file mode 100644 index 70459f85b..000000000 --- a/_posts/2018-08-13-Marshall-Clow-joins-as-staff-engineer.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, marshall -title: Marshall Clow joins as a Staff Engineer -author-id: marshall ---- - -Marshall Clow joins the Alliance as a Staff Engineer. Previously, -he worked at Qualcomm for many years. Most of his time is spent -working on -libc++, -the C++ standard library implementation for LLVM. He is also a member -of the -C++ standards committee, -currently serving as the chair of LWG, the library working group. -Marshall has been contributing to the -Boost libraries -since 2001, and is the author of the -Boost.Algorithm -library. Furthermore he maintains several other boost libraries, -and moderates some of the boost mailing lists. Finally, Marshall -has graciously taken on the role of release manager for several -Boost versions. diff --git a/_posts/2018-09-03-Damian-Jarek-joins-as-staff-engineer.md b/_posts/2018-09-03-Damian-Jarek-joins-as-staff-engineer.md deleted file mode 100644 index 97c117940..000000000 --- a/_posts/2018-09-03-Damian-Jarek-joins-as-staff-engineer.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, damian ---- -Damian Jarek joins the Alliance as Staff Engineer. Previously he worked on a -number of embedded networking projects for a few major clients. As a Staff -Engineer he’ll be working on an open-source companion library for Boost.Beast -and Boost.Asio, which will abstract away the platform-specific details of -acessing system proxy settings and performing TLS verification of a peer -certificate chain using the operating system’s key store. diff --git a/_posts/2018-09-23-Gold-sponsor-of-Cppcon-2018.md b/_posts/2018-09-23-Gold-sponsor-of-Cppcon-2018.md deleted file mode 100644 index ace275c09..000000000 --- a/_posts/2018-09-23-Gold-sponsor-of-Cppcon-2018.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis ---- -

-The Alliance is a Gold sponsor for -CppCon 2018. This -conference is the annual, week-long face-to-face gathering for the -entire C++ community. The conference is organized by the C++ community -for the community. Attendees enjoy inspirational talks and a friendly -atmosphere designed to help individuals learn from each other, meet -interesting people, and generally have a stimulating experience. -

diff --git a/_posts/2018-10-24-Initial-work-on-Certify-complete.md b/_posts/2018-10-24-Initial-work-on-Certify-complete.md deleted file mode 100644 index e78ed48d8..000000000 --- a/_posts/2018-10-24-Initial-work-on-Certify-complete.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, damian -author-id: damian ---- -# Initial work on `Certify` complete -It's been mentioned in my initial blog post that I'd be working on a TLS -certificate store abstraction library, with the intent of submitting it for -formal review for Boost, at some point in the (hopefully near) future. -The initial setup phase (things that every Software Engineer hates) is more -or less complete. CI setup was a bit tricky - getting OpenSSL to run with -the boost build system on both Windows and Linux (and in the future MacOS) -has provided a lot of "fun" thanks to the inherent weirdness of OpenSSL. - -The test harness currently consists of two test runners that loads certificates -from a database (big name for a folder structure stored in git) that has the -certificate chains divided into two groups. Chains that will fail due to various -reasons (e.g. self-signed certificates, wrong domain name) and ones that will pass -(when using a valid certificate store). I'm still working on checking whether -the failure was for the expected reason. All the verification is done offline -(i.e. no communication with external servers is performed, only chain verification). - -At this point it looks like I should consider, whether the current design of -the verification code is a good approach. Using the verification callback -from OpenSSL and asio::ssl is quite an easy way of integrating the platform-specific -certificate store API it causes issues with error propagation (transporting a platform-specific -error through OpenSSL) and may be fairly slow, because it requires certificates to be -reencdoded into the DER format so that they can be fed into the platform-specific API. -An alternative to this approach would be load the entire root certificate store, along with CRLs and -OCSP configuration into an OpenSSL context. This is potentially a little bit harder to get right but -may offer better performance (no reencoding required when veryfing certificate chains) and eliminates -the issues related to error handling. Further investigation, as to which approach is better, is required. - -Don't forget to star the repository: https://github.com/djarek/certify! diff --git a/_posts/2018-11-13-WG21-San-Diego-Trip-Report.md b/_posts/2018-11-13-WG21-San-Diego-Trip-Report.md deleted file mode 100644 index a94444b14..000000000 --- a/_posts/2018-11-13-WG21-San-Diego-Trip-Report.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: post -nav-class: dark -categories: standards, marshall -author-id: marshall ---- -# WG21 San Diego Meeting - -Last week was the fall 2018 WG21 standard committee meeting. It was held -in San Diego, which is my hometown. The fact that I helped organize it -(while I was working at Qualcomm) had absolutely no affect on the location, -I assure you. ;-) - -This was the largest WG21 meeting ever, with 180 attendees. The last meeting -(in Rapperswil, Switzerland) had about 150 attendees, and *that* was the -largest one until now. There were more than 270 papers in the pre-meeting -mailing; meaning that people were spending weeks reading papers to prepare -for the meeting. Herb Sutter (the convener) has been telling everyone that -new papers received after the San Diego meeting were out of scope for C++20, -and apparently people took him at his word. - -This was my first meeting representing the C++ Alliance (though hardly my -first overall). The Alliance was well represented, with Rene, Glen, Vinnie, -Jon and myself attending. For information about how WG21 is structured, -please see [isocpp.org](https://isocpp.org/std). - -I spent all of my time in LWG, since that's the group that I chair, and the -one that has the most influence over libc++, the library that I work on. - -The big news from a library POV was that we voted to merge an updated paper -based on the Ranges TS into the draft standard; which means that (barring -catastrophe) that it will be part of C++20. This was a huge paper, weighing -in at 220+ pages. We spent several days in LWG reviewing this (and a bunch -of time at previous meetings as well). - -We also moved a bunch (around 25) of smaller papers; too many to list here. - -Detailed trip reports can be found around the web: - -* [Herb Sutter](https://herbsutter.com/2018/11/13/trip-report-fall-iso-c-standards-meeting-san-diego/) -* [Reddit](https://www.reddit.com/r/cpp/comments/9vwvbz/2018_san_diego_iso_c_committee_trip_report_ranges/) - -The next WG21 meeting is in Kona, HI February 18-23rd. diff --git a/_posts/2019-01-14-MarshallsJanuaryUpdate.md b/_posts/2019-01-14-MarshallsJanuaryUpdate.md deleted file mode 100644 index d2224b295..000000000 --- a/_posts/2019-01-14-MarshallsJanuaryUpdate.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -layout: post -nav-class: dark -categories: marshall -title: Marshall's January Update -author-id: marshall ---- - -Monthly update (or, what Marshall did in December) - -There are three main areas where I spend my time. - -* Boost -* Libc++ -* WG21, where I am the chair of the Library Working Group (LWG) - ----- -Boost: -December was a big month for boost, and much of the first part of the month was taken up with the release process. I was the release manager for the 1.69.0 release, which went live on 12-December. The final release process was fairly straighforward, with only one release candidate being made/tested - as opposed to the beta, which took _three_. In any case, we had a successful release, and the I (and other boost developers) are now happily working on features/bug fixes for the 1.70 release - which will occur in March. - ----- -Libc++: -After the WG21 meeting in November, there was a bunch of new functionality to be added to libc++. The list of new features (and their status) can be seen [on the libc++ website](https://libcxx.llvm.org/cxx2a_status.html). My major contributions of new features in December were [Consistent Container Erasure](https://wg21.link/P1209R0), [char8_t: A type for UTF-8 characters and strings](https://wg21.link/P0482), and [Should Span be Regular?](https://wg21.link/P1085R2), and a big chunk of [Extending to Calendars and Time Zones](https://wg21.link/P0355R7). - -This is all pointing towards the January 16th "branch for release", and for the scheduled March release of LLVM 8.0. - -As the "code owner" for libc++, I also have to review the contributions of other people to libc++, and evaluate and fix bugs that are reported. That's a never ending task; there are new contributions ever day. - ----- -WG21 - -Being between meetings (November -> February) there was not any special WG21 work to be done in December. There's an ongoing stream of bug reports, discussion, paper reviews that get done between meetings, and there is a series of papers that I need to finish for the pre-Meeting mailing deadline on 21-January. I have 1 1/2 done, and need to do 3-4 more. diff --git a/_posts/2019-03-01-Adler-Colvin-engaged.md b/_posts/2019-03-01-Adler-Colvin-engaged.md deleted file mode 100644 index 555322a19..000000000 --- a/_posts/2019-03-01-Adler-Colvin-engaged.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis -title: Adler & Colvin engaged ---- -The Alliance engages -Adler & Colvin -to complete IRS Form 1023, Application for Recognition of Exemption Under Section 501(c)(3) of the Internal Revenue Code. Completing this form can be a daunting task because of the legal and tax technicalities you'll need to understand. Adler & Colvin is a group of seasoned attorneys based in San Francisco, deeply committed to serving the legal needs of the nonprofit sector. The firm brings an unrivaled depth of expertise and passion to its representation of tax-exempt organizations and individual philanthropists. - diff --git a/_posts/2019-03-04-MarshallsMarchUpdate.md b/_posts/2019-03-04-MarshallsMarchUpdate.md deleted file mode 100644 index 47e55d70a..000000000 --- a/_posts/2019-03-04-MarshallsMarchUpdate.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -layout: post -nav-class: dark -categories: marshall -title: Marshall's March Update -author-id: marshall ---- - -Monthly update (or, what Marshall did in January and February) - -There are four main areas where I spend my time. - -* Libc++, where I am the "code owner" -* WG21, where I am the chair of the Library Working Group (LWG) -* Boost -* Speaking at conferences - -# Libc++ - -The LLVM "branch for release" occurred in January, and there was a bit of a rush to get things into the LLVM 8 release. Now that is over, and we're just watching the test results, seeing if anyone finds any problems with the release. I don't anticipate any, but you never know. - -As the "code owner" for libc++, I also have to review the contributions of other people to libc++, and evaluate and fix bugs that are reported. That's a never-ending task; there are new contributions ever day. - -After the branch, I started working on new features for the LLVM 9 release (for this summer). More calendaring stuff, new C++20 features, and some C++17 features that haven't been done yet. - -### LWG papers implemented in Jan/Feb - -* P0355: Extending to Calendars and Time Zones. You may remember this from last month's update; this is a huge paper, and I am landing it in stages. -* P1024: tuple-like interface to span -* P1227: Signed ssize() functions -* P1357: Traits for [Un]bounded Arrays - -### LWG issues implemented in Jan/Feb (certainly incomplete) - -* LWG3101: span's Container constructors need another constraint -* LWG3144: span does not have a const_pointer typedef -* Enabled a `memcpy` optimization for const vectors that was surprisingly missing - -### LLVM bugs resolved in Jan/Feb (probably incomplete) - -* [Bug 28412](https://llvm.org/PR28412) `std::vector` incorrectly requires CopyConstructible, Destructible and other concepts -* [Bug 39183](https://llvm.org/PR39183) tuple comparison operators return true for tuples of different sizes -* [Bug 24411](https://llvm.org/PR24411) libFuzzer outputs that crash libc++'s regex engine -* [Bug 34330](https://llvm.org/PR34330) error: use of undeclared identifier 'isascii' while compiling strstream.cpp -* [Bug 38606](https://llvm.org/PR38606) no_sanitize("unsigned-integer-overflow") annotation for decremented size_type in `__hash_table` -* [Bug 40533](https://llvm.org/PR40533) `std::minmax_element` is 3 times slower than hand written loop -* [Bug 18584](https://llvm.org/PR18584) SD-6 Feature Test Recommendations -* [Bug 40566](https://llvm.org/PR40566) Libc++ is not Implicit Integer Truncation Sanitizer clean -* [Bug 21715](https://llvm.org/PR21715) 128-bit integers printing not supported in stl implementation -* [Bug 38844](https://llvm.org/PR38844) `__cpp_lib_make_unique` not defined in <memory> -* [Bug 40495](https://llvm.org/PR40495) `is_invokable_v` does not compile -* [Bug 40270](https://llvm.org/PR40270) `std::basic_stringstream` is not working with `std::byte` -* [Bug 39871](https://llvm.org/PR39871) `std::tuple_size` should be a struct -* [Bug 38052](https://llvm.org/PR38052) `std::fstream` still good after closing and updating content - -Also, there was a series of general cleanups in the libc++ tests to improve portability. - - -The current status of libc++ can be found here: -* [C++20 status](https://libcxx.llvm.org/cxx2a_status.html) -* [C++17 status](https://libcxx.llvm.org/cxx1z_status.html) -* [C++14 status](https://libcxx.llvm.org/cxx1y_status.html) (Complete) -* [Libc++ open bugs](https://bugs.llvm.org/buglist.cgi?bug_status=__open__&product=libc%2B%2B) - - - -# WG21 - -The "winter" WG21 meeting was held in Kona, HI on February 18-24. This was the last meeting for new features for C++20, and as such, it was both contentious and very busy. - -The Modules TS and the Coroutines TS were both adopted for C++20, along with a slew of language features. - -Here are some trip reports: -* [Herb Sutter](https://herbsutter.com/2019/02/23/trip-report-winter-iso-c-standards-meeting-kona/) -* [Bryce Adelstein Lelbach](https://www.reddit.com/r/cpp/comments/au0c4x/201902_kona_iso_c_committee_trip_report_c20/) -* [Guy Davidson](https://hatcat.com/?p=69) - - -My part in this was (as always) to chair the Library Working Group (LWG), the group responsible for the description of the library features in the standard (~1000 pages). -We adopted several new features for C++20: - -* P0339R6 polymorphic_allocator<> as a vocabulary type -* P0340R3 Making std::underlying\_type SFINAE-friendly -* P0738R2 I Stream, You Stream, We All Stream for istream_iterator -* P0811R3 Well-behaved interpolation for numbers and pointers -* P0920R2 Precalculated hash values in lookup -* P1001R2 Target Vectorization Policies from Parallelism V2 TS to C++20 -* P1024R3 Usability Enhancements for std::span -* P1164R1 Make create_directory() Intuitive -* P1227R2 Signed ssize() functions, unsigned size() functions -* P1252R2 Ranges Design Cleanup -* P1357R1 Traits for [Un]bounded Arrays - -I wrote five substantive papers for the Kona meeting, all were adopted. Five of them were very similar, all about improving the wording in the standard, rather than proposing new features. - -* [P1458](https://wg21.link/P1458) Mandating the Standard Library: Clause 16 - Language support library -* [P1459](https://wg21.link/P1459) Mandating the Standard Library: Clause 18 - Diagnostics library -* [P1462](https://wg21.link/P1462) Mandating the Standard Library: Clause 20 - Strings library -* [P1463](https://wg21.link/P1463) Mandating the Standard Library: Clause 21 - Containers library -* [P1464](https://wg21.link/P1464) Mandating the Standard Library: Clause 22 - Iterators library - -I was also the nominal author of [P1457](https://wg21.link/P1457) "C++ Standard Library Issues to be moved in Kona", but that was just a list of issues whose resolutions we adopted. - -Between now and the next meeting (July), LWG will be working on reviewing papers and issues to be adopted in July. I'm planning regular teleconferences (in fact, we had the first one on 1-March). - -The goal of the July meeting is to have a "Committee Draft" (CD) of the proposed C++20 standard that can be sent out for review. - - -# Boost - -It's been a quiet couple of months for Boost, since we're between releases, and I have been busy with libc++ and WG21 activities. There have been a few bugs to chase down, and the dealing with change requests for the libraries whose maintainers have "moved on" takes some time. - -However, it's time for another Boost release (1.70), and I will be acting as the release manager again. The release calendar is available (as always) on [the Boost website](https://www.boost.org/development). - -The beta release is schedule for March 13th, and the final release for 10-April. - -# Conferences - -I had submitted talk proposals to three conferences, and all three were accepted. Hence, I will be speaking at: - -* [LLVM European Developer's Conference](https://llvm.org/devmtg/2019-04), April 8-9 in Brussels -* [ACCU](https://conference.accu.org), April 10-13 in Bristol -* [CppNow](http://www.cppnow.org), May 5-10 in Aspen, CO - diff --git a/_posts/2019-03-16-Certify-X509-validation.md b/_posts/2019-03-16-Certify-X509-validation.md deleted file mode 100644 index 0e62980d4..000000000 --- a/_posts/2019-03-16-Certify-X509-validation.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, damian -author-id: damian ---- -# Certify - X509 certificate validation -I always knew that validating a certificate chain presented by a peer is not an -easy procedure, but my recent work in Certify to port over the procedure from -Chromium has only proven that I underestimated the complexity of it. Certificate -revocation seems to be a particularly hard issue, with 2 main categories of -solutions - offline and online validation. - -## Online validation - OCSP -OCSP is a protocol designed to allow checking the revocation status of a -certificate by sending a request over a subset of HTTP/1.1. At first glance, it -seems it solves the status checking problem on its own. However, OCSP has -problems, inherent to online checking. - -First of all, the validation server might not be currently available - so a lack -of response is most definitely not a state in which a chain can be trusted. -Secondly, the check may be slow, after all, it requires connecting to a separate -service. Additionally, the native Windows API for certificate verification does -the status check synchronously, which means potentially blocking a user's thread -that typically services asynchronous operations. There is a feature that -alleviates most of these issues, at least from the point of view of a TLS -client, OCSP stapling. Sadly, it's not very widespread and actually few large -services support it, due to the fact that it increases bandwidth requirements. -Certify will, at some point support both OCSP status checks on the client side -and support for OCSP stapling. The problem here is that OCSP requires a fairly -functional HTTP client and ASN.1 parsing. A lot of this functionality is already -present in OpenSSL, however, integrating it with ASIO and Beast may be tricky. - - -## Offline validation - CRLs and Google CRLSets -The traditional method of checking the status of a certificate involves looking -up revocation lists installed in the OS's store, or downloaded by the -application from the CA. Unfortunately CRLs have issues - an example would be an -incident from a few years ago when CloudFlare performed a mass revocation which -blew up the size of the CRLs by a few orders of magnitude, resulting in a -requirement to download multiple megabytes of data, turning CAs into a major -performance bottleneck. Google came up with a different mechanism, called -CRLSets, which involves a periodic download of a revocation list which is -created by Google's crawler querying certificate status over OCSP. This -verification method is fairly attractive for applications that run on systems -that already have Google products, since this database is shared, which is why -I've chosen to provide an opt-in implementation in Certify. For now, updating -the database will be out of scope, because that requires a few utilties that are -missing from Boost at this time (XML, JSON and an HTTP Client). - -Don't forget to star the repository: https://github.com/djarek/certify! diff --git a/_posts/2019-04-02-MarshallsAprilUpdate.md b/_posts/2019-04-02-MarshallsAprilUpdate.md deleted file mode 100644 index ca1727d0c..000000000 --- a/_posts/2019-04-02-MarshallsAprilUpdate.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -layout: post -nav-class: dark -categories: marshall -title: Marshall's March Update -author-id: marshall ---- - -There are four main areas where I spend my time. - -* Libc++, where I am the "code owner" -* WG21, where I am the chair of the Library Working Group (LWG) -* Boost -* Speaking at conferences - -This month, I spent far more time reviewing other people's code and preparing talks for conferences than the previous few months. The Boost release process consumed a fair chunk of time as well. - -# Libc++ - -The big news is: we released LLVM 8 this month! (March 20th). You can get the sources and pre-built binaries from the [LLVM download page](http://releases.llvm.org/download.html#8.0.0), or wait for your system vendor to provide you with an update. - -As the "code owner" for libc++, I also have to review the contributions of other people to libc++, and evaluate and fix bugs that are reported. That's a never-ending task; there are new contributions ever day. - -### LWG papers implemented this month. - -* [P0811](https://wg21.link/P0811) `std::midpoint` for integral and pointer types. This turned out to be *quite* involved, and spawned a [clang bug report](https://bugs.llvm.org/show_bug.cgi?id=40965). On the plus side, now I have a topic for a talk for CppCon this fall. - -Still to do, `std::midpoint` for floating point types. This is done, but it needs better tests. - -### LWG issues implemented this month - -* I didn't actually commit any LWG issue fixes this month. I worked with others on several bug fixes that landed, but not under my name. - -### LLVM features implemented this month (certainly incomplete) - -* Add noexcept to `operator[]` for `array` and `deque` -* Mark `vector::operator[]` and `front`/`back` as noexcept -* Mark `front()` and `back()` as noexcept for `array`/`deque`/`string`/`string_view` -* Make `to_chars`/`from_chars` work back to C++11. This lets us use them in `to_string`. - - -### LLVM bugs resolved this month (probably incomplete) - -* [Bug 35967](https://llvm.org/35967) <regex> `syntax_option_type` is not a proper bitmask -* _No bug #_ Fix a minor bug with `std::next` and `prev` not handling negative numbers. -* _No bug #_ Cleanup of requirements for `optional` - we no longer allow `optional` -* [Bug 41130](https://llvm.org/41130) `operator/` of `std::chrono::duration` and custom type. - -Also, there was a series of general cleanups in the libc++ tests to improve portability and readability. Eric and I (mostly Eric) revamped the debug-mode support, and there will be more activity there in the future. Also, we're moving towards using more of the `ASSERT_XXXX` macros for readability, and I revamped about 30 of the tests to use them. Only several thousand to go! - - -The current status of libc++ can be found here: -* [C++20 status](https://libcxx.llvm.org/cxx2a_status.html) -* [C++17 status](https://libcxx.llvm.org/cxx1z_status.html) -* [C++14 status](https://libcxx.llvm.org/cxx1y_status.html) (Complete) -* [Libc++ open bugs](https://bugs.llvm.org/buglist.cgi?bug_status=__open__&product=libc%2B%2B) - - - -# WG21 - -The "winter" WG21 meeting was held in Kona, HI on February 18-24. This was the last meeting for new features for C++20, and as such, it was both contentious and very busy. - -Between now and the next meeting (July), LWG will be working on reviewing papers and issues to be adopted in July. We have had three teleconferences since Kona, and a fourth is scheduled for mid-April. - -I am working on more "cleanup" papers similar to [P1458 - Mandating the Standard Library: Clause 16 - Language support library](https://wg21.link/P1458), and my [P0805 - Comparing Containers](https://wg21.link/P0805) needs an update. - -The goal of the July meeting is to have a "Committee Draft" (CD) of the proposed C++20 standard that can be sent out for review. - - -# Boost - -It's time for another Boost release (1.70), and I am acting as the release manager again. The release calendar is available (as always) on [the Boost website](https://www.boost.org/development). - -The cut-off for contributions for the release is 3-April, with a release candidate to follow close behind, and the actual release to happen on the 10th. - -Once the release is over, I'll be putting some serious time into Boost.Algorithm; there are a bunch of C++17/20 algorithms that can be added to the library (among other things). - -# Conferences - -I had submitted talk proposals to three conferences, and all three were accepted. - -I will be speaking at: - -* [LLVM European Developer's Conference](https://llvm.org/devmtg/2019-04), April 8-9 in Brussels -* [ACCU](https://conference.accu.org), April 10-13 in Bristol -* [CppNow](http://www.cppnow.org), May 5-10 in Aspen, CO - diff --git a/_posts/2019-04-04-DamiansAprilUpdate.md b/_posts/2019-04-04-DamiansAprilUpdate.md deleted file mode 100644 index da6439e16..000000000 --- a/_posts/2019-04-04-DamiansAprilUpdate.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, damian -title: Damian's March Update -author-id: damian ---- - -This month I've been working on the following projects: -- Certify -- Boost.Beast -- Boost.Build -- BeastLounge - -# Certify -Certify now properly verifies the hostname of a TLS server according to RFC 2818 -or TLS-DANE if available. Additionally, initial support for CRLSets has been -merged, although it's still missing integration into the verification code. - -I've also invested a fair bit of time into researching what other open source -libraries do to perform certificate status checking. I've looked into BoringSSL, -mbedTLS, Botan and even the Go standard library. It's interesting that no -library has a default way of performing the status check of a certificate and -it's left up to the user. - -The Windows implementation of the certificate store in Certify will now properly -use the entire chain passed by the peer, which resolves certificate failures in -less common cases. - -Don't forget to star the repository: [https://github.com/djarek/certify](https://github.com/djarek/certify)! - -# Boost.Beast -Most of the work this month involved making Beast compile faster and use less -memory by expanding the code that can use split compilation and reducing redundant -dependencies in a few places. - -# Boost.Build -I've worked on implementing 2 improvements that make it less painful to work with b2: -- support for finding OpenSSL -- support for sanitizers in gcc and clang -Both are currently still in review. - -# BeastLounge -The project lacked functioning CI so I implemented one. Since the project was -previously only compiled on MSVC, this proved to be quite challenging, because -MSVC accepts code that is not valid C++11. I've also created a deplyoment docker -image, which allows running the application in popular cloud environments, like -Heroku. A development version of the app is available at [https://beast-lounge.herokuapp.com/](https://beast-lounge.herokuapp.com/). diff --git a/_posts/2019-05-01-MarshallsMayUpdate.md b/_posts/2019-05-01-MarshallsMayUpdate.md deleted file mode 100644 index 35e54d2d0..000000000 --- a/_posts/2019-05-01-MarshallsMayUpdate.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -layout: post -nav-class: dark -categories: marshall -title: Marshall's April Update -author-id: marshall ---- - -There are four main areas where I spend my time. - -* Libc++, where I am the "code owner" -* WG21, where I am the chair of the Library Working Group (LWG) -* Boost -* Speaking at conferences - -# Libc++ - -The next big milestone for libc++ is the LLVM 9.0 release this summer. We're working towards that, implementing new features and fixing bugs. - -As the "code owner" for libc++, I also have to review the contributions of other people to libc++, and evaluate and fix bugs that are reported. That's a never-ending task; there are new contributions ever day. - -### LWG papers implemented this month. - -* [P0811](https://wg21.link/P0811) Add `std::midpoint` and `std::lerp` for C++20 - - -### LWG issues resolved this month - -* [2960](https://wg21.link/lwg2960) nonesuch is insufficiently useless -* [2977](https://wg21.link/lwg2977) unordered_meow::merge() has incorrect Throws: clause -* [2164](https://wg21.link/lwg2164) What are the semantics of `vector.emplace(vector.begin(), vector.back())`? - - -### LLVM features implemented this month (certainly incomplete) - -* Fixed the implementations of `list::remove_if` and `list::unique` to deal with values or predicates that are elements in the list. Same for `forward_list`. We did this for `remove` already, but now we do it for the other operations as well. - -* Added a bunch of new tests for things that we were missing -** `list::sort` and `forward_list::sort` are required to be stable. -** You can't use `match_results` until you've done a regex search. Our tests did this in several places; now we have assertions to prevent that. -` - -### LLVM bugs resolved this month (probably incomplete) - - -* [Bug 41323](https://llvm.org/PR41323) Race condition in `steady_clock::now` for `_LIBCPP_WIN32API` -* [Bug 41130](https://llvm.org/PR41130) `operator/` of `std::chrono::duration` and custom type. -* [Bug 41577](https://llvm.org/PR41577) test/std/utilities/optional/optional.object/optional.object.ctor/move.fail.cpp has wrong assumption. -* I spent a fair amount of time on [Bug 39696](https://llvm.org/PR39696) "Workaround "error: '(9.223372036854775807e+18 / 1.0e+9)' is not a constant expression"; which turned out to be a GCC bug on PowerPC machines. - - -Also, there was a series of general cleanups in the libc++ tests to improve portability and readability. I added a bunch of updates for debug-mode, and there were several places where we assumed that `string::compare` returned `-1/0/1` instead of what was specified, which is `\<0/0/\>0`. Also, I added tests for `std::any_cast` and array types. - - -The current status of libc++ can be found here: -* [C++20 status](https://libcxx.llvm.org/cxx2a_status.html) -* [C++17 status](https://libcxx.llvm.org/cxx1z_status.html) -* [C++14 status](https://libcxx.llvm.org/cxx1y_status.html) (Complete) -* [Libc++ open bugs](https://bugs.llvm.org/buglist.cgi?bug_status=__open__&product=libc%2B%2B) - - - -# WG21 - -There were no WG21 meetings in April. However, LWG held three teleconferences this month, reviewing papers in advance of the July meeting. We'll have more teleconferences in May. - -I am working on more "cleanup" papers similar to [P1458 - Mandating the Standard Library: Clause 16 - Language support library](https://wg21.link/P1458), and my [P0805 - Comparing Containers](https://wg21.link/P0805) needs an update. - -The goal of the July meeting is to have a "Committee Draft" (CD) of the proposed C++20 standard that can be sent out for review. - -Also on my TODO list is to attempt to implement some of the proposals that are coming up for a vote in July (`flat_map`, text formatting, etc). - -# Boost - -We released [Boost 1.70](https://www.boost.org/users/history/version_1_70_0.html) on the 12th of April. - -Once again, I was the release manager, which involved a bunch of "process management"; things like assembling the release candidates, packaging up release notes, deciding which problems that came up would be fixed (and which ones would not), and updating the web site (and so on, and so on). - - - -# Conferences - -This was a big travel month. I gave two presentations: - -* At the [LLVM European Developer's conference](https://llvm.org/devmtg/2019-04/) in Brussels, I gave a 30 minute overview of the changes that were coming to the standard library for C++20. - -* At [ACCU](https://conference.accu.org/) in Bristol, England, I gave a talk titled ["Navigating the development and evolution of a library"](https://conference.accu.org/2019/sessions.html#XNavigatingthedevelopmentandevolutionofalibrary) - - -In May, I will be speaking at: -* [CppNow](http://www.cppnow.org), May 5-10 in Aspen, CO - -I have submitted a talk for [CppCon](https://www.cppcon.com) in September, but I will not hear back about this for a month or two. diff --git a/_posts/2019-05-05-DamiansMayUpdate.md b/_posts/2019-05-05-DamiansMayUpdate.md deleted file mode 100644 index 9d6064735..000000000 --- a/_posts/2019-05-05-DamiansMayUpdate.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, damian -title: Damian's April Update -author-id: damian ---- - -This month I've been working on the following projects: -- Certify -- Boost.Beast -- Boost.Build -- BeastLounge - -# Certify -Certify did not have any platform-independent means of caching certificate -status (i.e. revoked, valid, unknown), so I implemented one. For now it has to -be manually filled, but I'll add a way to import a static blacklist (somewhat -similar to the builtin blacklist in Chrome) and query the status of a -certificate. Unfortunately there is no way to handle OCSP stapling within the -verification callback invoked by OpenSSL which is quite detrimental to -usability. Additionally, OpenSSL doesn't have a way of starting and waiting for -an asynchronous operation within callbacks (without blocking). - -Don't forget to star the repository: [https://github.com/djarek/certify](https://github.com/djarek/certify)! - -# Boost.Beast -When working on making sure Beast is `std::launder`-correct, I ran into a number -of previously undiagnosed bugs in Beast. All of them have been fixed in -[v254](https://github.com/boostorg/beast/pull/1598/files). I was quite confused -why these issues weren't found by CI previously. I've been able to track it down -to old toolchain versions in Travis. Additionally, the test matrix lacks a few -fairly important variants. Considering the fact that Trusty is no longer -supported and the switch to Xenial is inevitable, I've decided to port over the -CI to Azure Pipelines, because it offers better concurrency which allows the -Beast CI to afford a larger test matrix. In the process I've also decided to use -as many default b2 options as possible, to make future changes to the CI easier. -There's still an issue with Valgrind in Xenial to be resolved (doesn't support -the `RDRAND` instruction). - -# Boost.Build -While working on the AzP CI for Beast, I found out that the `coverage` feature -in `b2` doesn't actually set build flags. `coverage=all` will now properly cause -tests to produce `gcno` and `gcda` files for consumption by the lcov tool. - -# BeastLounge -When experimenting with the BeastLounge application running on Heroku I found -out that Heroku's router has a builtin 55s timeout which dropped websocket -connections. I solved the issue by making the websocket ping timeouts -configurable. diff --git a/_posts/2019-06-01-MarshallsJuneUpdate.md b/_posts/2019-06-01-MarshallsJuneUpdate.md deleted file mode 100644 index c97aebf31..000000000 --- a/_posts/2019-06-01-MarshallsJuneUpdate.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -layout: post -nav-class: dark -categories: marshall -title: Marshall's May Update -author-id: marshall ---- - -There are four main areas where I spend my time. - -* Libc++, where I am the "code owner" -* WG21, where I am the chair of the Library Working Group (LWG) -* Boost -* Speaking at conferences - -# Libc++ - -The next big milestone for libc++ is the LLVM 9.0 release this summer. We're working towards that, implementing new features and fixing bugs. - -As the "code owner" for libc++, I also have to review the contributions of other people to libc++, and evaluate and fix bugs that are reported. That's a never-ending task; there are new contributions ever day. - -This month was spent concentrating on code reviews and bug reports; so I implemented very little "new code". - -There was a lot of "infrastructure work" done on libc++ this month; a large cleanup of the test suite (still in progress), a bunch of work on debug mode for the library (also still in progress) - -### LWG issues resolved this month in libc++ - -* [2960](https://wg21.link/lwg3204) `sub_match::swap` only swaps the base class - -### LLVM features implemented this month (certainly incomplete) - -* Improved the behavior of the compiler intrinsic `__is_base_of`. Clang no longer generates an error when you ask about inheritance relationships with unions, even if the non-union class is incomplete. This intrinsic is used by libc++ to implement `std::is_base_of`. - -* Fixed a few `regex` bugs, and improved the `regex` tests in C++03. - -### LLVM bugs resolved this month (probably incomplete) - -* [Bug 42037](https://llvm.org/PR42037) C++2a `std::midpoint``'s "Constraints" are not implemented -* [Bug 41876](https://llvm.org/PR41876) `std::hash` should not accept `std::basic_strings` with custom character traits - - -The current status of libc++ can be found here: -* [C++20 status](https://libcxx.llvm.org/cxx2a_status.html) -* [C++17 status](https://libcxx.llvm.org/cxx1z_status.html) -* [C++14 status](https://libcxx.llvm.org/cxx1y_status.html) (Complete) -* [Libc++ open bugs](https://bugs.llvm.org/buglist.cgi?bug_status=__open__&product=libc%2B%2B) - - - -# WG21 - -There were no WG21 meetings in April. However, LWG held one teleconference this month, reviewing papers in advance of the July meeting. We'll have more teleconferences in June. - -I am working on more "cleanup" papers similar to [P1458 - Mandating the Standard Library: Clause 16 - Language support library](https://wg21.link/P1458), and my [P0805 - Comparing Containers](https://wg21.link/P0805) needs an update. - -The goal of the July meeting is to have a "Committee Draft" (CD) of the proposed C++20 standard that can be sent out for review. - -Also on my TODO list is to attempt to implement some of the proposals that are coming up for a vote in July (`flat_map`, text formatting, etc). - -# Boost - -It's been a quiet month for boost (except for C++ Now, the conference formerly known as BoostCon). - -There are a couple of good trip reports for C++Now: -* [Matthew Butler](https://maddphysics.com/2019/05/13/cnow-2019-trip-report/) -* [JeanHeyd Meneide](https://thephd.github.io/c++now-2019-trip-report) - -The next [Boost release cycle](https://www.boost.org/development/index.html) is starting soon; with the deadline for new libraries coming up later this month. I'm hoping to mentor a new release manager with this release. - - -# Conferences - -Another travel month. I spent a bunch of time away from home, but only one conference: - -* At [C++ Now](http://www.cppnow.org) in Aspen, CO, I presented "The View from a C++ Standard Library Implementor", which was voted the runner-up for "Most Engaging" talk. - -I have submitted a talk for [CppCon](https://www.cppcon.com) in September, but I will not hear back about this for a month or two. - -I have submitted talks for [C++ Russia](https://cppconf-piter.ru/en/) and [Meeting C++](https://meetingcpp.com), which are both very close (timewise) to the Belfast WG21 meeting, but I haven't heard back yet. - -I am looking forward to being at home for the entire month of June. diff --git a/_posts/2019-06-10-DamiansJuneUpdate.md b/_posts/2019-06-10-DamiansJuneUpdate.md deleted file mode 100644 index b5e130211..000000000 --- a/_posts/2019-06-10-DamiansJuneUpdate.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, damian -title: Damian's May Update -author-id: damian ---- - -This month I've been working on the following projects: -- Certify -- Boost.Beast -- Boost.Build - -# Certify -This month, I've worked on expanding the documentation of Certify, especially -the example and introduction parts. When looking through the documentation for -Boost.Build I found out it's possible to import snippets from *.cpp files -into the documentation, which will allow me to make sure that snippets in -the documentation compile and are tested. I've also attempted cleaning up the -Certify build script to use the OpenSSL module in b2, but I ran into issues, so -I'll have get back to this one in the future. - -Don't forget to star the repository: [https://github.com/djarek/certify](https://github.com/djarek/certify)! - -# Boost.Beast -I've been able to complete the port of the Beast CI to Azure Pipelines and expand -the test matrix beyond what was tested in the existing CI infrastructure. Thanks -to the expanded concurrent job limit, a full run on AzP takes less time than a -full Travis and Appveyor build, especially when wait times are taken into accout. -One of the matrix items I added were tests for header-only no-deprecated builds, -which turned out to be broken. Untested code has a nasty tendency to rot. -I've also been able to identify some function templates in `http::basic_fields` -which could be turned into regular functions. One of them, was instantiated -4 times because they were passed a predicate which was a lambda expression. -These two changes turned out to be fairly significant, because they allowed -shaving off at least 10 KiB of binary size per instantiation (amd64, -O3). - -# Boost.Build -When working on the Azure Pipelines CI for Beast I noticed that b2 doesn't support -the leak sanitizer, so I decided to add it. It's available via the `leak-sanitizer=on` feature. diff --git a/_posts/2019-07-02-MarshallsJulyUpdate.md b/_posts/2019-07-02-MarshallsJulyUpdate.md deleted file mode 100644 index a345ead35..000000000 --- a/_posts/2019-07-02-MarshallsJulyUpdate.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -layout: post -nav-class: dark -categories: marshall -title: Marshall's June Update -author-id: marshall ---- - -There are four main areas where I spend my time. - -* Libc++, where I am the "code owner" -* WG21, where I am the chair of the Library Working Group (LWG) -* Boost -* Speaking at conferences - -# Libc++ - -The next big milestone for libc++ is the LLVM 9.0 release this summer. We're working towards that, implementing new features and fixing bugs. The "Branch for release" is currently scheduled for July 18th. - -As the "code owner" for libc++, I also have to review the contributions of other people to libc++, and evaluate and fix bugs that are reported. That's a never-ending task; there are new contributions ever day. - -I created a [status page](https://libcxx.llvm.org/upcoming_meeting.html) for the LWG issues and papers that are already set up for a vote at the Cologne WG21 meeting. - - -### LWG issues resolved this month in libc++ (almost certainly incomplete) - -* [LWG2221](https://wg21.link/LWG2221) No formatted output operator for `nullptr` -* [LWG3206](https://wg21.link/LWG3206) `year_month_day` conversion to `sys_days` uses not-existing member function - - -### LLVM features implemented this month (almost certainly incomplete) - -* [P0553](https://wg21.link/P0553) Bit operations -* [P0556](https://wg21.link/P0556) Integral power-of-2 operations -* [P1355](https://wg21.link/P1355) Exposing a narrow contract for `ceil2` -* [P0646](https://wg21.link/P0646) Improving the Return Value of Erase-Like Algorithms I - - -### LLVM bugs resolved this month (probably incomplete) - -* [Bug 41843](https://llvm.org/PR41843) `std::is_base_of` should give correct result for incomplete unions -* [Bug 38638](https://llvm.org/PR38638) Wrong constraint for `std::optional::operator=(U&&)` -* [Bug 30589](https://llvm.org/PR30589) `std::complex` with a custom type does not work because of how std::__promote is defined -* [Bug 42396](https://llvm.org/PR42396) Alignment not respected in containers for over-aligned enumeration types -* [Bug 28704](https://llvm.org/PR28704) `num_get::do_get` incorrect digit grouping check -* [Bug 18074](https://llvm.org/PR18074) Undefined references when using pointer to member functions -* [Bug 26503](https://llvm.org/PR26503) `std::quoted` doesn't work with `char16_t` or `char32_t` strings. -* [Bug 41714](https://llvm.org/PR41714) `std::tuple<>` is not trivially constructible -* [Bug 36863](https://llvm.org/PR36863) `basic_string_view(const CharT*, size_type)` constructor shouldn't comment out assert of nullptr and length checks -* [Bug 42166](https://llvm.org/PR42166) `to_chars` can puts leading zeros on numbers - - -### Other LLVM bits from this month (certainly incomplete) - -* [Revision 364545](https://llvm.org/r364545) Provide hashers for `string_view` only if they are using the default `char_traits`. Reported on [StackOverflow](https://stackoverflow.com/questions/56784597/is-libc-providing-hash-specialization-for-too-many-basic-string-views/56792608#56792608) - -* Reworked `to_string` to use `to_chars`. Much faster, and avoids having multiple implementations. This involved reworking `to_chars` so that it was available back to C++03. _I did all of the `to_chars` refactoring, but not the `to_string` rework._ - - -The current status of libc++ can be found here: -* [C++20 status](https://libcxx.llvm.org/cxx2a_status.html) -* [C++17 status](https://libcxx.llvm.org/cxx1z_status.html) -* [C++14 status](https://libcxx.llvm.org/cxx1y_status.html) (Complete) -* [Libc++ open bugs](https://bugs.llvm.org/buglist.cgi?bug_status=__open__&product=libc%2B%2B) - - -# WG21 - -The next WG21 meeting is July 15-20 in Cologne, Germany. - -There were no WG21 meetings in June. We (LWG) held four teleconference this month, reviewing papers in advance of the July meeting, and will have another one next week. - -I had seven papers in the pre-Cologne mailing: -* [P1718R0: Mandating the Standard Library: Clause 25 - Algorithms library](https://wg21.link/P1718) -* [P1719R0: Mandating the Standard Library: Clause 26 - Numerics library](https://wg21.link/P1719) -* [P1720R0: Mandating the Standard Library: Clause 28 - Localization library](https://wg21.link/P1720) -* [P1721R0: Mandating the Standard Library: Clause 29 - Input/Output library](https://wg21.link/P1721) -* [P1722R0: Mandating the Standard Library: Clause 30 - Regular Expression library](https://wg21.link/P1722) -* [P1723R0: Mandating the Standard Library: Clause 31 - Atomics library](https://wg21.link/P1723) -* [P1724R0: C++ Standard Library Issues to be moved in Cologne](https://wg21.link/P1724) - -The goal of the July meeting is to have a "Committee Draft" (CD) of the proposed C++20 standard that can be sent out for review. - -Also on my TODO list is to attempt to implement some of the proposals that are coming up for a vote in July (`flat_map`, text formatting, etc). - -# Boost - -The next [Boost release cycle](https://www.boost.org/development/index.html) is in process; I am helping Michael Caisse as release manager with this release. - - -# Conferences - -Upcoming talks: -* [C++ Russia](https://cppconf-piter.ru/en/) is at the end of October in St. Petersburg. -* [Meeting C++](https://meetingcpp.com) is in mid-November in Berlin. - -I have submitted a talk for [CppCon](https://www.cppcon.com) in September, but I will not hear back about this for a month or two. - -I submitted a talk for [ACCU Autumn](https://conference.accu.org), which is in Belfast right after the WG21 meeting, but I haven't heard back about that yet. In any case, I will be attending this conference, since it's in the same hotel as the WG21 meeting, and starts two days after the WG21 meeting, and concludes right before Meeting C++. diff --git a/_posts/2019-07-14-DamiansJulyUpdate.md b/_posts/2019-07-14-DamiansJulyUpdate.md deleted file mode 100644 index 1154aad1d..000000000 --- a/_posts/2019-07-14-DamiansJulyUpdate.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, damian -title: Damian's June Update -author-id: damian ---- - -This month I've been working on the following projects: -- Certify -- Boost.Beast - -# Certify -After quite a bit of work exploring ways to make certificate verification more complete, -I've concluded that Boost is currently missing a few tools to make that viable. -A comprehensive solution requires, at the very least, a functional HTTP client -able to handle higher-level semantics like redirects, proxies or compressed bodies. -While these are unlikely to happen while performing an OCSP query or downloading -a CRL set from Google's update service, they still need to be handled, otherwise -the user will be left in a no better state than when no library is used. -At this point, Certify only offers basic verification, but that is still -simillar level to what cURL does. Providing a comprehensive solution will require -either a infrastructure solution (something like Google's CRLsets) or -a library based one (i.e. build up all the required libraries to be able -to perform proper certificate status checks). - -# Boost.Beast -I've continued the work on expanding split compilation in Beast, by turning some -internal function templates, in websocket code, into regular functions. Additionally, -I've simplified the websocket prng code after proving with some benchmarks that the -previous solution made it worse both for the fast case (with TLS enabled) -and the slow one. The speed up is marginal, but it made the code much simpler -and reduced the size of binaries by small amount (a few K at best). -I've also worked to cleaning up some of the compilation warnings that I found -using the new Azure Piepelines CI in Beast. I also had to deal with an an odd case of -miscompilation under MSVC 14.2 (x64 Release), where the use of `static_string<7>` -failed tests with paritally garbage output while `static_string<8>` succeeded. - diff --git a/_posts/2019-08-05-MarshallsAugustUpdate.md b/_posts/2019-08-05-MarshallsAugustUpdate.md deleted file mode 100644 index 557f2ee37..000000000 --- a/_posts/2019-08-05-MarshallsAugustUpdate.md +++ /dev/null @@ -1,118 +0,0 @@ ---- -layout: post -nav-class: dark -categories: marshall -title: Marshall's July Update -author-id: marshall ---- - -There are four main areas where I spend my time. - -* Libc++, where I am the "code owner" -* WG21, where I am the chair of the Library Working Group (LWG) -* Boost -* Speaking at conferences - -This month, the big news (and the big work item) was the approval of the C++ "Committee Draft" at the WG21 meeting in Cologne on July 15-20. - -You can think of this as a "beta version" of the C++20 standard; all features are complete. The next step is bug fixing, with an eye towards releasing next year. - -# Libc++ - -The LLVM 9.0 release is on track for September. We have a release branch, and the RC1 was recently dropped. - -Because of the run-up and the aftermath of the Cologne meeting, the libc++ accomplishments are a bit sparse this month. - -As the "code owner" for libc++, I also have to review the contributions of other people to libc++, and evaluate and fix bugs that are reported. That's a never-ending task; there are new contributions ever day. - -### LWG issues resolved this month in libc++ (almost certainly incomplete) - -* [LWG2273](https://wg21.link/LWG2273) `regex_match` ambiguity - - -### LLVM features implemented this month (almost certainly incomplete) - -* [P1612](https://wg21.link/P1612) Relocate endian -* [P1466](https://wg21.link/P1466) Parts of P1466 "Misc Chrono fixes" more to come here. - - -### LLVM bugs resolved this month (definitely incomplete) - - - -### Other interesting LLVM bits from this month (certainly incomplete) - -* [Revision 365854](https://llvm.org/r365854) Reorganize the `` header to make most of the facilities available for internal use pre-C++20. NFC for external users. - -* [Revision 367120](https://llvm.org/r367120) Fix a bug in std::chrono::abs where it would fail when the duration's period had not been reduced. - -* [Revision 364884](https://llvm.org/r364884) Add an internal call `__libcpp_is_constant_evaluated`, which works like `std::is_constant_evaluated`, except that it can be called at any language level (back to C++98). For older languages, it just returns `false`. This gets rid of a lot of ifdefs in the libc++ source code. - - -The current status of libc++ can be found here: -* [C++20 status](https://libcxx.llvm.org/cxx2a_status.html) -* [C++17 status](https://libcxx.llvm.org/cxx1z_status.html) -* [C++14 status](https://libcxx.llvm.org/cxx1y_status.html) (Complete) -* [Libc++ open bugs](https://bugs.llvm.org/buglist.cgi?bug_status=__open__&product=libc%2B%2B) - - -# WG21 - -As I said above, we shipped a CD out of Cologne. Now we wait for the National Bodies (members of ISO, aka "NBs") to review the draft and send us comments. When we've resolved all of these comments, we will send the revised draft out for balloting. If the NBs approve, then that draft will become C++20. - -We approved many new features for C++20 in Cologne: -* [P0553](https://wg21.link/0553) - Bit Operations -* [P0980](https://wg21.link/0980) - Constexpr `string` -* [P1004](https://wg21.link/1004) - Constexpr `vector` -* [P1065](https://wg21.link/1065) - Constexpr `INVOKE` -* [P1135](https://wg21.link/1135) - The C++20 Synchronization Library -* [P1208](https://wg21.link/1208) - Source Location -* [P0645](https://wg21.link/0645) - Text Formatting -* [P1361](https://wg21.link/1361) - Integration of `chrono` with text formatting -* [P1754](https://wg21.link/1754) - Rename concepts to standard\_case for C++20, while we still can -* [P1614](https://wg21.link/1614) - Spaceship integration in the Standard Library -* [P0600](https://wg21.link/0600) - Stop Tokens and a Joining Thread -* [P0631](https://wg21.link/0631) - Math Constants - -We also did not approve many proposed features. Most of these were not approved because we ran out of time, rather than any fault of theirs: -* [P1391](https://wg21.link/1391) - Range constructors for `string_view` -* [P1394](https://wg21.link/1394) - Range constructors for `span` -* [P0288](https://wg21.link/0288) - `any_invokable` -* [P0201](https://wg21.link/0201) - `polymorphic_value` -* [P0429](https://wg21.link/0429) - A Standard flatmap -* [P1222](https://wg21.link/1222) - A Standard flatset -* [P0533](https://wg21.link/0533) - constexpr for cmath -* [P0792](https://wg21.link/0792) - `function_ref` -* [P0881](https://wg21.link/0881) - A Proposal to add stacktrace library -* [P1272](https://wg21.link/1272) - Byte-swapping -* [P0627](https://wg21.link/0627) - Function to mark unreachable code -* _and many others_ - - -I still have a bunch of mechanical changes that need to be made before we ship: -* [P1718R0: Mandating the Standard Library: Clause 25 - Algorithms library](https://wg21.link/P1718) -* [P1719R0: Mandating the Standard Library: Clause 26 - Numerics library](https://wg21.link/P1719) -* [P1720R0: Mandating the Standard Library: Clause 28 - Localization library](https://wg21.link/P1720) -* [P1721R0: Mandating the Standard Library: Clause 29 - Input/Output library](https://wg21.link/P1721) -* [P1722R0: Mandating the Standard Library: Clause 30 - Regular Expression library](https://wg21.link/P1722) -* [P1723R0: Mandating the Standard Library: Clause 31 - Atomics library](https://wg21.link/P1723) - -We polled the NBs before Cologne, and they graciously agreed to have these changes made post-CD. - -# Boost - -The next [Boost release cycle](https://www.boost.org/development/index.html) is in process; I am helping Michael Caisse as release manager with this release. We should have a release in the next couple of weeks. - - -# Conferences - -Upcoming talks: -* [CppCon](https://www.cppcon.com) in September in Denver. -* [C++ Russia](https://cppconf-piter.ru/en/) is at the end of October in St. Petersburg. -* [ACCU Autumn](https://conference.accu.org) is right after the WG21 meeting in early November. -* [Meeting C++](https://meetingcpp.com) is in mid-November in Berlin. - -I will be making the "Fall 2019 C++ European Tour", going from St. Petersburg to Belfast to Berlin before heading home mid-November. diff --git a/_posts/2019-08-19-DamiansAugustUpdate.md b/_posts/2019-08-19-DamiansAugustUpdate.md deleted file mode 100644 index 7d3f01318..000000000 --- a/_posts/2019-08-19-DamiansAugustUpdate.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, damian -title: Damian's July Update -author-id: damian ---- - -# Boost.Beast - -I've started working on improvements to the zlib part of Beast. There are some gaps -in the test harness of these components, so I've decided to increase coverage. -As a first step, I started porting test cases from the original zlib library's tests, -to verify that existing code matches the expected behavior of the original library. -Fortunately, I've not found any significant discrepancies, there's only one issue -where Beast rejects malformed input for the wrong reason (I'm still looking into it -whether it's actually an issue at the time of writing this). - -I've also looked into providing more meaningful feedback from test failures in Beast, -especially when they're run in CI. While the current test framework does print -a line number on failure, the line number is often in a function template that's called -by multiple test cases, which makes it quite hard to determine which test failed -just from the log, often requiring the use of a debugger. Doing that locally -may not be a problem, but it's significantly harder in CI, so I've decided to -try to use Boost Stacktrace to provide a callstack on each failure in Beast tests. -Additionally, I've also worked on running the test suite without OpenSSL installed, -to hopefully fix some of the failures in the official Boost test matrix. - -# The question of Networking TS and TLS - -There's recently been quite a bit of discussion of networking being useless -without "secure by default" sockets. Since this is a recurring topic and I expect it to return in the future, -so I've decided to write up an analysis of this issue. - -First of all, I believe that an attempt to deliver a "secure by default" socket -within the current networking proposal right now will result in something like -`std::async` - not really practically useful. - -What kind of TLS facilities I'd consider useful for the average user of the standard library? -A reasonable guideline, I think, are ones I could trust to be used in a distributed -system that handles money (in any form). -Note, that TLS is not only a protocol that provides confidentiality (i.e. encryption), -but also allows verification of the identity either the server by the client, or both. -Remember, doesn't matter if 3rd parties can't see what you're sending, -if you're sending your data to the wrong peer in the first place! - -While it may seem simple at first look, just verifying the identity of a peer -is an extremely complex process, as my experience with Certify has shown. -Doing it portably and efficiently with the same interface and effects is extremely difficult. -Browsers resort to all kinds of workarounds and custom solutions to be able -to securely implement just this one aspect of TLS. I attempted to implement -a library (intended for inclusion into Boost) that would perform this one aspect, -however, I found it to be impossible to provide a practical solution with -the current state of the networking ecosystem in Boost. In fact, one method -of certificate verification (via the OCSP protocol) requires a (very) basic -HTTP client. Yes, in order to perform a TLS handshake and verify the peer's -certificate status using OCSP, you need an HTTP client. - -This is just one aspect of the TLS protocol that needs to be addressed. -There are others as well - what about the basic cryptographic building blocks, -like ciphers, hashing algorithms, PRFs and so on - they are bound to be used -in a hypothetical implementation in a standard library, should they be exposed? If yes then with what interface?. -Considering that there are no standard networking facilities and not even a proposal for standard TLS, -this is a discussion that would essentially postpone standard networking indefinitely. - -Finally, there's also an opposite position that no networking should be -in the standard at all. I disagree with this position - networking has become a very important -part of many C++ projects (in my career, all C++ projects I dealt with, touched -some sort of network in one way or another). -At the very least we need standard named requirements for library compatibility, since that is -severely lacking in the ecosystem at this point. diff --git a/_posts/2019-09-01-Gold-sponsor-of-Cppcon-2019.md b/_posts/2019-09-01-Gold-sponsor-of-Cppcon-2019.md deleted file mode 100644 index 83e65b46d..000000000 --- a/_posts/2019-09-01-Gold-sponsor-of-Cppcon-2019.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis ---- -

-The Alliance is a Gold sponsor for -CppCon 2019. This -conference is the annual, week-long face-to-face gathering for the -entire C++ community. The conference is organized by the C++ community -for the community. Attendees enjoy inspirational talks and a friendly -atmosphere designed to help individuals learn from each other, meet -interesting people, and generally have a stimulating experience. -

diff --git a/_posts/2019-09-27-MarshallsOctoberUpdate.md b/_posts/2019-09-27-MarshallsOctoberUpdate.md deleted file mode 100644 index ab898bed4..000000000 --- a/_posts/2019-09-27-MarshallsOctoberUpdate.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -layout: post -nav-class: dark -categories: marshall -title: Marshall's Combined August and September Update -author-id: marshall ---- - -There are four main areas where I spend my time. - -* Libc++, where I am the "code owner" -* WG21, where I am the chair of the Library Working Group (LWG) -* Boost -* Speaking at conferences - -Lots of work these month(s) behind the scenes, getting stuff ready for C++20, LLVM 9, and Boost 1.71.0. - -# Libc++ - -The [LLVM 9.0 release](http://releases.llvm.org/download.html#9.0.0) has shipped! The release date was 19-September, a few days later than planned. There are a lot of new [libc++ features](http://releases.llvm.org/9.0.0/projects/libcxx/docs/ReleaseNotes.html) in the release. - -As the "code owner" for libc++, I also have to review the contributions of other people to libc++, and evaluate and fix bugs that are reported. That's a never-ending task; there are new contributions ever day. - -Many times, bug reports are based on misunderstandings, but require a couple of hours of work in order to figure out where the misunderstanding lies. - -We're working on a major redesign of the "debug mode" for libc++, after we realized that the existing (not widely used) debug mode is useless when you're trying to do things at compile (constexpr) time. - -I have been spending a lot of time the last few weeks working on the calendaring stuff in ``, specifically the interface with the OS for getting time zone information. It is a surprisingly complicated task. Fortunately for me, I have a friend who has been down this road in the past, and is willing to answer questions. - -### LWG issues resolved in libc++ (almost certainly incomplete) - -* [LWG3296](https://wg21.link/LWG3296) Add a missing default parameter to `regex::assign` - -### LLVM features implemented (almost certainly incomplete) - -* [P1466](https://wg21.link/P1466) Parts of P1466 "Misc Chrono fixes" more to come here. - -### LLVM bugs resolved (definitely incomplete) - -* [Bug 42918](https://llvm.org/PR42918) Fix thread comparison by making sure we never pass our special 'not a thread' value to the underlying implementation - -* [Bug 43063](https://llvm.org/PR43063) Fix a couple of unguarded `operator,` calls in algorithm - -* [Bug 43034](https://llvm.org/PR43034) Add a missing `_VSTD::` before a call to `merge`. - -* [Bug 43300](https://llvm.org/PR43300) Add a missing `_VSTD::` Only initialize the streams `cout`/`wcout`/`cerr`/`wcerr` etc once, rather than any time `Init::Init` is called - -### Other interesting LLVM bits from (certainly incomplete) - -* [Revision 368299](https://llvm.org/r368299) Implement `hh_mm_ss` from [P1466](https://wg21.link/P1466). Part of the ongoing `` implementation work. - - -The current status of libc++ can be found here: -* [C++20 status](https://libcxx.llvm.org/cxx2a_status.html) -* [C++17 status](https://libcxx.llvm.org/cxx1z_status.html) -* [C++14 status](https://libcxx.llvm.org/cxx1y_status.html) (Complete) -* [Libc++ open bugs](https://bugs.llvm.org/buglist.cgi?bug_status=__open__&product=libc%2B%2B) - - -# WG21 - -We shipped a CD out of Cologne in July. Now we wait for the National Bodies (members of ISO, aka "NBs") to review the draft and send us comments. When we've resolved all of these comments, we will send the revised draft out for balloting. If the NBs approve, then that draft will become C++20. - -The next WG21 meeting will be November 2-8 in Belfast, Northern Ireland. -This will be the first of two meetings that are focused on resolving NB comments; the second one will be in Prague in February. - -I have several "clean-up" papers for the Belfast mailing. The mailing deadline is a week from Monday (5-October), so I need to finish them up. - -* [P1718R0: Mandating the Standard Library: Clause 25 - Algorithms library](https://wg21.link/P1718) -* [P1719R0: Mandating the Standard Library: Clause 26 - Numerics library](https://wg21.link/P1719) -* [P1720R0: Mandating the Standard Library: Clause 28 - Localization library](https://wg21.link/P1720) -* [P1721R0: Mandating the Standard Library: Clause 29 - Input/Output library](https://wg21.link/P1721) -* [P1722R0: Mandating the Standard Library: Clause 30 - Regular Expression library](https://wg21.link/P1722) -* [P1723R0: Mandating the Standard Library: Clause 31 - Atomics library](https://wg21.link/P1723) - -We polled the NBs before Cologne, and they graciously agreed to have these changes made post-CD. - -# Boost - -[Boost 1.71.0](https://www.boost.org/users/history/version_1_71_0.html) was released on 19-August. Micheal Caisse was the release manager, with some help from me. - -As part of the Boost Community maintenance team, I (and others) made many changes to libraries whose authors are no longer able (or interested) in maintaining them. - -I have a couple of suggestions for additions to the Boost.Algorithms library that I will be working on in the near future. - - -# Conferences - -I was a speaker at [CppCon](https://www.cppcon.com) last week. I gave a new talk "std::midpoint - How hard could it be?" (no link yet) which was quite well received. I got a few questions that will require additional research, and may improve my implementation. - -I also participated in the "Committee Fireside Chat", at CppCon, where conference members get to ask questions of the committee members who are present. - - -Upcoming talks: -* [LLVM Developer's Conference](http://llvm.org/devmtg/2019-10/) is in San Jose in October. I will not be speaking, but I will be moderating the lightning talks. -* [C++ Russia](https://cppconf-piter.ru/en/) is at the end of October in St. Petersburg. -* [ACCU Autumn](https://conference.accu.org) is right after the WG21 meeting in early November. -* [Meeting C++](https://meetingcpp.com) is in mid-November in Berlin. - -I will be making the "Fall 2019 C++ European Tour", going from St. Petersburg to Belfast to Berlin before heading home mid-November. diff --git a/_posts/2019-3-06-Gold-sponsor-of-C++-now.md b/_posts/2019-3-06-Gold-sponsor-of-C++-now.md deleted file mode 100644 index dcc0a0618..000000000 --- a/_posts/2019-3-06-Gold-sponsor-of-C++-now.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis -title: Gold sponsor of C++Now 2019 ---- -The Alliance is a Gold sponsor for -C++Now 2019. This -conference is a gathering of C++ experts and enthusiasts from around -the world in beautiful Aspen, Colorado from May 5, 2019 - May 10, 2019. diff --git a/_posts/2020-01-31-RichardsJanuaryUpdate.md b/_posts/2020-01-31-RichardsJanuaryUpdate.md deleted file mode 100644 index 508069801..000000000 --- a/_posts/2020-01-31-RichardsJanuaryUpdate.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's January Update -author-id: richard ---- - -# History - -This is my first entry on the C++ Alliance web site. I'm very happy to say that I was invited to join the organisation -at the end of December last year. - -I first met Vinnie on Slack when I chose to use [Boost.Beast](https://github.com/boostorg/beast) in a -greenfield project - a highly scalable market data distribution system and quoting gateway for the Japanese -cryptocurrency exchange [liquid.com](http://liquid.com). - -There were a number of candidates for C++ HTTP frameworks and it is interesting for me to examine the decision-making -process I went through in choosing one. - -If I am honest, there are two main factors that influenced me towards Boost.Beast: - -1. I am a long-time fanboi of [Boost.Asio](https://github.com/boostorg/asio). I find it's paradigm very pleasing. -Once you decipher the (extremely terse!) documentation it becomes obvious that it was written by a hyper-intelligent -extraterrestrial masquerading as a human being. - -2. I have used the [Boost Library](https://www.boost.org/) (or more correctly, libraries) for many years. -Boost has become synonymous with trust, quality and dependability. As far as I have always been concerned, -boost is *the* standard. The standard library has always been a pale shadow of it. - -When I started the new project there was an expectation that I would have a team to work with. In the end, I found -myself writing the entire system alone from scratch. - -Liquid Tap contains two fully-featured web servers (one facing the organisation and one facing the outside world), -supports inbound and outbound websocket connectivity (with per-fqdn keepalive connection pooling) and multi-threaded -operation. The project took me 3 months from writing the first line of code to full production readiness. - -I was personally impressed by the speed with which I was able to assimilate a new library and create a fairly complex -service infrastructure using nothing more than boost, nlohmann_json, openssl and a c++17 compiler. In my view -this would not have been possible without the excellent documentation and careful attention to detail in Boost.Beast. - -During the development, I reached out to Vinnie and Damian on Slack a number of times. Both were always helpful -and attentive. Without a doubt they were instrumental in the success of my project. - -So here I am in January 2020. Just like the old TV advert where Victor Kayam was so impressed with his electric -shaver that, "I bought the company". - -I was so impressed with Boost.Beast and its creators that when given the chance, I chose to join the company. - -# First Month - -I have spent my first month with the firm going through Vinnie's Boot Camp. It's been an interesting and invigorating -experience. - -I have many years of experience writing code for production environments, from embedded systems like slot machines -and video games to defence to banking and trading systems. I'm fairly confident that if you can describe it, I can -write it. - -But maintaining a publicly-facing library is a new and very different challenge. - -## Controlling the Maintenance Burden - -C++ is a language in which types are cheap. So cheap in fact that many people (including me) recommend describing any -concept in a program as its own type. This is a great way to cause logic errors to fail to compile, rather than fail -to impress your customers. - -But in a library such as Boost.Beast, every public type you create is a type you must document and maintain. If you -discover that your type has a design flaw, you're a little stuck. Any future changes need to be source-compatible -with previous versions of the library. Dangerous or incorrect elements in the design must be deprecated gracefully. -All this requires careful management. - -Management takes time. - -Something I learned from Vinnie very quickly is that for this reason, interfaces to public objects should provide -the bare minimum functionality that users can demonstrate a need for. Adding a new public method or class should only -happen after careful consideration of the consequences. - -## Supporting all Toolchains - -Another consideration I have never had to encounter before is that Boost.Beast is designed to work on every compiler -that "robustly supports" C++11. - -In my career as a software engineer I have always demanded (and mostly had) autonomy over the choice of toolset. Of -course I have always chosen the very latest versions of gcc, clang and msvc and used the very latest standard of -c++ available. Why wouldn't I? It improves productivity, and who wants to be stuck in the Dark Ages when all your -friends are using the new cool stuff? - -I wrote Liquid Tap in C++17. If C++2a had been more advanced and supported by all compilers at the time I'd have used -that, because compiler-supported coroutines would have shortened the code and made debugging and reasoning about -sequencing a whole lot easier. - -Now I find myself thinking about how to support the latest features of the language, while ensuring that the library -will continue to function for the many C++11 and 14 users who have not been as fortunate as me and are still -constrained by company policy, or indeed are simply happy not to have to learn the new features available in more -recent standards. - -## Attention to Detail - -Boost.Beast strives for 100% (or as close to it as possble) coverage in testing. This is only logical. Users are not -going to be happy if they have to continually decipher bugs in their programs caused by us, file bug reports and -either patch their Boost source or wait up to three months for another release. - -In addition, documentation matters. You know how it is in a production system. More effort is spent on content and -utility than documentation. Developers are often expected to read the code or ask someone if there's something they -don't understand. Not so when maintaining a library for public consumption. - -One of the reasons I chose Boost.Beast was the quality, completeness and accuracy of its documentation. This is no -accident. Vinnie and his team have put a lot of time into it. Only now am I becoming aware of what an Herculean -task this is. - -Users will hold you to your word, so your word had better be the _Truth, the Whole Truth and Nothing But the Truth_. - -# Activities - -## Issue Maintenance - -This month I have been working through some of the Issue backlog in Boost.Beast. It's satisfying to see PRs getting -accepted and the list of open issues being whittled away. At the moment it's interesting to see new issues and -queries being raised too. I'll revisit this in next month's blog and report as to whether it's still interesting -then :) - -I have also been taking time to liaise with users of the library when they raise queries via the -[Issue Tracker](https://github.com/boostorg/beast/issues), email or the -[Slack Channel](https://cpplang.slack.com/archives/CD7BDP8AX). I think staying in touch with the users is an excellent -way to get feedback on the quality of documentation and design. It's also nice to be able to help people. Not something -you get time to do very often when working on an FX-options desk in an investment bank. - -## Boost.Json - -I have been providing some support to the upcoming [Boost.JSON](https://github.com/vinniefalco/json) library. -This library focusses on: -* Absolute correctness in reference to [RFC8259](https://datatracker.ietf.org/doc/rfc8259/). -* Seeking to match or exceed the performance of other c++ JSON libraries such as [RapidJSON](https://rapidjson.org/). -* Providing a clean, unsurprising programming interface and impeccable documentation. - -This is a fascinating project for me. Various JSON libraries employ various tricks for improving performance. -Performance can be gained at the expense of rigorous syntax checking, use of buffers and so on. Achieving the Holy -Grail of full correctness, minimal state and absolute performance will be an interesting challenge. - -## Boost.URL - -Vinnie is also working on [Boost.URL](https://github.com/vinniefalco/url). While I have not contributed any code, -spending my time in the `#beast` Slack channel has meant that I've been able to keep up to speed with the various -design choices being made. Again, there has been much to learn about a design rationale that focuses heavily on -the potential maintenance burden. - -There is actually a lot that could be learned by developers in industry from taking part in or observing this -discourse. - -# Work Schedule - -The C++ Alliance is based on the West Coast of the USA, while I live and work in the tiny Principality of -[Andorra](https://en.wikipedia.org/wiki/Andorra) in mainland Europe. This puts me some nine hours ahead of my -colleagues across the Pond. - -It turns out that this is a perfect way of working for me. I can get up at 8am, nip out for a couple of hours skiing or -hiking, enjoy lunch and then get to work - before anyone else in the firm is even awake. - -I'm a bit of a night-owl anyway, so working later in order to engage in "lively debate" with my colleagues on Slack -is no problem. It also means I have a legitmate excuse to get out of any social engagments I don't want to be bothered -with. - -So for me it's all win. - -# Summary - -I've really enjoyed my first month. I think Vinnie worries that he'll nag me too much about seemingly unimportant -details like commit message wording and achieving a certain tone in code documentation, but I don't mind it. - -Boost.Beast is a fantastic piece of work. It's Vinnie's baby, and I am privileged to be asked to hold it in my -hands. - -I'm never going to take issue with a mother looking to protect her cubs. - -Furthermore, having a legitimate excuse to interact with the other maintainers of Boost on Slack is a pleasure. -These people are some of the brightest minds on the planet. I live in hope that some of this brilliance will -rub off. - -If you work with C++, I highly recommend that you join the [Cpplang](http://slack.cpp.al) Slack channel. - -If you'd like to contact me to discuss my experiences I'd be happy to receive a message on Slack. - -Thanks for reading. - -Richard Hodges diff --git a/_posts/2020-02-29-RichardsFebruaryUpdate.md b/_posts/2020-02-29-RichardsFebruaryUpdate.md deleted file mode 100644 index 7f5893f95..000000000 --- a/_posts/2020-02-29-RichardsFebruaryUpdate.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's February Update -author-id: richard ---- - -# The Lesser HTTP Refactor - -Aside from the normal answering of queries and [issues](https://github.com/boostorg/beast/issues), February has been a -month dominated by the difference between the `asio::DynamicBuffer_v1` and `asio::DynamicBuffer_v2` concepts. - -As I understand things, both Beast and Asio libraries developed the idea of the `DynamicBuffer` concept (or more -correctly, Named Type Requirement \[NTR\]) at roughly the same time, but with slightly different needs. - -The original Asio `DyanmicBuffer` describes a move-only type, designed to be a short-lived wrapper over storage which -would allow a composed operation to easily manage data insertions or retrievals from that storage through models of the -`MutableBufferSequence` and `ConstBufferSequence` NTRs. - -In Beast, it was found that `DynamicBuffer` objects being move-only caused a difficultly, because the necessarily -complex composed operations in Beast need to create a `DynamicBuffer`, perform operations on it, pass it to a -sub-operation for further manipulation and then continue performing operations on the same buffer. - -If the `DynamicBuffer` as been passed by move to a sub operation, then before the buffer can be used again, it will -have to be moved back to the caller by the callee. - -Rather than complicate algorithms, Beast's authors took a slightly different track - Beast `DynamicBuffer`s were specified -to be pass-by-reference. That is, the caller is responsible for the lifetime of the `DynamicBuffer` and the callee is -passed a reference. - -This satisfied Beast's needs but created an incompatibility with Asio and Net.TS. - -Vinnie Falco wrote a [paper](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1100r0.html) on the problem -offering a solution to enabling complex composed operations involving `DynamicBuffer`s. On reflection, LEWG took a -different view and solved the problem by -[re-engineering](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1790r1.html) the `DynamicBuffer` NTR. - -The result is that Boost.Beast objects are now likely to encounter three versions of `DynamicBuffer` objects in the wild -and needs to be able to cope gracefully. - -Boost.Asio now has the NTRs `DynamicBuffer_v1` and `DynamicBuffer_v2`, with the NTR `DynamicBuffer` being a synonym for -either depending on configuration flags (defaulting to `DynamicBuffer_v2`). - -We have had to go a little further and add a new NTR in Beast, `DynamicBuffer_v0`. The meanings of these NTRs are: - -| NTR | Mapping in Asio | Mapping in previous Beast | Overview | -| --- | --------------- | ------------------------- | -------- | -| DynamicBuffer_v0 | none | DynamicBuffer | A dynamic buffer with a version 1 interface which must be passed by reference | -| DynamicBuffer_v1 | DynamicBuffer_v1 | Asio DynamicBuffer (v1) | A dynamic buffer with a version 1 interface which must be passed by move | -| DynamicBuffer_v2 | DynamicBuffer_v2 | none | A dynamic buffer with a version 2 interface which is passed by copy | - -My intention this month was to migrate the entire Beast code base to use Asio's (and current net.ts's) `DynamicBuffer_v2` -concepts while still remaining fully compatible with `DynamicBuffer_v0` objects (which will be in existing user code). - -The first attempt sought to change as little of the Beast code as possible, by writing `DynamicBuffer_v0` wrappers for -DynamicBuffer_v[1|2] objects, with those wrappers automatically created on demand in the initiating function of Beast IO -operations. The problem with this approach is that it penalised the use of DynamicBuffer_v2 with an additional memory -allocation (in order to manage a proxy of DynamicBuffer_v1's input and output regions). On reflection, it became -apparent that in the future, use of `DynamicBuffer_v2` will be the norm, so it would be inappropriate for Beast to -punish its use. - -Therefore, we have chosen to take the harder road of refactoring Beast to use `DynamicBuffer_v2` in all composed -operations involving dynamic buffers, and refactor the existing `DynamicBuffer_v0` types in order to allow them to -act as storage providers for `DynamicBuffer_v2` proxies while still retaining their `DynamicBuffer_v0` public -interfaces. - -I had hoped to get all this done during February, but alas - in terms of released code - I only got as far as the -refactoring of existing types. - -The code has been released into the [develop](https://github.com/boostorg/beast/tree/develop) branch as part of -[Version 286](https://github.com/boostorg/beast/commit/c8a726f962b2fbf77d00b273b3c6fb0dd975a6b5). - - -The refactor of HTTP operations and Websocket Streams in terms of `DynamicBuffer_v2` is underway and indeed mostly -complete, but there was not sufficient time to release a sufficiently robust, reviewed and tested version this month. - -I plan to finish off this work in the first part of March, which will hopefully leave time for more interesting -challenges ahead. - -# What's Next - -Well, the Beast [Issue Tracker](https://github.com/boostorg/beast/issues) is far from clear, so there is plenty of -work to do. - -At some point though, I'd like to make a start on a fully featured higher level HTTP client library built on -Boost.Beast. - -We'll have to see what unfolds. diff --git a/_posts/2020-03-06-KrystiansFebruaryUpdate.md b/_posts/2020-03-06-KrystiansFebruaryUpdate.md deleted file mode 100644 index bae87e619..000000000 --- a/_posts/2020-03-06-KrystiansFebruaryUpdate.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -layout: post -nav-class: dark -categories: krystian -title: Krystian's February Update -author-id: krystian ---- - - -# Introduction - - - -I'm a first-year university student at the University of Connecticut pursuing a degree in Computer Science. I joined The C++ Alliance near the end of January as a part-time Staff Engineer at the recommendation of Vinnie, who has been mentoring me for the past several months. I’ve been programming in C++ for around 2 years now, with an interest in library development and standardization. My original reasoning for choosing C++ over other languages was a little misguided, being that it was generally regarded as a difficult language, and I liked the challenge. Prior to this, I had dabbled in the language a little bit, but I really didn’t get into it until I discovered the existence of the standard. A language with explicitly defined semantics, all specified in a document that is difficult to parse sounded very appealing to me, and thus I embarked on my journey to learn everything about it that I could. If you were to ask me what I like most about it now, it would probably be a tie between the “you don’t pay for what you don’t use” principle and zero cost generics. - - - -With regard to standardization, I do a lot of standardese bug-fixing in the form of editorial issues and defect reports, and I write papers mostly focused on fixing the language. As for library development, I’m currently working on [Boost.JSON](https://github.com/vinniefalco/json) and [Boost.StaticString](https://github.com/boostorg/static_string), the latter of which has been accepted into Boost. - - - -# Boost.JSON - - - -My work on Boost.JSON originally started with writing a documentation style guide and then applying it to the library. The first place where this documentation was applied to was `json::string`; a non-template string type similar to `std::string`, but with a type erased allocator, used exclusively with `char`. - - - -The documentation of `json::string` proved to be no easy feat, due to its overwhelming number of overloaded member functions (`string::replace` itself has 12 overloads). Detailed documentation of these functions did prove to be a little tedious, and once I finished, it was clear that a better interface was needed. - - - -The standard interface for `std::string` is bound to a strict regimen of backward compatibility, and thus the interface suffers due to its lack of the ability to remove stale and unnecessary overloads. Take `replace` for instance: it has overloads that take a `string`, a type convertible to `string_view`, a pointer to a string, a pointer to a string and its size, a `string` plus parameters to obtain a substring and a `string_view` plus parameters to obtain a substring. All of these overloads can be replaced with a single overload accepting a `string_view` parameter, as it is cheap to copy, and can be constructed from all of the aforementioned types. Those pesky overloads taking parameters to perform a substring can also be done away with, as the substring operation can be performed at the call site. `json::string` includes a `subview` function which returns a `string_view` for cheap substring operations, so needless `json::string` constructions can be avoided. - - - -With the guidance of Peter Dimov, I drafted up a new `string_view` based interface, which dramatically reduced the number of overloads for each member function. Once the sign-off was given, it was a relatively trivial task to implement, as it was merely a matter of removing no longer needed overloads, and changing function templates that accepted objects convertible to `string_view` to be non-template functions with a `string_view` parameter. - - - - -# Boost.StaticString - - - -I was originally invited by Vinnie to work on [Boost.StaticString](https://github.com/boostorg/static_string) (then Boost.FixedString) back in October, and since then it has been accepted into Boost. It provides a fixed capacity, dynamically sized string that performs no dynamic allocations. It’s a fast alternative to `std::string` when either the capacity of the string is known or can be reasonably approximated at compile time. - - - -My primary objective for February was to implement the post-review changes requested by our review manager Joaquin Muñoz in time for the next Boost release. Most of the changes by this point had already been implemented, but still lacked polish, and the test coverage had to be improved. Additionally, I wanted to add a few optimizations and improve the `constexpr` support, the latter proving to be quite a daunting task. - - - -The nice thing about `static_string` is that the capacity of the string is part of the type, consequently allowing us to make all kinds of assumptions and to statically resolve what optimizations we want to perform at compile time. In particular, the usual checks are done in insert and replace that determine if the source of the operation (i.e. the string that will be copied into the string) lies within the string the operation is performed upon can be elided if we can somehow guarantee that they will never overlap. Since non-potentially overlapping objects of different types are guaranteed to occupy distinct addresses, we can safely skip this check for overloads accepting `static_string` parameters, if the capacity of the strings differ. In my opinion, it’s pretty neat. - - - -The aforementioned check that is performed in insert and replace also was the source of some serious headaches with respect to `constexpr` support across implementations. When you mix in the requirement to support C++11, it gets even worse. The primary issue that presents itself is that comparing pointers with the built-in relational operators yield unspecified results when the pointers do not point to elements of the same array; evaluating such an expression is forbidden during constant evaluation. The usual workaround to this is to use the library comparison operators (i.e. `std::less`, `std::greater` etc.), and in standardese land, it’s all good and well. However, on actual implementations, this won’t always work. For example, when using clang with libstdc++, [the implementation for the library comparison operators](https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/bits/stl_function.h#L443) casts the pointers to integer types and compares the resulting integers. If `__builtin_is_constant_evaluated` is available it is used and the check is instead done using the built-in relational operators, but this was only [implemented on clang quite recently](https://reviews.llvm.org/D55500) and therefore cannot be used for the most part. - - - -Figuring this out and finding a solution was quite a process, but eventually, I settled on a solution that gave the least unspecified results across implementations, provided good performance, and actually compiled. In essence, if `std::is_constant_evaluated` or a builtin equivalent is available, we can use a neat trick involving the equality operator for pointers. Equality comparison for pointers [almost never has unspecified results](http://eel.is/c++draft/expr.eq#3), so to check if the pointer falls within a range, we can iterate over every pointer value within that range and test for equality. This is only done during constant evaluation, so performance does not suffer. If the aforementioned functions cannot be used, or if the function is not evaluated within a constant evaluation, we use the built-in comparison operators for configurations where the library comparison operators don’t work in constant evaluation, and otherwise use the library comparison operators. Having a portable version of this check in the standard library wouldn’t be a terrible idea, so it may be something I will pursue in the future. - - - -Another feature that took a little bit of time to get working were the `to_static_string` overloads for floating-point types. `static_string` does not enjoy the luxury of being able to increase capacity, so sticking to only using standard notation isn’t possible since floating-point types can represent extremely large values. To get the best of both worlds, we first attempt to use standard form and then retry using scientific notation if that fails. To match `std::to_string`, the default precision of 6 is used for standard form - however, if we resort to scientific notation, we use the highest precision value possible based on the number of digits required to represent all the values of the type being converted, and the number of digits in its maximum mantissa value. As Peter Dimov noted several times, using the `%g` format specifier would be a preferable solution, so I may change this in the future. - - - -# Standardization - - - -The Prague meeting came and went (I did not attend), and unfortunately no progress was made on any of my papers. CWG was very busy finalizing C++20, so that left no time for a wording review of [P1839](http://wg21.link/p1839), and I’m holding off on my other papers. I’m planning to attend the New York meeting in November representing The C++ Alliance (my first meeting, I’m excited!), and is where I will be presenting [P1945](http://wg21.link/p1945), [P1997](http://wg21.link/p1997), and a few others that are currently in the works. Outside of proposals, it was business as usual; finding and fixing several editorial issues, and filing a few defect reports. Most of the work I do focuses on fixing the core language, and generally improving the consistency of the wording, so this month I worked on fixing incorrect conventions, removing redundant wording, and a small rewrite of [[dcl.meaning]](http://eel.is/c++draft/dcl.meaning) on the editorial side of things. As for defect reports, they consisted of some small wording issues that weren’t quite editorial but didn’t have a significant impact on the language. - - - -# Summary - - - -My experience working at the Alliance has been very positive thus far. Being a student, having flexible hours is fantastic, as I am able to adjust when I work based on my school workload. In the short amount of time I have spent working on Boost.JSON and Boost.StaticString, I have learned a lot, and continue to do so every day. Vinnie, Peter, and Glen always provide their invaluable feedback through reviews and answering questions, which is extremely helpful when working on projects of this scale with little experience. I consider the acceptance of Boost.StaticString into Boost to be my crowning achievement thus far, and I’m excited to see what kinds of cool projects I’ll be working on in the future. - - - -If you want to get in touch with me, you can message me on the [Cpplang slack](http://slack.cpp.al/), or [shoot me an email](mailto:sdkrystian@gmail.com). \ No newline at end of file diff --git a/_posts/2020-03-31-RichardsMarchUpdate.md b/_posts/2020-03-31-RichardsMarchUpdate.md deleted file mode 100644 index cfef33d1c..000000000 --- a/_posts/2020-03-31-RichardsMarchUpdate.md +++ /dev/null @@ -1,311 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's March Update -author-id: richard ---- - -# Coding in the time of a Pandemic - -It has been an interesting month, there having been the minor distraction of a lockdown of our -little country. The borders with Spain and France were closed about three weeks ago and -all residents have been asked to stay at home other than to buy groceries or walk their dogs. -Fortunately I have dogs so I at least have a legitimate reason to see the sun. - -One of the advantages of living in a tiny country is that the government has been able to -secure the supply of 150,000 COVID-19 testing kits, which represents two tests per resident. -They are also working on supplying every resident with masks for use when shopping. -I am hoping to report in my next blog that we are allowed outside subject to a negative -test and the wearing of a mask and gloves. - -Fortunately, until today, our internet has been uninterrupted. Communication with my friends -and colleagues at the C++ Alliance and the wider developer community has continued. - -# Boost Release - -The Boost 1.73 release is imminent. Thus much of my focus in the latter half of the month has -been on addressing any remaining issues in Beast that represent an easy win in terms of -demonstrating progress between releases. - -This brings to a close my first quarter as a maintainer of the Beast library. I would have -liked to have produced more in terms of feature development and architectural improvements, -but a few interesting things came up which delayed this; some of which I will share with you -here. - -# (Possibly) Interesting Asio Things - -To say that Boost.Beast has a strong dependency on Boost.Asio would be an understatement. It -should therefore come as no surprise that the Beast team spend a lot of time working with -Asio and (certainly in my case) a lot of time working to understand the internals. - -We had cause to reach out to Chris Kohlhoff, Asio's author, on two occasions in recent -times. If you read my February blog you would have seen the issues we have faced with the -`DynamicBuffer` concept. This month it was about the thread-safety of composed operations and -IO objects. - -But first, the result of a question I asked myself: - -## Is it possible to write an asynchronous composed operation entirely as a lambda? - -In short, if you're using c++14 or better, the answer is happily yes! - -Here is the smallest program I could think of: - -a: Implemented asynchronously - -b: Targeting a POSIX system (just because I happen to know more about POSIX than Windows) - -This program simply copies the contents of `stdin` to `stdout`: - -```cpp -int -main() -{ - asio::io_context ioc; - auto exec = ioc.get_executor(); - - auto in = asio::posix::stream_descriptor(exec, ::dup(STDIN_FILENO)); - auto out = asio::posix::stream_descriptor(exec, ::dup(STDOUT_FILENO)); - - async_copy_all(in, out, [](auto&& ec, auto total){ - std::cout << "\ntransferred " << total << " bytes\n"; - if (ec.failed()) - { - std::cerr << "transfer failure: " << ec.message() << std::endl; - std::exit(ec.value()); - } - }); - - ioc.run(); - - return 0; -} -``` - -People who are unused to writing composed operations (asynchronous operations that fit into -the ASIO ecosystem), or people who have written them longer ago than last year, might at -this stage feel their hearts sinking in anticipation of the complex horror show awaiting -them when writing the function `async_copy_all`. - -Fortunately, Asio's new(ish) `async_compose` template function makes this reasonably -painless: - -```cpp -template -auto -async_copy_all( - InStream &fd_in, - OutStream &fd_out, - CompletionToken &&completion) -{ - return asio::async_compose< - CompletionToken, - void(system::error_code const &,std::size_t)>( - [&fd_in, &fd_out, - coro = asio::coroutine(), - total = std::size_t(0), - store = std::make_unique(4096)] - (auto &self, - system::error_code ec = {}, - std::size_t bytes_transferred = 0) mutable - { - BOOST_ASIO_CORO_REENTER(coro) - for(;;) - { - BOOST_ASIO_CORO_YIELD - { - auto buf = asio::buffer(store.get(), 4096); - fd_in.async_read_some(buf, std::move(self)); - } - if (ec.failed() || bytes_transferred == 0) - { - if (ec == asio::error::eof) - ec.clear(); - return self.complete(ec, total); - } - - BOOST_ASIO_CORO_YIELD - { - auto buf = asio::buffer(store.get(), bytes_transferred); - fd_out.async_write_some(buf, std::move(self)); - } - total += bytes_transferred; - if (ec.failed()) - return self.complete(ec, total); - } - }, - completion, fd_in, fd_out); -} -``` - -There are a few things to note in the implementation. - -1. The first is that the entire asynchronous operation's implementation state is captured -in the capture block of the lambda (this is why we need c++14 or higher) -2. Secondly, the lambda is mutable. This is so we can update the state and then `move` it -into the completion handler of each internal asynchronous operation. -3. The second and third arguments of the lambda's function signature are defaulted. This is -because `async_compose` will cause the implementation (in this case, our lambda) to be called -once with no arguments (other than `self`) during initiation. -4. There is an explicit check for `eof` after the yielding call to `fd_in.async_read_some`. -In Asio, `eof` is one of a few error codes that represents an informational condition -rather than an actual error. Another is `connection_aborted`, which can occur during -an `accept` operation on a TCP socket. Failing to check for this error-that-is-not-an-error -can result in asio-based servers suddenly going quiet for 'no apparent reason'. -5. Notice that the un-named object created by `async_compose` intercepts every invocation on -it and transfers control to our lambda by prepending a reference to itself to the argument -list. The type of `Self` is actually a specialisation of an `asio::detail::composed_op<...>` -(as at Boost 1.72). However, since this class is in the detail namespace, this should never -be relied on in any program or library. -6. Note that I create the buffer object `buf` in separate statements to the initiations of -the async operations on the streams. This is because the `unique_ptr` called `store` is going -to be `move`d during the initiating function call. Remember that arguments to function calls -are evaluated in unknowable order in c++, so accessing `store` in the same statement in -which the entire completion handler has been `move`d would result in UB. -7. Finally, `async_compose` is passed both the input and output stream (in addition to their -references being captured in the lambda) so that both streams' associated executors can be -informed that there is outstanding work. It may be surprising to some that the input and -output streams may legally be associated with different executors. - -Actually, now that I write this, it occurs to me that it is unclear to me what is the -'associated executor' of the composed operation we just created. Asio's documentation is -silent on the subject. - -Inspecting the code while single-stepping through a debug build revealed that the executor is -taken from the first of the `io_objects_or_executors&&...` arguments to `async_compose` which -itself has an associated executor. If none of them do, then the `system_executor` is chosen as -the default executor (more on why this may cause surprises and headaches later). Note that as -always, wrapping the lambda in a call to `bind_executor` will force the composed operation's -intermediate invocations to happen on the bound executor. - -In our case, it is `fd_in` which will be providing the executor and as a result, every -invocation of our lambda (except the first) is guaranteed to be happen by being invoked -as if by `post(fd_in.get_executor(), (...))`. - -## `system_executor` and "What Could Possibly Go Wrong?" - -Once upon a time, when I first started using Asio, there were no `executor`s at all. In -fact, there were no `io_context`s either. There was an `io_service` object. At some point -(I don't remember the exact version of Asio, but it was at least five years ago) the -`io_service` was replace with `io_context`, an object which did basically the same job. - -More recently, the `io_context` represents the shared state of a model of the `Executor` -Named Type Requirement (aka Concept). The state of the art is moving towards passing copies -of `Executor`s rather than references to `io_context`s. - -Asio now contains a concrete type, the `executor` which is a type-erased wrapper which -may be assigned any any class which models an `Executor`. - -As you might expect, we are heading into a world where there might be more than one model -of `Executor`. In anticipation of this, by default, all Asio IO objects are now associated -with the polymorphic wrapper type `executor` rather than a `io_context::executor_type`. - -One such model of `Executor` supplied by Asio is the `system_executor`, which is actually -chosen as the default associated executor of any completion handler. That is, if you initiate -an asynchronous operation in Asio today, against a hypothetical io_object that does not have -an associated executor and you do not bind your handler to an executor of your own, then -your handler will be invoked as-if by `post(asio::system_executor(), )` - that is, -it will be called on some implementation-defined thread. - -Now that the basics are covered, back to _what could possibly go wrong_? - -Well imagine a hypothetical home-grown IO Object or _AsyncStream_. Older versions of the Asio -documentation used to include an example user IO Object, the logging socket. - -The basic premise of our logging socket is that it will do everything a socket will do, plus -log the sending and receiving of data, along with the error codes associated with each read -or write operation. - -Clearly the implementation of this object will contain an asio socket object and some kind of -logger. The internal state must be touched on every asynchronous operation initiation (to -actually initiate the underlying operation and record the event) *and* during every -completion handler invocation, in order to update the logger with the results of the -asynchronous operation. - -As we know, invocations of intermediate completion handlers happen on the executor associated -with the final completion handler provided by the user, so in our case, the actions will be -something like this: - -``` -on the initiating thread: - logging_socket::async_write_some - logging_socket::async_write_some_op::operator()() - logging_socket::impl::update_logger(...) - socket::async_write_some(...) - -... time passes... - -on a thread associated with the associated executor: - logging_socket::async_write_some_op::operator()(ec, bytes_transferred) - logging_socket::impl::update_logger() - user_completion_handler(ec, bytes_transferred) -``` - -The situation will be similar for a write operation. - -Now consider the following code (`ls` is an object of our hypothetical type `logging_socket`: - -```cpp - ls.async_write_some( - get_tx_buffer(), - net::bind_executor( - net::system_executor(), - [](auto ec, auto size){ - /* what happens here is not relevant */ - })); - ls.async_read_some( - get_rx_buffer(), - net::bind_executor( - net::system_executor(), - [](auto ec, auto size){ - /* what happens here is not relevant */ - })); -``` - -What have I done? Not much, simply initiated a read and a write at the same time - a -perfectly normal state of affairs for a socket. The interesting part is that I have -bound both asynchronous completion handlers to the `system_executor`. This means that -each of the handlers will be invoked (without synchronisation) on two arbitrary threads. - -Looking at our pseudo-code above, it becomes clear that there will be a race for the -`logging_socket`'s implementation: - -* Between the initiation of the read and the completion of the write, and -* between the completion of the read and the completion of the write - -Again the Asio documentation is silent on the correct method of mitigating this situation. -Two possible workarounds have occurred to me so far: - -1. Never use a `system_executor` unless first wrapping it in a `strand`. -2. Ensure that all composed operations of IO objects are thread-safe with respect to - mutation of the implementation. If this is made true, it almost inevitably follows that - the entire IO Object may as well be made thread-safe (which Asio IO Objects are not). - -I have reached out to Chris for final judgement and will update the blog (and possibly much -of Beast!) in response to a definitive answer. - -# Unified Web Client - -I have been given the go-ahead to make a start on exploring a unified web-client library -which will eventually become a candidate for inclusion into Boost. - -The obvious course of action, building directly on top of Beast is a no-go. If the -library is to be used on platforms such as tablets and phones, or appear in the various -app stores of vendors, there are restrictions on which implementations of communications -libraries may be used. To cut a long story short, vendors want to minimise the risk of -security vulnerabilities being introduced by people's home-grown communications and -encryption code. - -So my initial focus will be on establishing an object model that: - - * Provides a high degree of utility (make simple things simple). - * Emulates or captures the subtleties of vendor's Web Client frameworks. - * Efficiently slots into the Asio asynchronous completion model. - -Of course, linux and proprietary embedded systems do not have a mandated communications -libraries, so there will certainly be heavy use of Beast in the unconstrained platform- -specific code. - -More information as it becomes available. - diff --git a/_posts/2020-04-07-KrystiansMarchUpdate.md b/_posts/2020-04-07-KrystiansMarchUpdate.md deleted file mode 100644 index 0b663ac68..000000000 --- a/_posts/2020-04-07-KrystiansMarchUpdate.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -layout: post -nav-class: dark -categories: krystian -title: Krystian's March Update -author-id: krystian ---- - - -# The Rundown - -Due to the COVID-19 pandemic, my classes have been moved online. It's certainly an interesting way to teach, but admittedly I can't say it's enjoyable or effective. However, it has given me a lot of time to work on various projects, which is a reasonable trade-off (at least in my opinion). I got quite a bit done this month due to the substantial increase in leisure time, and was able to work on several projects that previously didn't fit into my schedule. - -# Boost.StaticString - -I spent the first few days of March putting the finishing touches on Boost.StaticString in preparation for the release of Boost 1.73.0, mostly consisting of housekeeping tasks, but also some bug fixes for certain compiler configurations. In particular, a problem arose with GCC 5 regarding its `constexpr` support, two of which impede using `basic_static_string` during constant evaluation: `throw` expressions, and non-static member functions whose return type is the class they are a member of. With respect to the former, consider the following: - -```cpp -constexpr int throw_not_evaluated(bool flag) -{ - if (flag) - throw 1; - return 0; -} - -constexpr int const_eval = throw_not_evaluated(false); -``` -[View this on Godbolt](https://godbolt.org/z/CEuEvr) - -It is helpful to first establish what the standard has to say regarding the above example. Looking at [[dcl.constexpr] p3](https://timsong-cpp.github.io/cppwp/n4140/dcl.constexpr#3), we see that `throw_not_evaluated` contains no constructs that are explicitly prohibited from appearing within a `constexpr` function in all contexts. Now taking a look at [[expr.const] p2](https://timsong-cpp.github.io/cppwp/n4140/expr.const#2) we see: -> A *conditional-expression* `e` is a core constant expression unless the evaluation of `e`, following the rules of the abstract machine, would evaluate one of the following expressions: -> - [...] a *throw-expression* - -Boiling down the standardese, this effectively says that `throw_not_evaluated(false)` is a constant expression unless, when evaluated, it would evaluate`throw 1`. This would not occur, meaning that `throw_not_evaluated(false)` is indeed a constant expression, and we can use it to initialize `const_eval`. Clang, later versions of GCC, and MSVC all agree on this and compile it without any complaints. However, GCC 5 with the `-std=c++14` flag fails to do so, citing: -> error: expression '<**throw-expression**>' is not a constant-expression - -Sounds excusable to me! GCC 5.1 was released in 2015 after all, so you can't expect every feature to be implemented less than a year after C++14 was finalized, right? It all sounded sane to me, but before going ahead and disabling `constexpr` for functions that were potentially-throwing, I decided to try a small variation of the original: -```cpp -struct magic -{ - constexpr magic(bool flag) - { - if (flag) - throw 1; - return; - } - - constexpr operator int() { return 1; } -}; - -constexpr int magic_eval = magic(false); -``` -[View this on Godbolt](https://godbolt.org/z/fXQxDT) - -*What?* - -It miraculously works. Further, if we construct a `magic` object within a constexpr function: -```cpp -constexpr int throw_not_evaluated(bool flag) -{ - return magic(flag); -} -``` -[View this on Godbolt](https://godbolt.org/z/qMAkiQ) - -***What?*** - -Gathering the remnants of my sanity, I lifted the `BOOST_STATIC_STRING_THROW_IF` macros out of all the potentially-throwing functions and replaced them with a class template (for GCC 5) or function template (for all other compilers) that can be constructed/called with the `throw_exception(message)` syntax. I also considered adding a `throw_exception_if` variation to hide the `if` statement within shorter functions, but on advisement from Vinnie Falco and Peter Dimov, I didn't end up doing this to allow for better optimization. - -Moving on to a simpler GCC 5 issue (but significantly more annoying to diagnose), I discovered that `substr` was causes an ICE during constant evaluation. Took some time to get to the bottom of it, but I eventually figured out that this was because the return type of `substr` is `basic_static_string`. Unfortunately, the only remedy for this was to disable `constexpr` for the function when GCC 5 is used. - -Save for these two issues, the rest of the work that had to be done for StaticString was smooth sailing, mostly improvements to the coverage of the `insert` and `replace` overloads that take input iterators. In the future I plan to do an overhaul of the documentation, but as of now it's ready for release, so I'm excited to finally get this project out into the wild. - -# Boost.JSON - -Most of the time I spent on Boost.JSON in the past month was spent learning the interface and internals of the library, as I will be writing documentation on many of the components in the future. My primary focus was on the `value_ref` class, which is used as the value type of initializer lists used to represent JSON documents within source code. The reason that this is used instead of `value` is because `std::initializer_list` suffers from one fatal flaw: the underlying array that it refers to is `const`, which means you cannot move its elements. Copying a large JSON document is not trivial, so `value_ref` is used as a proxy referring to an underlying object that can be moved by an operation using an initializer list. This is achieved by storing a pointer to a function that will appropriately copy/move construct a `value` when called requested by the target. - -While looking at how `value_ref` works, I went ahead and added support for moving from a `json::string`, since up to that point all strings were stored as `string_view` internally, thus precluding the ability to move from a `value_ref` constructed from a `json::string`. There also was a bug caused by how `value_ref` handles construction from rvalues, in part due to the unintuitive nature of type deduction. Consider the following: -```cpp -struct value_ref -{ - template - value_ref(const T&) { ... } - - template - value_ref(T&&) { ... } -}; - -json::value jv; -const json::value const_jv; -value_ref rvalue = std::move(jv); // #1 -value_ref const_rvalue = std::move(const_jv); // #2 -``` -In both `#1` and `#2`, the constructor `value_ref(T&&)` is called. This certainly makes sense once you consider the deduction that is performed, however, by glancing at just the declarations themselves, it isn't obvious, as we've all been taught that references to non-const types will not bind to a const object. Where this becomes a problem for `value_ref` is that the constructor taking a `T&&` parameter expects to be able to move from the parameter, so it internally stores a non-const `void*`. Converting a "pointer to `const` `T`" to `void*` isn't permitted, so you get a hard error. The fix for this was fairly trivial. - -# Standardization - -Most of my time this month was spent working on standardization related activities, which can be broken up into two somewhat separate categories: -- Fixing existing wording -- Working on papers - -## Editorial issues - -I submitted a fair few pull requests to the [draft repository](https://github.com/cplusplus/draft), most of which were general wording cleanups and improvements to the consistency of the wording. With respect to wording consistency, I targeted instances of incorrect cv-qualification notation, definitions of terms within notes, cross-references that name the wrong subclause or are self-referential, and redundant normative wording. These kinds of issues are all over the standard, but I generally stay away from the library wording unless it's absolutely necessary since it has a subtitle difference in the wording style compared to that of the core language. I ended up writing atool to make grepping for these issues a little easier, which is certainly an improvement over manually inspecting each TeX source. - -The wording cleanups are the most time consuming, but also are the ones I find the most enjoyable. They all follow the same principle of rephrasing an idea with more accurate wording while not changing the actual meaning -- something that often proves to be a challenge. These generally start off as small issues I notice in the text, but then snowball into complete rewrites to make the whole thing consistent. Anyways, if it sparks your interest you can find the various editorial fixes I worked on [here](https://github.com/cplusplus/draft/pulls?q=is%3Apr+author%3Asdkrystian). - -## P1997 - -Of my papers, [P1997](http://wg21.link/p1997) is the one that I'm putting the most time into. In short, it proposes to make arrays more "regular", allowing assignment, initialization from other arrays, array placeholder types, and many more features we bestow upon scalar and class types. The holy grail of changes (not proposed in the paper) would be to allow the passing of arrays by value *without* decaying to a pointer: fully cementing arrays as first-class types. Unlike the other proposed changes this isn't a pure extension, so to make it remotely feasible existing declarations of parameters with array type (for the sake of brevity we will abbreviate them as PAT) would have to be deprecated in C++23, removed in C++26, and then reinstated in C++29. For this reason, we are undertaking this as an entirely different paper to allow for the pure extensions to be added in C++23, leaving the removal and reinstatement on the backburner. - -In order to bring convincing evidence that: - - 1. The current semantics for PAT are unintuitive and merely syntactic sugar. - 2. The deprecation of PAT would not be significantly disruptive to existing codebases. - -Ted (the co-author) and I decided to see exactly how often PAT appear within normal C++ code. While [codesearch.isocpp.org](https://codesearch.isocpp.org/) by Andrew Tomazos is a fantastic tool, the search we wanted to conduct was simply not possible with his tool, so we set out to create our own. We needed two things: - - 1. A dataset - 2. Some tool to parse all the source files - -For the dataset, I wrote a tool to clone the top 4000 C++ Github repositories, and clean out all files that weren't C++ source files (.cpp, .cxx, .cc, .h, .hpp, .hxx, .ipp, .tpp). Github has a nice API to search for repositories, but it unfortunately limits the results for a single search query to 1000 results, but since I was sorting them by start count, I was able to use it as an index to begin a new search query. After accidentally sending my Github credentials to Ted once and waiting 10 hours for all the repositories to be cloned, we had our dataset: 2.7 million source files, totaling around 32GB. - -To parse all these files, we opted to use the Clang frontend. Its AST matcher was perfectly suited to the task, so it was just a matter of forming the correct matchers for the three contexts a PAT can appear in (function parameter, non-type template parameter, and `catch` clause parameter). All the files were parsed in single file mode, since opening and expanding `#include` directives would make the processing of the files take exponentially longer. Forming the correct match pattern proved to be the most difficult part, as the syntax is not entirely intuitive and often times they simply didn't find every PAT in our test cases. - -Doing a single file parse along with wanting to find every PAT possible *and* suppressing all diagnostics landed us in the land of gotchas. To start, the `arrayType` matcher only would match declarations that had an array declarator present, i.e. `int a[]` would be found but `T a` where `T` names an array type would not. Eventually I found `decayedType`, which did exactly what we wanted, so long as we filtered out every result that was a function pointer. This worked for function parameters and non-type template parameters, but not `catch` clause parameters. In the Clang AST, `catch` is categorized as a statement that encloses a variable declaration whose type is not considered to be decayed (as far as I could see) so we could only match parameters that used an array declarator. I don't expect anyone to actually declare PATs in a `catch` clause, and after running the tool on the dataset exactly zero instances were found, so this is most likely a non-issue. - -Single file parsing introduced a number of issues all stemming from the fact that none of the `#include` directives were processed, meaning that there was a large number of types that were unresolved. Consider the following: -```cpp -#include - -using array = unresolved[2]; -using array_ref = unk_array&; -array_ref ref = {unresolved(), unresolved()}; - -void f(decltype(ref) param); -``` -For reasons that I don't know, since `unresolved` has no visible declaration Clang reports that `param` has a decayed type. I suspect this is because diagnostics are suppressed and some recursive procedure used in the determination of the type named by `array_ref` returns upon encountering an error at the declaration of `array` and simply returns `unresolved[2]` as the type. If you know why this happens, don't hesitate to ping me! I ended up tracking the number of PAT declarations that use an array declarator separately since I suspect that this number may end up being more accurate. - -Once the tool was ready to go and we started to run it on the dataset, we encountered issues of the worst kind: assertion failures. I suppose such errors could be expected when abusing a compiler to the extent that we did, but they weren't particularly enjoyable to fix. I should mention that tool itself is meant to be run on a directory, so once an assertion failed, it would end the entire run on that directory. My initial solution to this was changing the problematic asserts to throw an exception, but the number of failures was ever-growing. Creating a new `CompilerInstance` for each file did somewhat remedy the situation, but didn't fix it all. Eventually, we called it good enough and let it run over the entire dataset. Clang itself was in our dataset, with a nasty little surprise taking the form of infinitely recursive template instantiations and debug pragmas that would crash the compiler. Clang relies on the diagnostics to signal when the recursive instantiation limit is reached, but since those were disabled the thread would never terminate. Evil. - -Once the paper is finished, I'll report the results in a future post. - -## Implicit destruction - -This paper intends to substantially overhaul the wording that describes the interaction between objects and their storage. I originally brought this issue up several months back with [this pull request](https://github.com/cplusplus/draft/pull/2872) in an attempt to fix it editorially, but it was deemed too large in scope. I finally got started on the paper and drafted up a direction to take, which will hopefully resolve the shortfalls of our current wording. - -This problem stems from the notion that objects don't exist before their lifetime has started and after it has ended. This allows compilers to make a lot of assumptions and consequently leads to more optimized code, but the manner in which the wording was applied has severely crippled our ability to refer to "not-objects". We want to be able to place restrictions on storage that an object used to occupy, but simply have no way for doing so. Thus, the direction I plan to take is to define the semantics of storage, allowing us to place restrictions on that storage even if no object exists within it. I don't have too many of the core definitions completed yet as they require the most time to make them robust, but once that is hashed out, applying it where needed should be smooth sailing. - -Here is a short list of the main changes: -- Define what a *region of storage* is, specify when it is acquired, released, and what kinds of objects may occupy it. -- Remove the storage duration property from objects, and effectively make that agnostic of the storage they occupy. Instead, associate storage duration with a variable. Dynamic storage duration can removed since such storage isn't associated with a variable. -- Specify when *reuse* of storage occurs, and its effects upon the objects within that storage. -- Properly specify when pointers and expressions refer to storage while preserving the notion that they refer to objects (or functions). -- Specify that when control passes through the definition of a variable, storage is acquired, objects are created, and initialization is performed. Likewise, specify that exit from a scope, program, or thread causes objects within the storage to be destroyed (if any), and the storage to be released. - -It's a big undertaking, but I'm excited to work on this and see what kind of feedback I get. However, this paper will be more of a long term project, since it will be touching the majority of the wording in the standard. I'll provide updates in future posts. - -## Placement new during constant evaluation - -A paper that I've been thinking about working on and finally got around to revolves around a new (heh) C++20 feature: `new` expressions are now able to be evaluated during constant evaluation (CE). While the storage they acquire must be released during the CE and it may only call the replaceable global allocation functions, it finally allows for the use of dynamically sized containers within a constant expression. - -This is great! However, there is a restriction here that is completely avoidable. The function `std::construct_at` was introduced to allow for objects to be constructed as if by placement `new` -- nice, but we don't allow placement `new` to be used by itself. This is because a certain implementation can't resolve what object a `void*` points to during CE (thank you Tim Song for the info); and because CE is intended to always yield the same results on all implementations, `construct_at` is used to ensure the pointer type passed is always a pointer to object type. I think that *at the very least*, congruent placement `new` expressions should be allowed by the principle of this being unnecessarily restrictive. As with all the other papers, I'll post progress updates in a future post. I've drafted up some wording, and I plan to have this ready sometime around June. - -# Information -If you want to get in touch with me, you can message me on the [Cpplang slack](http://slack.cpp.al/), or [shoot me an email](mailto:sdkrystian@gmail.com). diff --git a/_posts/2020-04-28-New-Boost-Release.md b/_posts/2020-04-28-New-Boost-Release.md deleted file mode 100644 index 1aed4b54f..000000000 --- a/_posts/2020-04-28-New-Boost-Release.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis -title: New Boost Release ---- - -# Boost 1.73 - -## New Library -StaticString: -A dynamically resizable string of characters with compile-time fixed capacity and contiguous embedded storage, from Krystian Stasiowski and Vinnie Falco. - -Boost Release Notes diff --git a/_posts/2020-04-30-RichardsAprilUpdate.md b/_posts/2020-04-30-RichardsAprilUpdate.md deleted file mode 100644 index bfaf46412..000000000 --- a/_posts/2020-04-30-RichardsAprilUpdate.md +++ /dev/null @@ -1,454 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's April Update -author-id: richard ---- - -# Boost 1.73 Released and other Matters - -The 1.73.0 release of Boost took up more attention than I had anticipated, but in the end all seemed to go well. - -Since then I've been working through the issues list on GitHub and am now starting to make some headway. - -I cam across a few other interesting (to me) topics this month. - -# (Possibly) Interesting Asio Things - -Last month I asked the question, "Is it possible to write an asynchronous composed operation entirely as a lambda?". - -This month I went a little further with two items that interested me. - -The first is whether asio's `async_compose` can be adapted so that we can implement a complex composed operation involving -more than one IO object easily using the asio faux `coroutine` mechanism. - -The second was whether is was possible to easily implement an async future in Asio. - -## Async Asio Future - -Here is my motivating use case: - -```cpp - auto p = async::promise(); - auto f = p.get_future(); - - // long-running process starts which will yield a string - start_something(std::move(p)); - - // wait on the future - f.async_wait([](some_result_type x) { - // use the x - }); - - // or - auto str = co_await f.async_wait(net::use_awaitable); - - // or shorthand - auto str = co_await f(); - -``` - -The salient points here are: -* no matter on which thread the promise is fulfilled, the future will complete on the associated executor of the handler - passed to `async_wait` -* Ideally the promise/future should not make use of mutexes un-necessarily. -* (problematic for ASIO) It must work with objects that are not default-constructable. - -In the end, I didn't achieve the second goal as this was not a priority project, but I would be interested to see -if anyone can improve on the design. - -The source code is [here](https://github.com/madmongo1/webclient/blob/develop/include/boost/webclient/async/future.hpp) - -I tried a couple of ways around the non-default-constructable requirement. My first was to require the CompletionToken -to the async_wait initiating function to be compatible with: - -```cpp -void (error_code, std::optional) -``` - -But I felt this was unwieldy. - -Then I remembered Boost.Outcome. I have been looking for a use for this library for some time. -It turns out that you can legally write an ASIO composed operation who's handler takes a single -argument of any type, and this will translate cleanly when used with `net::use_future`, `net::use_awaitable` etc. - -A default Boost.Outcome object almost fits the bill, except that its exception_ptr type is boost rather than standard. - -This is easily solved with a typedef: -```cpp -template using myoutcome = boost::outcome2::basic_outcome; -``` - -I was feeling please with myself for figuring this out, until I came to test code code under C++11... and realised -that Boost.Outcome is only compatible with C++14 or higher. - -So in the end, I cobbled together a 'good enough' version of outcome using a variant: - -```cpp -template < class T > -struct outcome -{ - outcome(T arg) : var_(std::move(arg)) {} - outcome(error_code const& arg) : var_(arg) {} - outcome(std::exception_ptr const& arg) : var_(arg) {} - - auto has_value() const -> bool { return polyfill::holds_alternative< T >(var_); } - auto has_error() const -> bool { return polyfill::holds_alternative< error_code >(var_); } - auto has_exception() const -> bool { return polyfill::holds_alternative< std::exception_ptr >(var_); } - - auto value() & -> T &; - auto value() && -> T &&; - auto value() const & -> T const &; - - auto error() const -> error_code const &; - - using variant_type = polyfill::variant< T, error_code, std::exception_ptr >; - variant_type var_; -}; -``` - -The code for this is [here](https://github.com/madmongo1/webclient/blob/develop/include/boost/webclient/polyfill/outcome.hpp) - -Finally this allowed me to express intent at the call site like so: - -```cpp - auto f = p.get_future(); - - f.async_wait([](outcome os){ - if (os.has_value()) - // use the value - else if (os.has_error()) - // use the error - else - // deal with the exception - }); -``` - -The coroutine interface can be made cleaner: - -```cpp - try { - auto str = co_await f(); - // use the string - } - catch(system_error& ec) { - // use the error code in ec.code() - } - catch(...) { - // probably catastrophic - } -``` - -For the above code to compile we'd have to add the following trivial transform: - -```cpp - template < class T > - auto future< T >::operator()() -> net::awaitable< T > - { - auto r = co_await async_wait(net::use_awaitable); - if (r.has_value()) - co_return std::move(r).assume_value(); - else if (r.has_error()) - throw system_error(r.assume_error()); - else - throw r.exception(); - } -``` - - -## Easy Complex Coroutines with async_compose - -When your composed operation's intermediate completion handlers are invoked, -the underlying `detail::composed_op` provides a mutable reference to itself. A typical completion handler looks like -this: - -```cpp - template - void operator()(Self& self, error_code ec = {} , std::size_t bytes_transferred = 0) - { - reenter(this) { - // yields and operations on Self - yield async_write(sock, buf, std::move(self)); // note that self is moved - } - } -``` - -What I wanted was a composed operation where the following is legal: - -```cpp - template - void operator()(Self self /* note copy */, error_code ec = {} , std::size_t bytes_transferred = 0) - { - reenter(this) { - // yields and operations on Self - yield - { - async_write(sock, buf, self); - timer.async_wait(self); - writing = true; - sending = true; - } - - while(writing || sending) - yield - // something needs to happen here to reset the flags and handle errors and cancellation. - ; - } - } -``` - -Which I think looks reasonably clear and easy to follow. - -In this work I had to overcome two problems - writing the framework to allow it, and thinking of a maintainable way to -express intent in the interrelationships between the asynchronous operations on the timer and the socket. - -Solving the copyable composed_op problem was easy. I did what I always do in situations like this. I cheated. - -`asio::async_compose` produces a specialisation of a `detail::composed_op<>` template. Substituting a disregard of the -rules for knowledge and skill, I simply reached into the guts of asio and produced a copyable wrapper to this class. -I also cut/pasted some ancillary free functions in order to make asio work nicely with my new class: - -Here's the code... it's not pretty: - -```cpp -template < class Impl, class Work, class Handler, class Signature > -struct shared_composed_op -{ - using composed_op_type = boost::asio::detail::composed_op< Impl, Work, Handler, Signature >; - - using allocator_type = typename net::associated_allocator< composed_op_type >::type; - using executor_type = typename net::associated_executor< composed_op_type >::type; - - shared_composed_op(composed_op_type &&op) - : impl_(std::make_shared< composed_op_type >(std::move(op))) - { - } - - shared_composed_op(std::shared_ptr< composed_op_type > op) - : impl_(std::move(op)) - { - } - - void initial_resume() { impl_->impl_(*this); } - - template < class... Args > - void operator()(Args &&... args) - { - if (impl_->invocations_ < ~unsigned(0)) - { - ++impl_->invocations_; - impl_->impl_(*this, std::forward< Args >(args)...); - } - } - - template < class... Args > - void complete(Args &&... args) - { - impl_->complete(std::forward< Args >(args)...); - } - - auto get_allocator() const -> allocator_type { return impl_->get_allocator(); } - auto get_executor() const -> executor_type { return impl_->get_executor(); } - - std::shared_ptr< composed_op_type > impl_; -}; - -template < class Impl, class Work, class Handler, class Signature > -auto share(boost::asio::detail::composed_op< Impl, Work, Handler, Signature > &composed_op) - -> shared_composed_op< Impl, Work, Handler, Signature > -{ - auto op = shared_composed_op< Impl, Work, Handler, Signature >(std::move(composed_op)); - op.initial_resume(); - return op; -} - -template < class Impl, class Work, class Handler, class Signature > -auto share(shared_composed_op< Impl, Work, Handler, Signature > shared_thing) - -> shared_composed_op< Impl, Work, Handler, Signature > -{ - return shared_thing; -} - -template < typename Impl, typename Work, typename Handler, typename Signature > -inline void *asio_handler_allocate(std::size_t size, shared_composed_op< Impl, Work, Handler, Signature > *this_handler) -{ - return boost_asio_handler_alloc_helpers::allocate(size, this_handler->impl_->handler_); -} - -template < typename Impl, typename Work, typename Handler, typename Signature > -inline void asio_handler_deallocate(void * pointer, - std::size_t size, - shared_composed_op< Impl, Work, Handler, Signature > *this_handler) -{ - boost_asio_handler_alloc_helpers::deallocate(pointer, size, this_handler->impl_->handler_); -} - -template < typename Impl, typename Work, typename Handler, typename Signature > -inline bool asio_handler_is_continuation(shared_composed_op< Impl, Work, Handler, Signature > *this_handler) -{ - return asio_handler_is_continuation(this_handler->impl_.get()); -} - -template < typename Function, typename Impl, typename Work, typename Handler, typename Signature > -inline void asio_handler_invoke(Function &function, shared_composed_op< Impl, Work, Handler, Signature > *this_handler) -{ - boost_asio_handler_invoke_helpers::invoke(function, this_handler->impl_->handler_); -} - -template < typename Function, typename Impl, typename Work, typename Handler, typename Signature > -inline void asio_handler_invoke(const Function & function, - shared_composed_op< Impl, Work, Handler, Signature > *this_handler) -{ - boost_asio_handler_invoke_helpers::invoke(function, this_handler->impl_->handler_); -} - -``` - -With that in hand, and with a little more _jiggery pokery_, I was able to express intent thus: - -```cpp - template < class Self > - void operator()(Self &self, error_code ec = {}, std::size_t bytes_transferred = 0) - { -... - auto &state = *state_; - - reenter(this) - { - ... - - // here's the interesting bit - self becomes a copyable handle to itself - yield share(self); - - // deduce the port - yield - { - this->initiate_resolve(share(self), state.uri.hostname(), deduce_http_service(state.uri)); - this->initiate_timout(share(self), state.session_.resolve_timeout()); - } - - while (this->resolving() || this->timeout_outstanding()) - yield; - - if (this->error) - goto finish; - - // connect the socket - - state.current_resolve_result = this->resolved_endpoints().begin(); - while (state.current_resolve_result != this->resolved_endpoints().end()) - { - state.tcp_stream().expires_after(state.session_.connect_timeout()); - yield state.tcp_stream().async_connect(state.current_resolve_result->endpoint(), share(self)); - log("Connect to: ", state.current_resolve_result->endpoint(), " result: ", ec); - // if the connect is successful, we can exit the loop early. - if (!ec) - goto connected; - ++state.current_resolve_result; - } - // if we leave the loop, make sure there is an error of some kind - this->set_error(ec); - goto finish; - - connected: - - ... -``` - -The full code can be seen [here](https://github.com/madmongo1/webclient/blob/develop/include/boost/webclient/asio/get_op.hpp) - -There are a couple of interesting things to note: - -If you start two or more async operations that will complete on the same object, they must all be allowed to complete. -This is why we yield and wait for both the socket and the timeout: - -```cpp - while (this->resolving() || this->timeout_outstanding()) - yield; -``` - -This leads directly to the problem of managing the error_code. Two error_codes will be produced - one for the timer -(which we hope to cancel before it times out) and one for the resolve operation. -This means we have to store the first relevant error code somewhere: - -```cpp -/// @brief a mixin to manage overall operation error state -struct has_error_code -{ - auto set_error(error_code const &ec) -> error_code & - { - if (!error) - { - if (ec && ec != net::error::operation_aborted) - error = ec; - } - return error; - } - - error_code error; -}; -``` - -And we need a means of allowing communication between the timeout timer and the resolver: - -```cpp - template < class Self > - void initiate_resolve(Self self, std::string const &host, std::string const &service) - { - results_.reset(); - resolver_.async_resolve(host, service, std::move(self)); - } - - template < class Self > - void operator()(Self &self, error_code ec, resolver_type::results_type results) - { - results_.emplace(std::move(results)); - - auto &this_ = *static_cast< Derived * >(this); - this_.on_resolved(ec); - - auto &has_err = static_cast< has_error_code & >(this_); - this_(self, has_err.set_error(ec)); - } - -``` - -One cancels the other.... - -```cpp - void on_timeout() - { - this->cancel_resolver(); - log("Timeout"); - } - - void on_resolved(error_code const &ec) - { - this->cancel_timeout(); - log("Resolve complete: ", ec); - } -``` - -```cpp - auto resolving() const -> bool { return !results_.has_value(); } - - auto cancel_resolver() -> void { resolver_.cancel(); } -``` - -In the end I was unsure how much is gained, other than pretty code (which does have value in itself). - -# Unified WebClient - -Exploratory work started on the unified web client. After some discussion, Vinnie and I agreed on the following design -decisions: - -* Interface to model closely the very popular Python Requests module. -* Sync and Async modes available. -* Homogenous (mostly non-template) interface, behind which system-specific implementations can reside. -* Where native library support is available, that will be used, -* Where not, internally the library will be implemented in Asio/Beast. -* Coroutine friendly. - -Once more progress has been made on the Boost.Beast issue tracker, I will be focusing attention here. - diff --git a/_posts/2020-05-08-KrystiansAprilUpdate.md b/_posts/2020-05-08-KrystiansAprilUpdate.md deleted file mode 100644 index 3592e0020..000000000 --- a/_posts/2020-05-08-KrystiansAprilUpdate.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -layout: post -nav-class: dark -categories: krystian -title: Krystian's April Update -author-id: krystian ---- - -# Overview - -Boost 1.73.0 has been released! Save for some minor documentation issues, Boost.StaticString enjoyed a bug-free release, so most of this month was spent working on Boost.JSON getting it ready for review. Unfortunately, I could not spend too much time working due to school and final exams, but now that those have passed I'll be able to put in significantly more time working on projects such as Boost.JSON. - -# Boost.JSON - -A good portion of my work on Boost.JSON was spent updating the documentation to reflect the replacement of the `storage` allocator model with `boost::container::pmr::memory_resource` (or `std::pmr::memory_resource` in standalone). The old model wasn't necessarily bad, but using `memory_resource` permits the use of existing allocators found in Boost.Container/the standard library, eliminating the need for writing proprietary allocators that only work with Boost.JSON. - -Even though `storage` will be going away, `storage_ptr` will remain to support shared ownership of a `memory_resource` -- something that `polymorphic_allocator` lacks. As with `polymorphic_allocator`, `storage_ptr` will still support non-owning reference semantics in contexts where the lifetime of a `memory_resource` is bound to a scope, giving users more flexibility. - -I also worked on `monotonic_resource`, the `memory_resource` counterpart to `pool`. This allocator has one goal: to be *fast*. I ended up adding the following features to facilitate this (mostly from `monotonic_buffer_resource`): - -- Construction from an initial buffer, -- The ability to reset the allocator without releasing memory, and -- The ability to set a limit on the number of bytes that can be dynamically allocated. - -The implementations of these features are pretty trivial, but they provide significant opportunities to cut down on dynamic allocations. For example, when parsing a large number of JSON documents, a single `monotonic_resource` can be used and reset in between the parsing of each document without releasing any dynamically allocated storage. While care should be taken to destroy objects that occupy the storage before the allocator is reset, this can substantially reduce the number of allocations required and thus result in non-trivial performance gains. - -The other major thing I worked on was fixing an overload resolution bug on clang-cl involving `json::value`. This was originally brought to my attention by Vinnie when the CI build for clang-cl started reporting that overload resolution for `value({false, 1, "2"})` was ambiguous. After a few hours of investigating, I found that `false` was being treated as a null pointer constant -- something that was certainly annoying, but it also didn't fully explain why this error was happening. - -After this unfortunate discovery, I tried again with `value({0, 1, "2"})`, this time on clang, and it turns out this was a problem here as well. After *many* hours of testing, I found that the constructor in `storage_ptr` taking a parameter of type `memory_resource` had a small problem: its constraint was missing `::type` after the `enable_if`, allowing `storage_ptr` to be constructed from any pointer type, including `const char*`. This somewhat helped to alleviate the problem, but `value({false, false, false})` was still failing. After many more hours of groking the standard and trying to reproduce the error, I finally came upon the following `json::string` constructors: - -``` -string(string const& other, std::size_t pos, std::size_t count = npos, storage_ptr sp = {}) - -string(string_view other, std::size_t pos, std::size_t count = npos, storage_ptr sp = {}) -``` - -See the problem here? Since the first parameter of both constructors can be constructed from null pointer constants, overload resolution for `string(0, 0, 0)` would be ambiguous. However, this isn't the full story. Consider the following constructors for `value`: - -``` -value(std::initializer_list init) - -value(string str) -``` - -For the initialization of `value({0, 0, 0})` the implicit conversion sequence to `str` would be ambiguous, but the one to `value_ref` can be formed. There is a special rule for overload resolution (separate from two-stage overload resolution during list-initialization) that considers any list-initialization sequence that converts to `std::initializer_list` to be a better conversion sequence than one that does not, with the exception to this rule being that it only applies when the two conversion sequences are otherwise identical. - -This rule *should* apply here, however, I found that clang has a small bug that prevents this rule from going into effect if any of the candidates have an ambiguous conversion sequence for the same parameter. We solve this pretty trivially by removing some of the redundant constructor overloads in `json::string` and all was well. It was a fun little puzzle to solve (the explanation was a bit of an oversimplification; if you have questions please let me know). - -If you want to get in touch with me, you can message me on the [Cpplang slack](http://slack.cpp.al/), or [shoot me an email](mailto:sdkrystian@gmail.com). \ No newline at end of file diff --git a/_posts/2020-06-04-WebsitePreviews.md b/_posts/2020-06-04-WebsitePreviews.md deleted file mode 100644 index 076c92bd8..000000000 --- a/_posts/2020-06-04-WebsitePreviews.md +++ /dev/null @@ -1,334 +0,0 @@ ---- -layout: post -nav-class: dark -categories: sam -title: Automated Documentation Previews -author-id: sam ---- -# Overview - -Greetings, and welcome to my first blog post at The C++ Alliance. - -I've recently begun working on an interesting project for the Alliance which might also have more widespread applicability. The same requirement could possibly apply to your organization as well. - -Consider an open-source project that has multiple contributors who are submitting changes via pull-requests in Github. You'd like to have assurances that a pull-request passes all tests before being merged. That is done with continuous integration solutions such as Travis or Circle-CI, which are quite popular and well-known. Similarly, if the submission is *documentation*, you would like to be able to view the formatted output in it's final published format so you can review the layout, the colors, and so on. What would be the best way to build and publish documentation from pull requests? - -Perhaps the first thought would be to include the functionality in Travis or Circle-CI. And that is certainly possible. However, in some cases there may be sensitive passwords, ssh keys, or other tokens in the configuration. Is it safe to allow random pull requests, from conceivably anyone on the whole internet, to trigger a Circle-CI build that contains authentication information? Let's explore that question, and then present a possible alternative that should be more secure. - -# Security - -In Circle-CI, you can choose to enable or disable jobs for Pull Requests. It's clearly safer to leave them disabled, but if the goal is to run automatic tests, this feature must be turned on. Next, you may choose to enable or disable access to sensitive keys for Pull Requests. This sounds like a great feature that will allow the jobs to be run safely. You could build Pull Requests with limited authorization. But what if you'd like to include secret keys in the build, that are needed to publish the documentation to an external server which is going to host the resulting content. After building the docs, they must be transferred to wherever they will be hosted. That means you must either include the secret keys in plain text, or toggle the setting to enable sensitive keys in Circle-CI. - -Let's briefly think about the latter option. If secret keys are enabled in Circle-CI, they are not outright published or visible to the end-user. The build system obfuscates them. The obfuscation is a good first step. Unfortunately, there's a file called .circleci/config.yml in the project, which contains all the commands to be run by the build system. A pull request could modify that file so that it prints the secrets in clear text. - -What can be done? - -The answer - which is not overly difficult if you already have some experience - is to run an in-house build server such as Jenkins. This adds multiple layers of security: - -- Optionally, does *not* publicly print the build output. -- Optionally, does *not* run based on a .circleci file or Jenkinsfile, so modifying the configuration file is not an avenue for external attacks. -- For each build job, it will only include the minimal number of secret keys required for the current task, and nothing more. - -While the new system may not be impregnable, it's a major improvement compared to the security issues with Circle-CI for this specific requirement. - -# Design - -Here is a high level overview of how the system operates, before getting into further details. - -A jenkins server is installed. - -It builds the documentation jobs, and then copies the resulting files to AWS S3. - -The job posts a message in the GitHub pull request conversation with a hyperlink to the new docs. - -Each pull request will get it's own separate "website". There could be hundreds of versions being simultaneously hosted. - -An nginx proxy server which sits in front of S3 serves the documents with a consistent URL format, and allows multiple repositories to share the same S3 bucket. - -The resulting functionality can be seen in action. On this pull request [https://github.com/boostorg/beast/pull/1973](https://github.com/boostorg/beast/pull/1973) a message appears: - -| An automated preview of the documentation is available at [http://1973.beastdocs.prtest.cppalliance.org/libs/beast/doc/html/index.html](http://1973.beastdocs.prtest.cppalliance.org/libs/beast/doc/html/index.html) | - -The link takes you to the preview, and will be updated with each new commit to the pull request. - -# More Details - -The Jenkins server polls each configured repository at a 5 minute interval, to see if a new pull request has been added. Alternatively, instead of polling, you may add a webhook in Github. - -Each repository corresponds to a separate Jenkins "project" on the server. A job checks out a copy of the submitted code, runs the specific steps necessary for that codebase, and uploads the resulting website to an AWS S3 bucket. - -The configuration leverages a few Jenkins plugins:
-- "GitHub Pull Request Builder" to launch jobs based on the existence of a new pull request. -- "S3 Publisher Plugin" for copying files to S3. -- "CloudBees Docker Custom Build Environment Plugin" to run the build inside an isolated docker container. - -One previews bucket is created in S3 such as s3://example-previews - -The file path in the S3 bucket is formatted to be "repository"/"PR #". For example, the filepath of pull request #60 for the repo called "website" is s3://example-previews/website/60 - -The web URL is generated by inverting this path, so "website/60" becomes "60.website". The full URL has the format "60.website.prtest.example.com". This translation is accomplished with an nginx reverse proxy, hosted on the same Jenkins server. - -nginx rule:
-rewrite ^(.*)$ $backendserver/$repo/$pullrequest$1 break; - -A wildcard DNS entry sends the preview visitors to nginx:
-*.prtest.example.com -> jenkins.example.com - -# Implementation - -In this section, we will go over all the steps in detail, as a tutorial. - -In the following code sections,
-Replace "example.com" with your domain.
-Replace "website" with your repository name.
-Replace "example-previews" with your S3 bucket name. - -### General Server Setup - -Install Jenkins - https://www.jenkins.io/doc/book/installing/ - -Install SSL certificate for Jenkins (jenkins.example.com): -``` -apt install certbot -certbot certonly -``` - -Install nginx. - -``` -apt install nginx -``` - -Create a website, as follows: -``` -server { - listen 80; - listen [::]:80; - server_name jenkins.example.com; - location '/.well-known/acme-challenge' { - default_type "text/plain"; - root /var/www/letsencrypt; - } - location / { - return 301 https://jenkins.example.com:8443$request_uri; - } -} - -server { -listen 8443 ssl default_server; -listen [::]:8443 ssl default_server; -ssl_certificate /etc/letsencrypt/live/jenkins.example.com/fullchain.pem; -ssl_certificate_key /etc/letsencrypt/live/jenkins.example.com/privkey.pem; -#include snippets/snakeoil.conf; -location / { -include /etc/nginx/proxy_params; -proxy_pass http://localhost:8080; -proxy_read_timeout 90s; -} -} -``` - -Set the URL inside of Jenkins->Manage Jenkins->Configure System to be https://_url_ , replacing _url_ with the hostname such as jenkins.example.com. - -Install the plugin "GitHub pull requests builder" -Go to ``Manage Jenkins`` -> ``Configure System`` -> ``GitHub pull requests builder`` section. - -Click "Create API Token". Log into github. - -Update "Commit Status Build Triggered", "Commit Status Build Start" to --none-- -Create all three types of "Commit Status Build Result" with --none-- - -On the server: - -``` -apt install git build-essential -``` - -Install the plugin "CloudBees Docker Custom Build Environment" - -add Jenkins to docker group - -``` -usermod -a -G docker jenkins -``` - -Restart jenkins. - -``` -systemctl restart jenkins -``` - -Install the "S3 publisher plugin" - -In Manage Jenkins->Configure System, go to S3 Profiles, create profile. Assuming the IAM user in AWS is called "example-bot", then create example-bot-profile with the AWS creds. The necessary IAM permissions are covered a bit further down in this document. - -Install the "Post Build Task plugin" - -### Nginx Setup - -Create a wildcard DNS entry at your DNS hosting provider: -*.prtest.website.example.com CNAME to jenkins.example.com - -Create an nginx site for previews: - -``` -server { - # Listen on port 80 for all IPs associated with your machine - listen 80 default_server; - - # Catch all other server names - server_name _; - - if ($host ~* ([0-9]+)\.(.*?)\.(.*)) { - set $pullrequest $1; - set $repo $2; - } - - location / { - set $backendserver 'http://example-previews.s3-website-us-east-1.amazonaws.com'; - - #CUSTOMIZATIONS - if ($repo = "example" ) { - rewrite ^(.*)/something$ $1/something.html ; - } - - #FINAL REWRITE - rewrite ^(.*)$ $backendserver/$repo/$pullrequest$1 break; - - # The rewritten request is passed to S3 - proxy_pass http://example-previews.s3-website-us-east-1.amazonaws.com; - #proxy_pass $backendserver; - include /etc/nginx/proxy_params; - proxy_redirect /$repo/$pullrequest / ; - } -} - -``` - -### AWS Setup - -Turn on static web hosting on the bucket. -Endpoint is http://example-previews.s3-website-us-east-1.amazonaws.com - -Add bucket policy - -``` -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PublicReadGetObject", - "Effect": "Allow", - "Principal": "*", - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::example-previews/*" - } - ] -} -``` - -Create an IAM user and add these permissions - -``` - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:GetBucketLocation", - "s3:ListAllMyBuckets" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::example-previews" - ] - }, - { - "Effect": "Allow", - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::example-previews/*" - ] - } - ] -} -``` - -### JENKINS FREESTYLE PROJECTS - -Create a new Freestyle Project - -Github Project (checked) -Project URL: https://github.com/yourorg/website/ - -Source Code Management -Git (checked) -Repositories: https://github.com/yourorg/website -Credentials: github-example-bot (you should add a credential here, that successfully connects to github) -Advanced: -Refspec: +refs/pull/*:refs/remotes/origin/pr/* -Branch Specifier: ${ghprbActualCommit} - -Build Triggers -GitHub Pull Request Builder (checked) -GitHub API Credentials: mybot - -#Consider whether to enable the following setting. -#It is optional. You may also approve each PR. -Advanced: -Build every pull request automatically without asking. - -Trigger Setup: -Build Status Message: -`An automated preview of this PR is available at [http://$ghprbPullId.website.prtest.example.com](http://$ghprbPullId.website.prtest.example.com)` -Update Commit Message during build: -Commit Status Build Triggered: --none-- -Commit Status Build Started: --none-- -Commit Status Build Result: create all types of result, with message --none-- - -Build Environment: -Build inside a Docker container (checked) -#Note: choose a Docker image that is appropriate for your project -Pull docker image from repository: circleci/ruby:2.4-node-browsers-legacy - -Build: -Execute Shell: -``` -#Note: whichever build steps your site requires. -``` - -Post-build Actions -Publish artifacts to S3 -S3 Profile: example-bot-profile - -Source: _site/** (set this value as necessary for your code) -Destination: example-previews/example/${ghprbPullId} -Bucket Region: us-east-1 -No upload on build failure (checked) - -#The following part is optional. It will post an alert into a Slack channel. -Add Post Build Tasks - -Log Text: GitHub - -Script: - -``` -#!/bin/bash -PREVIEWMESSAGE="A preview of the example website is available at http://$ghprbPullId.example.prtest.example.com" -curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$PREVIEWMESSAGE\"}" https://hooks.slack.com/services/T21Q22/B0141JT/aPF___ -``` - -Check box "Run script only if all previous steps were successful" - -In Slack administration, (not in jenkins), create a Slack app. Create a "webhook" for your channel. That webhook goes into the curl command. diff --git a/_posts/2020-06-31-RichardsJuneUpdate.md b/_posts/2020-06-31-RichardsJuneUpdate.md deleted file mode 100644 index 501087e80..000000000 --- a/_posts/2020-06-31-RichardsJuneUpdate.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's May/June Update -author-id: richard ---- - -# Boost 1.74 - Interesting Developments in Asio - -We're currently beta-testing Boost 1.74, the lead-up to which has seen a flurry of activity in Asio, which has -impacted Beast. - -Recent versions of Asio have moved away from the idea of sequencing completion handlers directly on an `io_context` -(which used to be called an `io_service`) towards the execution of completion handlers by an Executor. - -The basic idea being that the executor is a lightweight handle to some execution context, which did what the `io_context` -always used to do - schedule the execution of completion handlers. - -The changes to Asio have been tracking -[The Networking TS](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4771.pdf) which describes a concept -of Executor relevant to asynchronous IO. - -The [Unified Executors](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0443r11.html) proposal unifies the -concepts of io execution and the general concept of "a place to execute work" - a somewhat more generic idea than merely -an IO loop or thread pool. Work has been ongoing by the members of WG21 to produce an execution model that -serves all parties' needs. - -Courtesy of an incredible effort by Chris Kohlhoff, Latest Asio and Boost.Asio 1.74 has been updated to accommodate both -models of executors, with the Unified Executors model being the default. It's important to note that most users won't -notice the change in API this time around since by default the Asio in 1.74 also includes the 1.73 interface. - -There are a number of preprocessor macros that can be defined to change this default behaviour: - -## `BOOST_ASIO_NO_TS_EXECUTORS` - -Defining this macro disables the Networking TS executor model. The most immediate thing you'll notice if you define this -macro is that for some executor `e`, the expression `e.context()` becomes invalid. - -In the Unified Executors world, this operation is expressed as a query against the executor: -```c++ -auto& ctx = asio::query(e, asio::execution::context); -``` -The idea being that the execution context is a _property_ of an executor that can be _queried_ for. - -Another change which users are likely to notice when this macro is defined is that the `asio::executor_work_guard<>` -template corresponding `asio::make_work_guard` function is no longer defined. - -You may well ask then, how we would prevent an underlying execution context from running out of work? - -In the Unified Executors world, we can think of Executors as an unbounded set of types with various properties -enabled or disabled. The idea is that the state of the properties define the behaviour of the interaction between the -executor and its underlying context. - -In the new world, we don't explicitly create a work guard which references the executor. We 'simply' create a new -executor which happens to have the property of 'tracking work' (i.e. this executor will in some way ensure that the -underlying context has outstanding work until the executor's lifetime ends). - -Again, given that `e` is some executor, here's how we spell this: - -```c++ -auto tracked = asio::require(e, asio::execution::outstanding_work.tracked); -``` - -After executing this statement, there are now two executors in play. The first, `e` may or may not be "tracking work" -(ensuring that the underlying context does not stop), but `tracked` certainly is. - -There is another way to spell this, more useful in a generic programming environment. - -Suppose you were writing generic code and you don't know the type of the executor presented to you, or even what kind -of execution context it is associated with. However, you do know that *if* the underlying context can stop if it runs -out of work, then we want to prevent it from doing so for the duration of some operation. - -In this case, we can't use `require` because this will fail to compile if the given executor does not support the -`outstanding_work::tracked` property. Therefore we would request (or more correctly, _prefer_) the capability rather -than require it: - -```c++ -auto maybe_tracked = asio::prefer(e, asio::execution::outstanding_work.tracked); -``` - -We can now use `maybe_tracked` as the executor for our operation, and it will "do the right thing" regarding the tracking -of work whatever the underlying type of execution context. It is important to note that it _is_ an executor, not merely -a guard object that contains an executor. - -### post, dispatch and defer - -Another notable change in the Asio API when this macro is defined is that models of the Executor concept lose their -`post`, `dispatch` and `defer` member functions. - -The free function versions still remain, so if you have code like this: -```c++ -e.dispatch([]{ /* something */ }); -``` - -you will need to rewrite it as: - -```c++ -asio::dispatch(e, []{ /* something */ }); -``` - -or you can be more creative with the underlying property system: - -```c++ -asio::execution::execute( - asio::prefer( - e, - asio::execution::blocking.possibly), - []{ /* something */ }); -``` - -Which is more-or-less what the implementation of `dispatch` does under the covers. It's actually a little more involved -than that since the completion token's associated allocator has to be taken into account. There is a -property for that too: `asio::execution::allocator`. - -In summary, all previous Asio and Networking TS execution/completion scenarios are now handled by executing a handler -in some executor supporting a set of relevant properties. - -## BOOST_ASIO_NO_DEPRECATED - -Defining this macro will ensure that old asio-style invocation and allocation completion handler customisation -functions will no longer be used. The newer paradigm is to explicitly query or require execution properties at the -time of scheduling a completion handler for invocation. If you don't know what any of that means, you'd be in the -majority and don't need to worry about it. - -## BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT - -As of Boost 1.74, Asio IO objects will be associated with the new `asio::any_io_executor` rather than the previous -polymorphic `asio::executor`. Defining this macro, undoes this change. It may be useful to you if you have written code -that depends on the use of `asio::executor`. - -## Other observations - -### Strands are Still a Thing - -Asio `strand` objects still seem to occupy a twilight zone between executors and something other than executors. - -To be honest, when I first saw the property mechanism, I assumed that a strand would be "just another executor" with -some "sequential execution" property enabled. This turns out not to be the case. A strand has its own distinct execution -context which manages the sequencing of completion handler invocations within it. The strand keeps a copy of the inner -executor, which is the one where the strand's completion handlers will be invoked in turn. - -However, a strand models the Executor concept, so it also *is an* executor. - -### execute() looks set to become the new call(). - -Reading the Unified Executors paper is an interesting, exciting or horrifying experience - depending on your view of -what you'd like C++ to be. - -My take from the paper, fleshed out a little with the experience of touching the implementation in Asio, is that in the -new world, the programming thought process will go something like this, -imagine the following situation: - -"I need to execute this set of tasks, - -Ideally I'd like them to execute in parallel, - -I'd like to wait for them to be done" - -As I understand things, the idea behind unified executors is that I will be able to express these desires and mandates -by executing my work function(s) in some executor yielded by a series of calls to `prefer` and `require`. - -Something like: - -```c++ - auto eparallel = prefer(e, bulk_guarantee.unsequenced); // prefer parallel execution - auto eblock = require(eparallel, blocking.always); // require blocking - execute(eblock, task1, task2, task3, task...); // blocking call which will execute in parallel if possible -``` - -Proponents will no doubt think, - -"Great! Programming by expression of intent". - -Detractors might say, - -"Ugh! Nondeterministic programs. How do I debug this when it goes wrong?" - -To be honest that this stage, I find myself in both camps. No doubt time will tell. - -# Adventures in B2 (Boost Build) - -Because of the pressure of testing Beast with the new multi-faceted Asio, I wanted a way to bulk compile and test many -different variants of: - -* Compilers -* Preprocessor macro definitions -* C++ standards -* etc. - -I was dimly aware that the Boost build tool, B2, was capable of doing this from one command-line invocation. - -It's worth mentioning at this point that I have fairly recently discovered just how powerful B2 is. It's a shame that -it has never been offered to the world in a neat package with some friendly conversation-style documentation, which -seems to be the norm these days. - -It can actually do anything CMake can do and more. For example, all of the above. - -My thanks to Peter Dimov for teaching me about the existence of B2 *features* and how to use them. - -It turns out to be a simple 2-step process: - -First defined a `user-config.jam` file to describe the feature and its settings: - -```jam -import feature ; - -feature.feature asio.mode : dflt nodep nots ts nodep-nots nodep-ts : propagated composite ; -feature.compose nodep : "BOOST_ASIO_NO_DEPRECATED" ; -feature.compose nots : "BOOST_ASIO_NO_TS_EXECUTORS" ; -feature.compose ts : "BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT" ; -feature.compose nodep-nots : "BOOST_ASIO_NO_DEPRECATED" "BOOST_ASIO_NO_TS_EXECUTORS" ; -feature.compose nodep-ts : "BOOST_ASIO_NO_DEPRECATED" "BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT" ; - -using clang : : clang++ : "libc++" "-Wno-c99-extensions" ; -using gcc : : g++ : "-Wno-c99-extensions" ; -``` - -Then ask b2 to do the rest: - -``` -./b2 --user-config=./user-config.jam \ - toolset=clang,gcc \ - asio.mode=dflt,nodep,nots,ts,nodep-nots,nodep-ts \ - variant=release \ - cxxstd=2a,17,14,11 \ - -j`grep processor /proc/cpuinfo | wc -l` \ - libs/beast/test libs/beast/example -``` - -This will compile all examples and run all tests in beast on a linux platform for the cross-product of: - -1. clang and gcc -2. all 6 of the legal combinations of the preprocessor macros BOOST_ASIO_NO_DEPRECATED, BOOST_ASIO_NO_TS_EXECUTORS and -BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT -3. C++ standards 2a, 17, 14 and 11 - -So that's 48 separate scenarios. - -It will also: - -* Build any dependencies. -* Build each scenario into its own separately named path in the bin.v2 directory. -* Understand which tests passed and failed so that passing tests are not re-run on subsequent calls to b2 unless a - dependent file has changed. -* Use as many CPUs as are available on the host (in my case, fortunately that's 48, otherwise this would take a long time - to run) - diff --git a/_posts/2020-07-01-KrystiansMayJuneUpdate.md b/_posts/2020-07-01-KrystiansMayJuneUpdate.md deleted file mode 100644 index b62b47617..000000000 --- a/_posts/2020-07-01-KrystiansMayJuneUpdate.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -layout: post -nav-class: dark -categories: krystian -title: Krystian's May & June Update -author-id: krystian ---- - -# Overview - -I've been very busy these last two months getting Boost.JSON ready for release, hence the combined blog post. Now that things are winding down, I hopefully can get back the normal blog release schedule. - -# Boost.JSON - -Aside from a couple of personal projects, the vast majority of my time was spent getting Boost.JSON set for release. Breaking it down, this consisted of three main tasks: a `tag_invoke` based `value` conversion interface, parser optimizations, and support for extended JSON syntax. - -## Value Conversion - -Our previous interface that allowed users to specify their own conversions to and from `value` proved unsatisfactory, as it required too much boiler-plate when specifying conversions to and from non-class types (e.g. enumeration types). To remedy this, I was tasked with implementing an ADL solution based on `tag_invoke` which greatly reduces the amount of boiler-plate and provides a single, straightforward way to implement a custom conversion. For example, consider the following class type: - -```cpp -struct customer -{ - std::string name; - std::size_t balance; -}; -``` - -To convert an object of type `customer` to `value`, all you need is to write an overload of `tag_invoke`. This can be implemented as an inline `friend` function within the class definition (thus making it visible to ADL but not unqualified lookup; see [[basic.lookup.argdep] p4.3]](http://eel.is/c++draft/basic.lookup.argdep#4.3)), or as a free function: - -```cpp -void tag_invoke(value_from_tag, value& jv, const customer& c) -{ - object& obj = jv.emplace_object(); - obj["name"] = c.name; - obj["balance"] = c.balance; -} -``` - -Note that a reference to `value` is passed to the function performing the conversion. This ensures that the `storage_ptr` passed to the calling function (i.e. `value_from(T&&, storage_ptr)`) is correctly propagated to the result. - -Conversions from `value` to a type `T` are specified in a similar fashion: - -```cpp -customer tag_invoke(value_to_tag, const value& jv) -{ - return customer{ - value_to(jv.at("name"])), - jv.at("balance").as_uint64() - }; -} -``` - -In addition to user-provided `tag_invoke` overloads, generic conversions are provided for container-like, map-like, and string-like types, with obvious results. In general, if your container works with a range-based for loop, it will work with `value_from` and `value_to` without you having to write anything. - -## Parser Optimizations - -Optimizing the parser was a side-project turned obsession for me. While it's often a painfully tedious process of trying an idea, running benchmarks, and being disappointed with the results, the few times that you get a performance increase makes it all worth it. - -To preface, Boost.JSON is unique in that it can parse incrementally (no other C++ libraries implement this). However, incremental parsing is considerably slower than parsing a JSON document in its entirety, as a stack must be maintained to track which function the parser should resume to once more data is available. In addition to this, the use cases for incremental parsing will often involve bottlenecks much more significant than the speed of the parser. With this in mind, Boost.JSON's parser is optimized for non-incremental parsing of a valid JSON document. The remainder of this post will be written without consideration for incremental parsing. - -Most of the optimizations were branch eliminations, such as removing branches based on call site preconditions. These yield small performance gains, but once compounded we saw a performance increase of up to 7% on certain benchmarks. The biggest gain in this category came from removing a large switch statement in `parse_value` in favor of a manually written jump table. Making this function branchless significantly increases performance as it's the most called function when parsing. This also makes the function very compact, meaning it can be inlined almost everywhere. - -In addition to benchmark driven optimization, I also optimized based on codegen. Going into it I really had no idea what I was doing, but after staring at it for a long time and watching some videos I got the hang of it. I used this method to optimize `parse_array` and `parse_object`, aiming to get the most linear hot path possible, with the fewest number of jumps. It took a few hours, but I was able to reach my target. This was done by moving some branches around, removing the `local_const_stream` variable, and adding some optimization hints to various branches. In addition to this, the `std::size_t` parameter (representing the number of elements) was removed from the `on_array_end` and `on_object_end` handlers as it didn't provide any useful information and is not used by `parser`. This yielded a performance increase of up to 4% in certain cases. - -The last major optimization was [suggested](https://github.com/CPPAlliance/json/issues/115) by [Joaquín M López Muñoz](https://github.com/joaquintides). In essence, integer division is a slow operation, so compilers have all sorts of ways to avoid it; one of which is doing multiplication instead. When dividing by a constant divisor, the compiler is able to convert this to multiplication by the reciprocal of the divisor, which can be up to 20 times faster. Where this is applicable in Boost.JSON is in the calculation used to get the index of the bucket for a `object` key. The implementation was pretty straightforward, and it yielded up to a 10% increase in performance for `object` heavy benchmarks -- a remarkable gain from such a small change. Thank you Joaquín :) - -## Parser Extensions - -The last major thing I worked on for Boost.JSON was implementing support for extended JSON syntaxes. The two supported extensions are: - allowing C and C++ style comments to appear within whitespace, and -- allowing trailing commas to appear after the last element of an array or object. -This post isn't quite in chronological order, but comment support was my introduction into working on the parser (a trial by fire). After a few naive attempts at implementation, the result was comment parsing that did not affect performance at all when not enabled (as it should) and only has a minor impact on performance when enabled. This was done by building off existing branches within `parse_array` and `parse_object` instead of checking for comments every time whitespace is being parsed. Allowing for trailing commas was done in much the same way. The larger takeaway from implementing these extensions was getting to know the internals of the parser much better, allowing me to implement the aforementioned optimizations, as well as more complex extensions in the future. - -If you want to get in touch with me, you can message me on the [Cpplang slack](http://slack.cpp.al/), or [shoot me an email](mailto:sdkrystian@gmail.com). \ No newline at end of file diff --git a/_posts/2020-08-01-KrystiansJulyUpdate.md b/_posts/2020-08-01-KrystiansJulyUpdate.md deleted file mode 100644 index 16a04ff54..000000000 --- a/_posts/2020-08-01-KrystiansJulyUpdate.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -layout: post -nav-class: dark -categories: krystian -title: Krystian's July Update -author-id: krystian ---- - -# What I've been doing - -I've been spending a *lot* of time working on optimizing the parser; perhaps a bit too much. Nevertheless, it's very enjoyable and in doing so I've learned more than I could hope to ever learn in school. In addition to the optimization, comment and trailing comma support finally got merged, and I implemented UTF-8 validation (enabled by default, but it can be disabled). - -## UTF-8 validation - -Prior to implementing this extension (or rather, feature which can be disabled), the parser considers any character appearing within a string to be valid, so long as it wasn't a control character or formed an illegal escape. While this is _fast_, it technically does not conform to the JSON standard. - -As per Section 2 of the [JSON Data Interchange Syntax Standard](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf): - -> A conforming JSON text is a sequence of Unicode code points that strictly conforms to the JSON grammar defined by this specification. - -As with most standardese, this particular requirement for conformance is not outright stated, but rather implied. Anyways, that's enough standardese talk for this post. - -After working on this parser so much, I've pretty much got the suspend/resume idiom we use nailed down, so integrating it with the string parsing function was trivial... the actual validation, not so much. I hadn't the slightest clue about any of the terminology used in the Unicode standard, so it took a good couple of hours to point myself in the right direction. Anyways, a lot of Googling and a messy python script for generating valid and invalid byte sequences later, I had something functional. - -Then came my favorite part: optimization. - -The first byte within a UTF-8 byte sequence determines how many bytes will follow, as well as the valid ranges for these following bytes. Since this byte has such a large valid range, I settled on using a lookup table to check whether the first byte is valid. - -Luckily, the following bytes have ranges that can be trivially checked using a mask. For example, if the first byte is `0xE1`, then the byte sequence will be composed of three bytes, the latter two having a valid range of `0x80` to `0xBF`. Thus, our fast-path routine to verify this sequence can be written as: - -```cpp -uint32_t v; -// this is reversed on big-endian -std::memcpy(&v, bytes, 4); // 4 bytes load - -switch (lookup_table[v & 0x7F]) // mask out the most significant bit -{ -... -case 3: - if ((v & 0x00C0C000) == 0x00808000) - return result::ok; - return result::fail; -... -} -``` - -This works well for all but one byte sequence combination. For whatever reason, UTF-8 byte sequences that start with `0xF0` can have a second byte between `0x90` and `0xBF` which requires the check to be done as: - -```cpp -(v & 0xC0C0FF00) + 0x7F7F7000 <= 0x00002F00 -``` - -It's a weird little outlier that I spent way too much time trying to figure out. - -Since our parser supports incremental parsing, we only take the fast path if the input stream has four or more bytes remaining. If this condition isn't met, we have to check each byte individually. It's slower, but shouldn't happen often. - -## Other optimizations - -I've been trying out a number of different optimizations to squeeze all the performance we can get out of the parser. Most recently, I rewrote the parser functions to take a `const char*` parameter indicating the start of the value, and return a pointer to the end of the value (if parsing succeeds) or `nullptr` upon failure or partial parsing. - -Since I'm not great at explaining things, here's the before: - -```cpp -result parse_array(const_stream&); -``` - -and here's the after: - -```cpp -const char* parse_array(const char*); -``` - -This allows us to keep the pointer to the current position in the stream entirely within the registers when parsing a document. Since the value is local to the function, the compiler no longer needs to write it to the `const_stream` object at the top of the call stack (created within `basic_parser::write_some`), nor read it each time a nested value is parsed. This yields an *8%* boost in performance across the board. - -More time was spent optimizing the SSE2 functions used for parsing unescaped strings and whitespace as well. Within `count_whitespace`, we were able to get rid of a `_mm_cmpeq_epi8` (`PCMPEQB`) instruction by performing a bitwise or with 4 after testing for spaces, and then comparing the result with `'\r'`, as the ASCII value of tab (`'\t'`) only differs from that of the carriage return by the third least significant bit. This was something that clang was doing for us, but it's nice to implement it for all other compilers. - -For `count_unescaped` (used to parse unescaped strings), we were able to again reduce the length of the hot path, this time a bit more significantly. Instead of checking for control characters by means of relational comparison, we can instead check for quotes and backslash first, and once that's done, the `_mm_min_epu8` (`PMINUB`) instruction can be used to set all control characters (0 - 31) to 31, and then test for equality. This brought our performance on the `strings.json` benchmark past the 8 GB/s mark from around 7.7 GB/s. Combined with the optimization of how the stream pointer is passed around, we now hit just a hair under 8.5 GB/s on this benchmark. - -## The important but boring stuff - -After merging the parser extensions, there was a bunch of housekeeping to do such as improving coverage and writing documentation. Though these are far from being my favorite tasks, they are integral to writing a good library, so it must be done. My initial approach to writing tests for the parser extensions was to run each test on every parser configuration we have, but this soon proved to be a nonoptimal approach when the time taken to run the test suite quadrupled. I ended up doing the right thing by making the tests more surgical in nature, and in doing so we even got 100% coverage on the parser. diff --git a/_posts/2020-08-01-RichardsJulyUpdate.md b/_posts/2020-08-01-RichardsJulyUpdate.md deleted file mode 100644 index d8bfa955b..000000000 --- a/_posts/2020-08-01-RichardsJulyUpdate.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's July Update -author-id: richard ---- - -# Boost 1.74 - Progress Update - -Boost 1.74 beta release has been published and the various maintainers are applying last-minute bug fixes to their -libraries in readiness for the final release on 12th August. - -For us in the Beast team, a fair amount of attention has been spent monitoring last minutes changes to Asio, as Chris -makes the final tweaks after the Unified Executors update I mentioned in last month's blog. - -## Comprehensive Testing - -Last month I [committed](https://github.com/boostorg/beast/commit/b84d8ad3d48d173bd78ed6dc2ed8d26d84762af3) what I hoped -would be the first of a suite of Dockerfiles which help the mass testing of Beast. The upstream changes to Asio -were a lesson in just how many compilers, hosts and target environments we have to support in order that our user base -is not surprised or impeded as a result of compiler selection or imposition. - -I am not expert is Docker matters. I mean, I can read the manual and follow basic instructions like anyone else, -but I was hoping that someone would come along to help flesh out the suite a little. Particularly for the Windows -builds, since I have no experience in installing software from the command line in Windows, and the greatest respect -for those individuals who have mastered the art. - -Fortunately for me, we've had a new addition to the team. Sam Darwin, who has submitted a number of commits which -increase Docker coverage. Of these I was most pleased to see the submission of the -[Windows](https://github.com/boostorg/beast/commit/3486e9cb18aa39b392e07031a33e65b1792fbccf) build matrix which has -been of enormous value. I think it would be fair to say that Microsoft Visual Studio is nothing short of notorious -for its subtle deviations from the standard. As if it were not difficult enough already to create useful and coherent -template libraries, supporting (particularly older) versions of MSVC requires extra care and workarounds. - -Hopefully, now that two-phase lookup has been firmly -[adopted](https://devblogs.microsoft.com/cppblog/two-phase-name-lookup-support-comes-to-msvc/) by Microsoft (some two -decades after its standardisation), this kind of issue will become less of a concern as time moves forward and support -for older compilers is gradually dropped. - -To be fair to Microsoft, if my memory serves, they were pioneers of bringing the C++ language to the masses back in the -days of Visual Studio 97 and prior to that, the separate product Visual C++ (which we used to have to pay for!). - -In hindsight a number of errors were made in terms of implementation that had lasting effects on a generation of -developers and their projects. But arguably, had Microsoft not championed this effort, it is likely that C++ may not -have achieved the penetration and exposure that it did. - -# A Bug In Asio Resolver? Surely Not? - -One of the examples in the Beast repository is a simple -[web crawler](https://github.com/boostorg/beast/tree/develop/example/http/client/crawl). If you have taken sufficient -interest to read the code, you will have noticed that it follows the model of "multiple threads, `one io_context` per -thread." - -This may seem an odd decision, since a web crawler spends most of its time idle waiting for asynchronous IO to complete. -However, there is an unfortunate implementation detail in the *nix version of `asio::ip::tcp::resolver` which is due to -a limitation of the [`getaddrinfo`](https://linux.die.net/man/3/getaddrinfo) API upon which it depends. - -For background, `getaddrinfo` is a thread-safe, blocking call. This means that Asio has to spawn a background thread -in order to perform the actual name resolution as part of the implementation of `async_resolve`. - -So with that out of the way, why am I writing this? - -One of the bugs I tackled this month was that this demo, when run with multiple (say 500) threads, can be reliably made -to hang indefinitely on my Fedora 32 system. At first, I assumed that either we or Asio had introduced a race condition. -However, after digging into the lockup it turned out to almost always lock up while resolving the FQDN `secureupload.eu`. - -Investigating further, it turns out that the nameserver response for this FQDN is too long to fit into a UDP packet. -This means that the DNS client on the Linux host is forced to revert to a TCP connection in order to receive the entire -record. This can be evidenced by using `nslookup` on the command line: - -``` -$ nslookup secureupload.eu -Server: 192.168.0.1 <<-- address of my local nameserver -Address: 192.168.0.1#53 - -Non-authoritative answer: -Name: secureupload.eu -Address: 45.87.161.67 - -... many others ... - -Name: secureupload.eu -Address: 45.76.235.58 -;; Truncated, retrying in TCP mode. <<-- indication that nslookup is switching to TCP -Name: secureupload.eu -Address: 2a04:5b82:3:209::2 - -... many others - -Name: secureupload.eu -Address: 2a04:5b82:3:203::2 -``` - -Furthermore, whenever I checked the call stack, the thread in question was always stuck in the glibc function -[`send_vc()`](https://code.woboq.org/userspace/glibc/resolv/res_send.c.html#send_vc), which is called by `getaddrinfo` -in response to the condition of a truncated UDP response. - -So despite my initial assumption that there must be a race in user code, the evidence was starting to point to something -interesting about this particular FQDN. Now I've been writing software for over three decades on and off and I've seen -a lot of bugs in code that the authors were adamant they they had not put there. We are as a rule, our own worst -critics. So I was reluctant to believe that there could be a data-driven bug in glibc. - -Nevertheless, a scan of the redhat bug tracker by Chris turned up -[this little nugget](https://bugzilla.redhat.com/show_bug.cgi?id=1429442). - -It turns out that what was happening was that the TCP connection to the upstream name server had gone quiet or had -dropped packets - presumably courtesy my cheap Huawei Home Gateway router which was being swamped by 500 simultaneous - requests by the 500 threads I had assigned to the crawler. Because the glibc implementation does not implement -a timeout on the request, the failed reception of a response by the nameserver caused the call to `gethostinfo` to hang -whenever this FQDN was being resolved. - -So it turns out that there is indeed a bug in glibc, which can affect any *nix (or cygwin) program that performs DNS -address resolution when the requested domain's response is too long to fit in a UDP response message. - -Until this bug is fixed, I have learned a lesson and you have been warned. diff --git a/_posts/2020-09-01-RichardsAugustUpdate.md b/_posts/2020-09-01-RichardsAugustUpdate.md deleted file mode 100644 index 63be03e91..000000000 --- a/_posts/2020-09-01-RichardsAugustUpdate.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's August Update -author-id: richard ---- - -# New Debugging Feature in Asio and Beast - -As covered previously, Boost 1.74 brought an implementation of the new unified executors model to Boost.Asio. - -Support for this is not the only thing that is new in Beast. - -Chris Kohlhoff recently submitted a [PR](https://github.com/boostorg/beast/pull/2053) to Beast's repository -demonstrating how to annotate source code with the `BOOST_ASIO_HANDLER_LOCATION` macro. I have since followed up and -annotated all asynchronous operations in Beast this way. - -In a normal build, there is no effect (and zero extra code generation). However, defining the preprocessor macro -`BOOST_ASIO_ENABLE_HANDLER_TRACKING` will cause these macros to generate code which will emit handler tracking -log data to stdout in a very specific format. - -The output is designed to describe the flow of asynchronous events in a format suitable for generating a visualisation -in linear terms. i.e. the asynchronous events are flattened and linked to show causality. - -Here is an example of the output: - -``` -@asio|1597543084.233257|>33| -@asio|1597543084.233273|33|deadline_timer@0x7fa6cac25218.cancel -@asio|1597543084.233681|33^34|in 'basic_stream::async_write_some' (../../../../../../boost/beast/core/impl/basic_stream.hpp:321) -@asio|1597543084.233681|33^34|called from 'async_write' (../../../../../../boost/asio/impl/write.hpp:331) -@asio|1597543084.233681|33^34|called from 'ssl::stream<>::async_write_some' (../../../../../../boost/asio/ssl/detail/io.hpp:201) -@asio|1597543084.233681|33^34|called from 'http::async_write_some' (../../../../../../boost/beast/http/impl/write.hpp:64) -@asio|1597543084.233681|33^34|called from 'http::async_write' (../../../../../../boost/beast/http/impl/write.hpp:223) -@asio|1597543084.233681|33^34|called from 'http::async_write(msg)' (../../../../../../boost/beast/http/impl/write.hpp:277) -@asio|1597543084.233681|33*34|deadline_timer@0x7fa6cac25298.async_wait -@asio|1597543084.233801|33^35|in 'basic_stream::async_write_some' (../../../../../../boost/beast/core/impl/basic_stream.hpp:373) -@asio|1597543084.233801|33^35|called from 'async_write' (../../../../../../boost/asio/impl/write.hpp:331) -@asio|1597543084.233801|33^35|called from 'ssl::stream<>::async_write_some' (../../../../../../boost/asio/ssl/detail/io.hpp:201) -@asio|1597543084.233801|33^35|called from 'http::async_write_some' (../../../../../../boost/beast/http/impl/write.hpp:64) -@asio|1597543084.233801|33^35|called from 'http::async_write' (../../../../../../boost/beast/http/impl/write.hpp:223) -@asio|1597543084.233801|33^35|called from 'http::async_write(msg)' (../../../../../../boost/beast/http/impl/write.hpp:277) -@asio|1597543084.233801|33*35|socket@0x7fa6cac251c8.async_send -@asio|1597543084.233910|.35|non_blocking_send,ec=system:0,bytes_transferred=103 -@asio|1597543084.233949|<33| -@asio|1597543084.233983|<31| -@asio|1597543084.234031|>30|ec=system:89 -@asio|1597543084.234045|30*36|strand_executor@0x7fa6cac24bd0.execute -@asio|1597543084.234054|>36| -@asio|1597543084.234064|<36| -@asio|1597543084.234072|<30| -@asio|1597543084.234086|>35|ec=system:0,bytes_transferred=103 -@asio|1597543084.234100|35*37|strand_executor@0x7fa6cac24bd0.execute -@asio|1597543084.234109|>37| -@asio|1597543084.234119|37|deadline_timer@0x7fa6cac25298.cancel -@asio|1597543084.234198|37^38|in 'basic_stream::async_read_some' (../../../../../../boost/beast/core/impl/basic_stream.hpp:321) -@asio|1597543084.234198|37^38|called from 'ssl::stream<>::async_read_some' (../../../../../../boost/asio/ssl/detail/io.hpp:168) -@asio|1597543084.234198|37^38|called from 'http::async_read_some' (../../../../../../boost/beast/http/impl/read.hpp:212) -@asio|1597543084.234198|37^38|called from 'http::async_read' (../../../../../../boost/beast/http/impl/read.hpp:297) -@asio|1597543084.234198|37^38|called from 'http::async_read(msg)' (../../../../../../boost/beast/http/impl/read.hpp:101) -@asio|1597543084.234198|37*38|deadline_timer@0x7fa6cac25218.async_wait -@asio|1597543084.234288|37^39|in 'basic_stream::async_read_some' (../../../../../../boost/beast/core/impl/basic_stream.hpp:373) -@asio|1597543084.234288|37^39|called from 'ssl::stream<>::async_read_some' (../../../../../../boost/asio/ssl/detail/io.hpp:168) -@asio|1597543084.234288|37^39|called from 'http::async_read_some' (../../../../../../boost/beast/http/impl/read.hpp:212) -@asio|1597543084.234288|37^39|called from 'http::async_read' (../../../../../../boost/beast/http/impl/read.hpp:297) -@asio|1597543084.234288|37^39|called from 'http::async_read(msg)' (../../../../../../boost/beast/http/impl/read.hpp:101) -@asio|1597543084.234288|37*39|socket@0x7fa6cac251c8.async_receive -@asio|1597543084.234334|.39|non_blocking_recv,ec=system:35,bytes_transferred=0 -@asio|1597543084.234353|<37| -@asio|1597543084.234364|<35| -@asio|1597543084.234380|>34|ec=system:89 -@asio|1597543084.234392|34*40|strand_executor@0x7fa6cac24bd0.execute -@asio|1597543084.234401|>40| -@asio|1597543084.234408|<40| -@asio|1597543084.234416|<34| -@asio|1597543084.427594|.39|non_blocking_recv,ec=system:0,bytes_transferred=534 -@asio|1597543084.427680|>39|ec=system:0,bytes_transferred=534 -``` - -So far, so good. But not very informative or friendly to the native eye. - -Fortunately as of Boost 1.74 there is a tool in the Asio source tree to convert this data into something consumable by the open source -tool dot, which can then output the resulting execution graph in one of a number of common graphical formats such as -PNG, BMP, SVG and many others. - -Here is an example of a visualisation of a simple execution graph: - -![](/images/posts/richard/2020-09-01-handler-tracking-example.png) - -The tool you need to do this is in the `asio` subproject of the Boost repo. The full path is -`libs/asio/tools/handlerviz.pl`. The command is self-documenting but for clarity, the process would be like this: -* Compile and link your program with the compiler flag `-DBOOST_ASIO_ENABLE_HANDLER_TRACKING` -* run your program, capturing stdout to a file (say `mylog.txt`) (or you can pipe it to the next step) -* `handlerviz.pl < mylog.txt | dot -Tpng mygraph.png` -* You should now be able to view your graph in a web browser, editor or picture viewer. - -The documentation for dot is [here](https://linux.die.net/man/1/dot) dot is usually available in the graphviz package -of your linux distro/brew cask. Windows users can download an executable suite -[here](https://www.graphviz.org/download/). - -If you have written your own asynchronous operations to compliment Beast or Asio, or indeed you just wish you add your -handler locations to the graph output, you can do so by inserting the `BOOST_ASIO_HANDLER_LOCATION` macro just before -each asynchronous suspension point (i.e. just before the call to `async_xxx`). If you're doing this in an Asio -`coroutine` (not to be confused with C++ coroutines) then be sure to place the macro in curly braces after the -YIELD macro, for example: - -``` - ... - - // this marks a suspension point of the coroutine - BOOST_ASIO_CORO_YIELD - { - // This macro creates scoped variables so must be in a private scope - BOOST_ASIO_HANDLER_LOCATION(( // note: double open brackets - __FILE__, __LINE__, // source location - "websocket::tcp::async_teardown" // name of the initiating function - )); - - // this is the initiation of the next inner asynchronous operation - s_.async_wait( - net::socket_base::wait_read, - beast::detail::bind_continuation(std::move(*this))); - - // there is an implied return statement here - } - - ... -``` - -When writing applications, people historically have used Continuation Passing Style when calling asynchronous -operations, capturing a shared_ptr to the connection implementation in each handler (continuation). - -When using this macro in user code with written in continuation passing style, you might do so like this: - -``` -void send_request(http::request req) -{ - send_queue_.push_back(std::move(req)); - if (!sending_) - { - sending_ = true; - maybe_initiate_send(); - } -} - -void my_connection_impl::maybe_initiate_send() -{ - if (send_queue_.empty()) - { - sending_ = false; - return; - } - - // assume request_queue_ is a std::deque so elements will have stable addresses - auto& current_request = request_queue_.front(); - - BOOST_ASIO_HANDLER_LOCATION(( - __FILE__, __LINE__, - "my_connection_impl::maybe_initiate_send" - )); - - // suspension point - - boost::beast::http::async_write(stream_, current_request_, - [self = this->shared_from_this()](boost::beast::error_code ec, std::size_t) - { - // continuation - - if (!ec) - { - self->request_queue_.pop_front(); - self->maybe_initiate_send(); - } - else - { - // handle error - } - }); -} -``` - - -If you're using c++ coroutines it becomes a little more complicated as you want the lifetime of the tracking -state to be destroyed after the asynchronous initiation function but before the coroutine continuation: - -``` -namespace net = boost::asio; -namespace http = boost::beast::http; - -auto connect_and_send( - boost::asio::ip::tcp::socket& stream, - std::string host, - std::string port, - http::request req) --> net::awaitable -{ - namespace net = boost::asio; - - auto resolver = net::ip::tcp::resolver(co_await net::this_coro::executor); - - // suspension point coming up - - auto oresults = std::optional>(); - { - BOOST_ASIO_HANDLER_LOCATION(( - __FILE__, __LINE__, - "my_connection_impl::connect_and_send" - )); - oresults.emplace(resolver.async_resolve(host, port, net::use_awaitable)); - } - auto results = co_await std::move(*oresults); - - auto oconnect = std::optional>(); - { - BOOST_ASIO_HANDLER_LOCATION(( - __FILE__, __LINE__, - "my_connection_impl::connect_and_send" - )); - oconnect.emplace(net::async_connect(stream, results, net::use_awaitable)); - } - auto ep = co_await *std::move(oconnect); - - // ... and so on ... - -} -``` - -Which might look a little unwieldy compared to the unannotated code, which could look like this: - -``` -auto connect_and_send( - boost::asio::ip::tcp::socket& stream, - std::string host, - std::string port, - http::request req) --> net::awaitable -{ - namespace net = boost::asio; - - auto resolver = net::ip::tcp::resolver(co_await net::this_coro::executor); - - auto ep = co_await net::async_connect(stream, - co_await resolver.async_resolve(host, port, net::use_awaitable), - net::use_awaitable); - - // ... and so on ... - -} -``` diff --git a/_posts/2020-09-06-KrystiansAugustUpdate.md b/_posts/2020-09-06-KrystiansAugustUpdate.md deleted file mode 100644 index 4fd4ea7f3..000000000 --- a/_posts/2020-09-06-KrystiansAugustUpdate.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: post -nav-class: dark -categories: krystian -title: Krystian's August Update -author-id: krystian ---- - -# Boost.JSON - -Boost.JSON is officially scheduled for review! It starts on September 14th, so there isn't much time left to finish up polishing the library -- but it looks like we will make the deadline. - -## Optimize, optimize, optimize - -Boost.JSON's performance has significantly increased in the past month. The change to the parsing functions where we pass and return `const char*` instead of `result` (detailed in my last post) was merged, bringing large gains across the board. After this, my work on optimizing `basic_parser` was complete (for now...), save for a few more minor changes: - -- The handler is stored as the first data member as opposed to passing a reference to each parse function. This means that the `this` pointer for `basic_parser` is the `this` pointer for the handler, which eliminates some register spills. - -- The parser's depth (i.e. nesting level of objects/arrays) is now tracked as `max_depth - actual_depth`, meaning that we don't have to read `max_depth` from memory each time a structure is parsed. - -- `parse_string` was split into two functions: `parse_unescaped` and `parse_escaped`. The former is much cheaper to call as it doesn't have to store the string within a local buffer, and since unescaped strings are vastly more common in JSON documents, this increases performance considerably. - -### The DOM parser - -Our old implementation of `parser` was pretty wasteful. It stored state information (such as whether we were parsing an object or array), keys, and values, all on one stack. This proved to be quite a pain when it came to unwinding it and also required us to align the stack when pushing arrays and objects. - -Several months ago, Vinnie and I tried to figure out how to make the homogeneous but came to a dead end. I decided to revisit the idea, and after some experimentation, it became apparent that there was a *lot* of redundancy in the implementation. For example, `basic_parser` already keeps track of the current object/array/string/key size, so there is no reason to so within `parser`. The state information we were tracking was also not needed -- `basic_parser` already checks the syntactic correctness of the input. That left one more thing: strings and keys. - -My rudimentary implementation required two stacks: one for keys and strings, and the other for values. Other information, such as the sizes of objects and arrays, were obtained from `basic_parser`. My implementation, though primitive, gave some promising results on the benchmarks: up to 10% for certain documents. After some brainstorming with Vinnie, he had the idea of storing object keys as values; the last piece of the puzzle we needed to make this thing work. - -His fleshed-out implementation was even faster. In just a week's time, Boost.JSON's performance increased by some 15%. I'm still working on the finishing touches, but the results are looking promising. - -## More UTF-8 validation malarkey - -Out of all the things I've worked on, nothing has proved as frustrating as UTF-8 validation. The validation itself is trivial; but making it work with an incremental parser is remarkably difficult. Shortly after merging the feature, [an issue was opened](https://github.com/CPPAlliance/json/issues/162); while validation worked just fine when a document was parsed without suspending, I neglected to write tests for incremental parsing, and that's precisely where the bug was. Turns out, if parsing suspended while validating a UTF-8 byte sequence, the handler just would not be called. - -This was... quite a problem to say the least, and required me to reimplement UTF-8 validation from scratch -- but with a twist. We don't want to pass partial UTF-8 sequences because it just transfers the burden of assembling incomplete sequences to the handler. This means that we need to store the sequences, append to them until we get a complete codepoint, and only then can we validate and send it off to the handler. Doing this in an efficient manner proved to be quite challenging, so I ended up with a "fix" that was 50% code and 50% `// KRYSTIAN TODO: this can be optimized`. The tests provided in the issue finally passed, so the patch was merged. - -I thought my woes with validation were over, but I was wrong. Just over a week later, a new issue rolled in: - -[Handler not invoked correctly in multi-byte UTF8 sequences, part 2](https://github.com/CPPAlliance/json/issues/162) - -Luckily, fixing this didn't require another rewrite. This taught me a fine lesson in exhaustive testing. diff --git a/_posts/2020-09-29-KrystiansSeptemberUpdate.md b/_posts/2020-09-29-KrystiansSeptemberUpdate.md deleted file mode 100644 index f7acc6475..000000000 --- a/_posts/2020-09-29-KrystiansSeptemberUpdate.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -layout: post -nav-class: dark -categories: krystian -title: Krystian's September Update -author-id: krystian ---- - -# Reviewing the review - -The review period for Boost.JSON has come and gone, and we got some great feedback on the design of the library. Glancing over the results, it appears that the general mood was to accept the library. This doesn't mean that there weren't any problem areas -- most notably the documentation, which often did contain the information people wanted, but it was difficult to find. - -Other points of contention were the use of a push parser as opposed to a pull parser, the use of `double`, `uint64_t`, and `int64_t` without allowing for users to change them, and the value conversion interface. Overall some very good points were made, and I'd like to thank everyone for participating in the review. - -# Customizing the build - -I put a bit of work into improving our CI matrix, as it had several redundant configurations and did not test newer compiler versions (e.g. GCC 10, clang 11), nor did we have any 32-bit jobs. The most difficult thing about working on the build matrix is balancing how exhaustive it is with the turnaround time -- sure, we could add 60 configurations that test x86, x86-64, and ARM on every major compiler version released since 2011, but the turnaround would be abysmal. - -To alleviate this, I only added 32-bit jobs for the sanitizers that use a recent version of GCC. It's a less common configuration in the days of 64-bit universality, and if 64 bit works then it's highly likely that 32 bit will "just work" as well. - - -Here's a table of the new Travis configurations that will be added: - -| Compiler | Library | C++ Standard | Variant | OS | Architecture | Job | -|:-------------:|:---------:|:------------:|:----------:|:--------------:|:------------:|:-----------------:| -| --- | --- | --- | Boost | Linux (Xenial) | x86-64 | Documentation | -| gcc 8.4.0 | libstdc++ | 11 | Boost | Linux (Xenial) | x86-64 | Coverage | -| clang 6.0.1 | libstdc++ | 11, 14 | Boost | Linux (Xenial) | x86-64 | Valgrind | -| clang 11.0.0 | libstdc++ | 17 | Boost | Linux (Xenial) | x86-64 | Address Sanitizer | -| clang 11.0.0 | libstdc++ | 17 | Boost | Linux (Xenial) | x86-64 | UB Sanitizer | -| msvc 14.1 | MS STL | 11, 14, 17 | Boost | Windows | x86-64 | --- | -| msvc 14.1 | MS STL | 17, 2a | Standalone | Windows | x86-64 | --- | -| msvc 14.2 | MS STL | 17, 2a | Boost | Windows | x86-64 | --- | -| msvc 14.2 | MS STL | 17, 2a | Standalone | Windows | x86-64 | --- | -| icc 2021.1 | libstdc++ | 11, 14, 17 | Boost | Linux (Bionic) | x86-64 | --- | -| gcc 4.8.5 | libstdc++ | 11 | Boost | Linux (Trusty) | x86-64 | --- | -| gcc 4.9.4 | libstdc++ | 11 | Boost | Linux (Trusty) | x86-64 | --- | -| gcc 5.5.0 | libstdc++ | 11 | Boost | Linux (Xenial) | x86-64 | --- | -| gcc 6.5.0 | libstdc++ | 11, 14 | Boost | Linux (Xenial) | x86-64 | --- | -| gcc 7.5.0 | libstdc++ | 14, 17 | Boost | Linux (Xenial) | x86-64 | --- | -| gcc 8.4.0 | libstdc++ | 17, 2a | Boost | Linux (Xenial) | x86-64 | --- | -| gcc 9.3.0 | libstdc++ | 17, 2a | Boost | Linux (Xenial) | x86-64 | --- | -| gcc 9.3.0 | libstdc++ | 17, 2a | Standalone | Linux (Xenial) | x86-64 | --- | -| gcc 10.2.0 | libstdc++ | 17, 2a | Boost | Linux (Focal) | x86-64 | --- | -| gcc 10.2.0 | libstdc++ | 17, 2a | Standalone | Linux (Focal) | x86-64 | --- | -| gcc (trunk) | libstdc++ | 17, 2a | Boost | Linux (Focal) | x86-64 | --- | -| gcc (trunk) | libstdc++ | 17, 2a | Standalone | Linux (Focal) | x86-64 | --- | -| clang 3.8.0 | libstdc++ | 11 | Boost | Linux (Trusty) | x86-64 | --- | -| clang 4.0.0 | libstdc++ | 11, 14 | Boost | Linux (Xenial) | x86-64 | --- | -| clang 5.0.2 | libstdc++ | 11, 14 | Boost | Linux (Xenial) | x86-64 | --- | -| clang 6.0.1 | libstdc++ | 14, 17 | Boost | Linux (Xenial) | x86-64 | --- | -| clang 7.0.1 | libstdc++ | 17, 2a | Boost | Linux (Xenial) | x86-64 | --- | -| clang 9.0.1 | libstdc++ | 17, 2a | Boost | Linux (Xenial) | x86-64 | --- | -| clang 9.0.1 | libstdc++ | 17, 2a | Standalone | Linux (Xenial) | x86-64 | --- | -| clang 10.0.1 | libstdc++ | 17, 2a | Boost | Linux (Xenial) | x86-64 | --- | -| clang 10.0.1 | libstdc++ | 17, 2a | Standalone | Linux (Xenial) | x86-64 | --- | -| clang 11.0.0 | libstdc++ | 17, 2a | Boost | Linux (Xenial) | x86-64 | --- | -| clang 11.0.0 | libstdc++ | 17, 2a | Standalone | Linux (Xenial) | x86-64 | --- | -| clang (trunk) | libstdc++ | 17, 2a | Boost | Linux (Xenial) | x86-64 | --- | -| clang (trunk) | libstdc++ | 17, 2a | Standalone | Linux (Xenial) | x86-64 | --- | - -I think it strikes a good balance between exhaustiveness and turnaround time, and we now test the most recent compiler versions to make sure they won't cause problems on the cutting edge. - -# Binary size - -It doesn't matter how good a library is if it's too big to use within your environment. As with all things in computer science, there is a trade-off between size and speed; seldom can you have both. We have been exploring options to reduce the size of the binary, and this mostly involved removing a lot of the pre-written tables we have (such as the ever-controversial jump table), since it allows the compiler to take into account the specific options it was past and optimize for those constraints (i.e. size and speed) rather than hard-coding in a set configuration as we did with the jump tables. - -Peter Dimov also helped out by transitioning our compile-time system of generating unique parse functions for each permutation of extensions to a runtime system, which drastically decreases the binary size without affecting performance too much. - -I must admit I'm not the biggest fan of these changes, but it's important to support the use of Boost.JSON in embedded environments. As Peter has said time and time again: don't overfit for a particular use-case or configuration. - -Another place with room for improvement is with string to float-point conversions. Right now we calculate a mantissa and base-10 exponent, then lookup the value in a massive table that contains pre-calculated powers of 10 from 1e-308 to 1e+308. As you can surmise, this takes up a substantial amount of space (8 bytes * 618 elements = 4.95 kb). - -Here is a boiled down version of how we currently perform the conversion: - -```cpp -double calculate_float( - std::uint64_t mantissa, - std::uint32_t exponent, - bool sign) -{ - constexpr static double table[618] = - { - 1e-308, 1e-307, - ..., - 1e307, 1e308 - }; - double power; - if(exponent < -308 || exponent > 308) - power = std::pow(10.0, exponent); - else - power = table[exponent + 308] - double result = mantissa * power; - return sign ? -result : result; -} -``` - -To further reduce the size of the binary, Peter suggested that we instead calculate `power` as `10^floor(exponent / 8) * 10^(exponent mod 8)`. Yes, the division operations there might look expensive, but any decent optimizing compiler will transform `exponent / 8` to `exponent >> 3`, and `exponent mod 8` to `exponent & 7`. This does introduce another multiplication instruction, but at the same time, it makes our table 8 times smaller. In theory, the slight drop in performance is worth the significant reduction in binary size. - \ No newline at end of file diff --git a/_posts/2020-09-30-RichardsSeptemberUpdate.md b/_posts/2020-09-30-RichardsSeptemberUpdate.md deleted file mode 100644 index d6a64cde6..000000000 --- a/_posts/2020-09-30-RichardsSeptemberUpdate.md +++ /dev/null @@ -1,608 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's September Update -author-id: richard ---- - -# Cancellation in Beast/Asio and Better Compile Performance with Beast.Websocket - -This month I will be discussing two issues. One of interest to many people who come to us with questions on the -[Github Issue Tracker](https://github.com/boostorg/beast/issues) and the #beast channel of -[Cpplang Slack](https://cppalliance.org/slack/). - -## Compile Times and Separation of Concerns - -A common complaint about Boost.Beast is that compilation units that use the `websocket::stream` template class -often take a long time to compile, and that because websocket::stream is a template, this compilation overhead can -become viral in an application. - -This is a valid complaint and we believe there are some reasonable tradeoffs we can make by refactoring the websocket -stream to use fewer templates internally. Vinnie has started work to express the WebSocket's -intermediate completion handlers, buffer sequence and executor in terms of a polymorphic object. This would mean a -few indirect jumps in the compiled code but would significantly reduce the number of internal template expansions. -In the scheme of things, we don't believe that the virtual function calls will materially affect runtime performance. -The branch is [here](https://github.com/vinniefalco/beast/tree/async-an) - -I will be continuing work in this area in the coming days. - -In the meantime, our general response is to suggest that users create a base class to handle the transport, and -communicate important events such as frame received, connection state and the close notification to a derived -application-layer class through a private polymorphic interface. - -In this way, the websocket transport compilation unit may take a while to compile, but it needs to be done only once -since the transport layer will rarely change during the development life of an application. Whenever there is a change -to the application layer, the transport layer is not affected so websocket-related code is not affected. - -This approach has a number of benefits. Not least of which is that developing another client implementation over -a different websocket connection in the same application becomes trivial. - -Another benefit is that the application can be designed such that application-level concerns are agnostic of the -transport mechanism. Such as when the server can be accessed by multiple means - WSS, WS, long poll, direct connection, -unix sockets and so on. - -In this blog I will present a simplified implementation of this idea. My thanks to the cpplang Slack user `@elegracer` -who most recently asked for guidance on reducing compile times. It was (his/her? Slack is silent on the matter) question -which prompted me to finally conjure up a demo. `@elegracer`'s problem was needing to connect to multiple cryptocurrency -exchanges in the same app over websocket. In this particular example I'll demonstrate a simplified connection to -the public FMex market data feed since that was the subject of the original question. - -## Correct Cancellation - -Our examples in the Beast Repository are rudimentary and don't cover the issue of graceful shutdown of an application -in response to a SIGINT (i.e. the user pressing ctrl-c). It is common for simple programs to exit suddenly in response -to this signal, which is the default behaviour. For many applications, this is perfectly fine but not all. We may want -active objects in the program to write data to disk, we may want to ensure that the underlying websocket is -shut down cleanly and we may want to give the user an opportunity to prevent the shutdown. - -I will further annotate the example by providing this ability to prevent the shutdown. The user will have to confirm the -first SIGINT with another within 5 seconds to confirm. - -# Designing the application - -When I write IO applications involving Asio and Beast, I prefer to create an "application" object. This has the -responsibility of monitoring signals and starting the initial connection objects. It also provides the communication -between the two. - -The construction and configuration of the `io_context` and `ssl::context` stay in `main()`. The executor and ssl context -are passed to the application by reference as dependencies. The application can then pass on these refrences as -required. It is also worth mentioning that I don't pass the io_context's executor as a polymorphic `any_io_executor` -type at this stage. The reason is that I may want in future to upgrade my program to be multi-threaded. If I do this, -then each individual io_enabled object such as a connection or the application will need to have its _own_ strand. -Getting the strand out of an any_io_executor is not possible in the general case as it will have been type-erased, so -for top level objects I pass the executor as `io_context::executor_type`. It is then up to each object to create its own -strand internally which will have the type `strand`. The `strand` type provides the method -`get_inner_executor` which allows the application to extract the underlying `io_context::executor_type` and pass it to -the constructor of any subordinate but otherwise self-contained io objects. The subordinates can then build their own -strands from this. - -## Step 1 - A Simple Application Framework That Supports ctrl-c - -OK, let's get started and build the framework. Here's a link to -[step 1](https://github.com/test-scenarios/boost_beast_websocket_echo/tree/blog-2020-09-step-1/pre-cxx20/blog-2020-09). - -`ssl.hpp` and `net.hpp` simply configure the project to use boost.asio. The idea of these little configuration headers -is that they could be generated by the cmake project if necessary to allow the option of upgrading to std networking -if it ever arrives. - -As a matter of style, I like to ensure that no names are created in the global namespace other than `main`. This saves -headaches that could occur if I wrote code on one platform, but then happened to port it to another where the name -was already in use by the native system libraries. - -`main.cpp` simply creates the io execution context and a default ssl context, creates the application, starts it and -runs the io context. - -At the moment, the only interesting part of our program is the `signit_state`. This is a state machine which handles the -behaviour of the program when a `SIGINT` is received. Our state machine is doing something a little fancy. Here is the -state diagram: - -![sigint_state](/images/posts/richard/2020-09-sigint-state.png) - -Rather than reproduce the code here, please refer to -[step 1](https://github.com/test-scenarios/boost_beast_websocket_echo/tree/blog-2020-09-step-1/pre-cxx20/blog-2020-09) -to see the source code. - -At this point the program will run and successfully handle ctrl-c: - -``` -$ ./blog_2020_09 -Application starting -Press ctrl-c to interrupt. -^CInterrupt detected. Press ctrl-c again within 5 seconds to exit -Interrupt unconfirmed. Ignoring -^CInterrupt detected. Press ctrl-c again within 5 seconds to exit -^CInterrupt confirmed. Shutting down -``` - -## Step 2 - Connecting to an Exchange - -Now we need to create our WebSocket transport class and our FMex exchange protocol class that will derive from it. -For now we won't worry about cancellation - we'll retrofit that in Step 3. - -Here is the code for -[step 2](https://github.com/test-scenarios/boost_beast_websocket_echo/tree/blog-2020-09-step-2/pre-cxx20/blog-2020-09). - -This section introduces two new main classes - the `wss_transport` and the `fmex_connection`. In addition, the connection -phase of the wss_transport is expressed as a composed operation for exposition purposes (and in my opinion it actually -makes the code easier to read than continuation-passing style code) - -Here is the implementation of the connect coroutine: - -```cpp - struct wss_transport::connect_op : asio::coroutine - { - using executor_type = wss_transport::executor_type; - using websock = wss_transport::websock; -``` -Here we define the _implementation_ of the coroutine - this is an object which will not be moved for the duration of the -execution of the coroutine. This address stability is important because intermediate asynchronous operations will rely -on knowing the address of the resolver (and later perhaps other io objects). -```cpp - struct impl_data - { - impl_data(websock & ws, - std::string host, - std::string port, - std::string target) - : ws(ws) - , resolver(ws.get_executor()) - , host(host) - , port(port) - , target(target) - { - } - - layer_0 & - tcp_layer() const - { - return ws.next_layer().next_layer(); - } - - layer_1 & - ssl_layer() const - { - return ws.next_layer(); - } - - websock & ws; - net::ip::tcp::resolver resolver; - net::ip::tcp::resolver::results_type endpoints; - std::string host, port, target; - }; -``` -The constructor merely forwards the arguments to the construction of the `impl_data`. -```cpp - connect_op(websock & ws, - std::string host, - std::string port, - std::string target) - : impl_(std::make_unique< impl_data >(ws, host, port, target)) - { - } - -``` -This coroutine is both a composed operation and a completion handler for sub-operations. This means it must have an -`operator()` interface matching the requirements of each sub-operation. During the lifetime of this coroutine we -will be using the resolver and calling `async_connect` on the `tcp_stream`. We therefore provide conforming member -functions which store or ignore the and forward the `error_code` to the main implementation of the coroutine. -```cpp - template < class Self > - void - operator()(Self & self, - error_code ec, - net::ip::tcp::resolver::results_type results) - { - impl_->endpoints = results; - (*this)(self, ec); - } - - template < class Self > - void - operator()(Self &self, error_code ec, net::ip::tcp::endpoint const &) - { - (*this)(self, ec); - } -``` -Here is the main implementation of the coroutine. Note that the last two parameters provide defaults. This is in order -to allow this member function to match the completion handler signatures of: -* `void()` - invoked during async_compose in order to start the coroutine. -* `void(error_code)` - invoked by the two functions above and by the async handshakes. -* `void(error_code, std::size_t)` - invoked by operations such as async_read and async_write although not strictly -necessary here. -```cpp - template < class Self > - void operator()(Self &self, error_code ec = {}, std::size_t = 0) - { -``` -Note that here we are checking the error code before re-entering the coroutine. This is a shortcut which allows us to -omit error checking after each sub-operation. This check will happen on every attempt to re-enter the coroutine, -including the first entry (at which time `ec` is guaranteed to be default constructed). -```cpp - if (ec) - return self.complete(ec); - - auto &impl = *impl_; -``` -Note the use of the asio yield and unyield headers to create the fake 'keywords' `reenter` and `yield` in avery limited -scope. -```cpp -#include - reenter(*this) - { - yield impl.resolver.async_resolve( - impl.host, impl.port, std::move(self)); - - impl.tcp_layer().expires_after(15s); - yield impl.tcp_layer().async_connect(impl.endpoints, - std::move(self)); - - if (!SSL_set_tlsext_host_name(impl.ssl_layer().native_handle(), - impl.host.c_str())) - return self.complete( - error_code(static_cast< int >(::ERR_get_error()), - net::error::get_ssl_category())); - - impl.tcp_layer().expires_after(15s); - yield impl.ssl_layer().async_handshake(ssl::stream_base::client, - std::move(self)); - - impl.tcp_layer().expires_after(15s); - yield impl.ws.async_handshake( - impl.host, impl.target, std::move(self)); -``` -If the coroutine is re-entered here, it must be because there was no error (if there was an error, it would have been -caught by the pre-reentry error check above). Since execution has resumed here in the completion handler of the -`async_handshake` initiating function, we are guaranteed to be executing in the correct executor. Therefore we can -simply call `complete` directly without needing to post to an executor. Note that the `async_compose` call which will -encapsulate the use of this class embeds this object into a wrapper which provides the `executor_type` and -`get_executor()` mechanism which asio uses to determine on which executor to invoke completion handlers. -```cpp - impl.tcp_layer().expires_never(); - yield self.complete(ec); - } -#include - } - - std::unique_ptr< impl_data > impl_; - }; -``` - -The `wss_connection` class provides the bare bones required to connect a websocket and maintain the connection. It -provides a protected interface so that derived classes can send text frames and it will call private virtual functions -in order to notify the derived class of: -* transport up (websocket connection established). -* frame received. -* connection error (either during connection or operation). -* websocket close - the server has requested or agreed to a graceful shutdown. - -Connection errors will only be notified once, and once a connection error has been indicated, no other event will reach -the derived class. - -One of the many areas that trips up asio/beast beginners is that care must be taken to ensure that only one `async_write` -is in progress at a time on the WebSocket (or indeed any async io object). For this reason we implement a simple -transmit queue state which can be considered to be an orthogonal region (parallel task) to the read state. - -```cpp - // send_state - data to control sending data - - std::deque send_queue_; - enum send_state - { - not_sending, - sending - } send_state_ = not_sending; -``` - -You will note that I have used a `std::deque` to hold the pending messages. Although a deque has theoretically better -complexity when inserting or removing items at the ends than a vector, this is not the reason for choosing this data -structure. The actual reason is that items in a deque are guaranteed to have a stable address, even when other items -are added or removed. This is useful as it means we don't have to move frames out of the transmit queue in order to -send them. Remember that during an `async_write`, the data to which the supplied buffer sequence refers must have a -stable address. - -Here are the functions that deal with the send state transitions. -```cpp - void - wss_transport::send_text_frame(std::string frame) - { - if (state_ != connected) - return; - - send_queue_.push_back(std::move(frame)); - start_sending(); - } - - void - wss_transport::start_sending() - { - if (state_ == connected && send_state_ == not_sending && - !send_queue_.empty()) - { - send_state_ = sending; - websock_.async_write(net::buffer(send_queue_.front()), - [this](error_code const &ec, std::size_t bt) { - handle_send(ec, bt); - }); - } - } - - void - wss_transport::handle_send(const error_code &ec, std::size_t) - { - send_state_ = not_sending; - - send_queue_.pop_front(); - - if (ec) - event_transport_error(ec); - else - start_sending(); - } -``` - -Finally, we can implement our specific exchange protocol on top of the `wss_connection`. In this case, FMex eschews -the ping/pong built into websockets and requires a json ping/pong to be initiated by the client. - -```cpp - void - fmex_connection::ping_enter_state() - { - BOOST_ASSERT(ping_state_ == ping_not_started); - ping_enter_wait(); - } - - void - fmex_connection::ping_enter_wait() - { - ping_state_ = ping_wait; - - ping_timer_.expires_after(5s); - - ping_timer_.async_wait([this](error_code const &ec) { - if (!ec) - ping_event_timeout(); - }); - } - - void - fmex_connection::ping_event_timeout() - { - ping_state_ = ping_waiting_pong; - - auto frame = json::value(); - auto &o = frame.emplace_object(); - o["cmd"] = "ping"; - o["id"] = "my_ping_ident"; - o["args"].emplace_array().push_back(timestamp()); - send_text_frame(json::serialize(frame)); - } - - void - fmex_connection::ping_event_pong(json::value const &frame) - { - ping_enter_wait(); - } -``` - -Note that since we have implemented frame transmission in the base class in terms of a queue, the fmex class has no -need to worry about ensuring the one-write-at-a-time rule. The base class handles it. This makes the application -developer's life easy. - -Finally, we implement `on_text_frame` and write a little message parser and switch. Note that this function may throw. -The base class will catch any exceptions thrown here and ensure that the `on_transport_error` event will be called at -the appropriate time. Thus again, the application developer's life is improved as he doesn't need to worry about -handling exceptions in an asynchronous environment. -```cpp - void - fmex_connection::on_text_frame(std::string_view frame) - try - { - auto jframe = - json::parse(json::string_view(frame.data(), frame.size())); - - // dispatch on frame type - - auto &type = jframe.as_object().at("type"); - if (type == "hello") - { - on_hello(); - } - else if (type == "ping") - { - ping_event_pong(jframe); - } - else if (type.as_string().starts_with("ticker.")) - { - fmt::print(stdout, - "fmex: tick {} : {}\n", - type.as_string().subview(7), - jframe.as_object().at("ticker")); - } - } - catch (...) - { - fmt::print(stderr, "text frame is not json : {}\n", frame); - throw; - } -``` - -Compiling and running the program produces output similar to this: - -``` -Application starting -Press ctrl-c to interrupt. -fmex: initiating connection -fmex: transport up -fmex: hello -fmex: tick btcusd_p : [1.0879E4,1.407E3,1.0879E4,2.28836E5,1.08795E4,1.13E2,1.0701E4,1.0939E4,1.0663E4,2.51888975E8,2.3378048830533768E4] -fmex: tick btcusd_p : [1.08795E4,1E0,1.0879E4,3.79531E5,1.08795E4,3.518E3,1.0701E4,1.0939E4,1.0663E4,2.51888976E8,2.3378048922449758E4] -fmex: tick btcusd_p : [1.0879E4,2E0,1.0879E4,3.7747E5,1.08795E4,7.575E3,1.0701E4,1.0939E4,1.0663E4,2.51888978E8,2.3378049106290182E4] -fmex: tick btcusd_p : [1.0879E4,2E0,1.0879E4,3.77468E5,1.08795E4,9.229E3,1.0701E4,1.0939E4,1.0663E4,2.5188898E8,2.337804929013061E4] -fmex: tick btcusd_p : [1.0879E4,1E0,1.0879E4,1.0039E4,1.08795E4,2.54203E5,1.0701E4,1.0939E4,1.0663E4,2.51888981E8,2.3378049382050827E4] -``` - -Note however, that although pressing ctrl-c is noticed by the application, the fmex feed does not shut down in response. -This is because we have not wired up a mechanism to communicate the `stop()` event to the implementation of the -connection: - -``` -$ ./blog_2020_09 -Application starting -Press ctrl-c to interrupt. -fmex: initiating connection -fmex: transport up -fmex: hello -fmex: tick btcusd_p : [1.0859E4,1E0,1.0859E4,6.8663E4,1.08595E4,4.1457E4,1.07125E4,1.0939E4,1.0667E4,2.58585817E8,2.3968266005011003E4] -^CInterrupt detected. Press ctrl-c again within 5 seconds to exit -fmex: tick btcusd_p : [1.08595E4,2E0,1.0859E4,5.9942E4,1.08595E4,4.3727E4,1.07125E4,1.0939E4,1.0667E4,2.58585819E8,2.3968266189181537E4] -^CInterrupt confirmed. Shutting down -fmex: tick btcusd_p : [1.08595E4,2E0,1.0859E4,5.9932E4,1.08595E4,4.0933E4,1.07125E4,1.0939E4,1.0667E4,2.58585821E8,2.396826637335208E4] -fmex: tick btcusd_p : [1.0859E4,1E0,1.0859E4,6.2722E4,1.08595E4,4.0943E4,1.07125E4,1.0939E4,1.0667E4,2.58585823E8,2.3968266557531104E4] -fmex: tick btcusd_p : [1.08595E4,1.58E2,1.0859E4,6.2732E4,1.08595E4,3.7953E4,1.07125E4,1.0939E4,1.0667E4,2.58585981E8,2.3968281107003917E4] -^Z -[1]+ Stopped ./blog_2020_09 -$ kill %1 - -[1]+ Stopped ./blog_2020_09 -$ -[1]+ Terminated ./blog_2020_09 -``` - -## Step 3 - Re-Enabling Cancellation - -You will remember from step 1 that we created a little class called `sigint_state` which notices that the application -has received a sigint and checks for a confirming sigint before taking action. We also added a slot to this to pass the -signal to the fmex connection: - -```cpp - fmex_connection_.start(); - sigint_state_.add_slot([this]{ - fmex_connection_.stop(); - }); -``` - -But we didn't put any code in `wss_transport::stop`. Now all we have to do is provide a function object within -`wss_transport` that we can adjust whenever the current state changes: - -```cpp - // stop signal - std::function stop_signal_; -``` - -```cpp - void - wss_transport::stop() - { - net::dispatch(get_executor(), [this] { - if (auto sig = boost::exchange(stop_signal_, nullptr)) - sig(); - }); - } -``` - -We will also need to provide a way for the connect operation to respond to the stop signal (the user might press -ctrl-c while resolving for example). - -The way I have done this here is a simple approach, merely pass a reference to the `wss_transport` into the composed -operation so that the operation can modify the function directly. There are other more scalable ways to do this, but -this is good enough for now. - -The body of the coroutine then becomes: - -```cpp - auto &impl = *impl_; - - if(ec) - impl.error = ec; - - if (impl.error) - return self.complete(impl.error); - -#include - reenter(*this) - { - transport_->stop_signal_ = [&impl] { - impl.resolver.cancel(); - impl.error = net::error::operation_aborted; - }; - yield impl.resolver.async_resolve( - impl.host, impl.port, std::move(self)); - - // - - transport_->stop_signal_ = [&impl] { - impl.tcp_layer().cancel(); - impl.error = net::error::operation_aborted; - }; - - impl.tcp_layer().expires_after(15s); - yield impl.tcp_layer().async_connect(impl.endpoints, - std::move(self)); - - // - - if (!SSL_set_tlsext_host_name(impl.ssl_layer().native_handle(), - impl.host.c_str())) - return self.complete( - error_code(static_cast< int >(::ERR_get_error()), - net::error::get_ssl_category())); - - // - - impl.tcp_layer().expires_after(15s); - yield impl.ssl_layer().async_handshake(ssl::stream_base::client, - std::move(self)); - - // - - impl.tcp_layer().expires_after(15s); - yield impl.ws.async_handshake( - impl.host, impl.target, std::move(self)); - - // - - transport_->stop_signal_ = nullptr; - impl.tcp_layer().expires_never(); - yield self.complete(impl.error); - } -#include -``` - -The final source code for -[step 3 is here](https://github.com/test-scenarios/boost_beast_websocket_echo/tree/blog-2020-09-step-3/pre-cxx20/blog-2020-09). - -Stopping the program while connecting: - -``` -$ ./blog_2020_09 -Application starting -Press ctrl-c to interrupt. -fmex: initiating connection -^CInterrupt detected. Press ctrl-c again within 5 seconds to exit -^CInterrupt confirmed. Shutting down -fmex: transport error : system : 125 : Operation canceled -``` - -And stopping the program while connected: - -``` -$ ./blog_2020_09 -Application starting -Press ctrl-c to interrupt. -fmex: initiating connection -fmex: transport up -fmex: hello -^CInterrupt detected. Press ctrl-c again within 5 seconds to exit -fmex: tick btcusd_p : [1.0882E4,1E0,1.0882E4,3.75594E5,1.08825E4,5.103E3,1.07295E4,1.0939E4,1.06785E4,2.58278146E8,2.3907706652603207E4] -^CInterrupt confirmed. Shutting down -closing websocket -fmex: closed -``` - -# Future development - -Next month I'll refactor the application to use C++20 coroutines and we can see whether this makes developing -event based systems easier and/or more maintainable. - -Thanks for reading. diff --git a/_posts/2020-1-30-Gold-sponsor-of-C++-now.md b/_posts/2020-1-30-Gold-sponsor-of-C++-now.md deleted file mode 100644 index 862bbdde9..000000000 --- a/_posts/2020-1-30-Gold-sponsor-of-C++-now.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: post -nav-class: dark -categories: company, louis -author-id: louis -title: Gold sponsor of C++Now 2020 ---- -The Alliance is a Gold sponsor for -C++Now 2020. This -conference is a gathering of C++ experts and enthusiasts from around -the world in beautiful Aspen, Colorado from May 3, 2020 - May 8, 2020. diff --git a/_posts/2020-10-31-RichardsOctoberUpdate.md b/_posts/2020-10-31-RichardsOctoberUpdate.md deleted file mode 100644 index 8acf62c4e..000000000 --- a/_posts/2020-10-31-RichardsOctoberUpdate.md +++ /dev/null @@ -1,906 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's October Update -author-id: richard ---- - -# Asio Coroutines in Qt applications! - -I started this train of thought when I wanted to hook up some back-end style code that I had written to a gui front end. -One way to do this would be to have a web front end subscribing to a back-end service, but I am no expert in modern web -technologies so rather than spend time learning something that wasn't C++ I decided to reach for the -popular-but-so-far-unused-by-me C++ GUI framework, Qt. - -The challenge was how to hook up Qt, which is an event driven framework to a service written with Asio C++ coroutines. - -In the end it turned out to be easier than I had expected. Here's how. - -## A simple Executor - -As mentioned in a previous blog, Asio comes with a full implementation of the -[Unified Executors proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0443r12.html). Asio coroutines -are designed to be initiated and continued within an executor's execution context. So let's build an executor that will -perform work in a Qt UI thread. - -The executor I am going to build will have to invoke completion handlers to Asio IO objects, so we need to make it -compatible with `asio::any_io_executor`. This means it needs to have an associated -[execution context](https://www.boost.org/doc/libs/1_74_0/doc/html/boost_asio/reference/execution_context.html). - -The execution context is going to ultimately perform work on a Qt Application, so it makes sense to capture a reference -to the Application. Although Qt defines the macro `qApp` to resolve to a pointer to the "current" application, for -testing and sanity purposes I prefer that all services I write allow dependency injection, so I'll arrange things so -that the execution_context's constructor takes an optional pointer to an application. In addition, it will be convenient -when writing components to not have to specifically create and pass an an execution context to windows within the Qt -application so it makes sense to be able to provide access to a default context which references the default application. -Here's a first cut: - -```cpp -struct qt_execution_context : net::execution_context - , boost::noncopyable -{ - qt_execution_context(QApplication *app = qApp) - : app_(app) - { - instance_ = this; - } - - template - void - post(F f) - { - // todo - } - - static qt_execution_context & - singleton() - { - assert(instance_); - return *instance_; - } - -private: - static qt_execution_context *instance_; - QApplication *app_; -}; -``` - -This class will provide two services. The first is to provide the asio service infrastructure so that we can create -timers, sockets etc that use executors associated with this context and the second is to allow the executor to actually -dispatch work in a Qt application. This is the purpose of the `post` method. - -Now a Qt application is itself a kind of execution context - in that it dispatches QEvent objects to be handled by -children of the application. We can use this infrastructure to ensure that work dispatched by this execution context -actually takes place on the correct thread and at the correct time. - -In order for us to dispatch work to the application, we need to wrap our function into a QEvent: - -```cpp -class qt_work_event_base : public QEvent -{ -public: - qt_work_event_base() - : QEvent(generated_type()) - { - } - - virtual void - invoke() = 0; - - static QEvent::Type - generated_type() - { - static int event_type = QEvent::registerEventType(); - return static_cast(event_type); - } -}; - -template -struct basic_qt_work_event : qt_work_event_base -{ - basic_qt_work_event(F f) - : f_(std::move(f)) - {} - - void - invoke() override - { - f_(); - } - -private: - F f_; -}; -``` -As opposed to using a `std::function`, the `basic_qt_work_event` allows us to wrap a move-only function object, which is -important when that object is actually an Asio completion handler. Completion handlers benefit from being move-only as -it means they can carry move-only state. This makes them more versatile, and can often lead to improvements in -execution performance. - -Now we just need to fill out the code for `qt_execution_context::post` and provide a mechanism in the Qt application to -detect and dispatch these messages: - -```cpp - template - void - post(F f) - { - // c++20 auto template deduction - auto event = new basic_qt_work_event(std::move(f)); - QApplication::postEvent(app_, event); - } -``` - -```cpp -class qt_net_application : public QApplication -{ - using QApplication::QApplication; - -protected: - bool - event(QEvent *event) override; -}; - -bool -qt_net_application::event(QEvent *event) -{ - if (event->type() == qt_work_event_base::generated_type()) - { - auto p = static_cast(event); - p->accept(); - p->invoke(); - return true; - } - else - { - return QApplication::event(event); - } -} -``` - -Note that I have seen on stack overflow the technique of invoking a function object in the destructor of the -`QEvent`-derived event. This would mean no necessity of custom event handling in the `QApplication` but there are two -problems that I can see with this approach: -1. I don't know enough about Qt to know that this is safe and correct, and -2. Executors-TS executors can be destroyed while there are still un-invoked handlers within them. The correct behaviour -is to destroy these handlers without invoking them. If we put invocation code in the destructors, they will actually -mass-invoke when the executor is destroyed, leading most probably to annihilation of our program by segfault. - -However, that being done, we can now write the executor to meet the minimal expectations of an asio executor which can -be used in an `any_io_executor`. - -```cpp -struct qt_executor -{ - qt_executor(qt_execution_context &context = qt_execution_context::singleton()) noexcept - : context_(std::addressof(context)) - { - } - - qt_execution_context &query(net::execution::context_t) const noexcept - { - return *context_; - } - - static constexpr net::execution::blocking_t - query(net::execution::blocking_t) noexcept - { - return net::execution::blocking.never; - } - - static constexpr net::execution::relationship_t - query(net::execution::relationship_t) noexcept - { - return net::execution::relationship.fork; - } - - static constexpr net::execution::outstanding_work_t - query(net::execution::outstanding_work_t) noexcept - { - return net::execution::outstanding_work.tracked; - } - - template < typename OtherAllocator > - static constexpr auto query( - net::execution::allocator_t< OtherAllocator >) noexcept - { - return std::allocator(); - } - - static constexpr auto - query(net::execution::allocator_t< void >) noexcept - { - return std::allocator(); - } - - template - void - execute(F f) const - { - context_->post(std::move(f)); - } - - bool - operator==(qt_executor const &other) const noexcept - { - return context_ == other.context_; - } - - bool - operator!=(qt_executor const &other) const noexcept - { - return !(*this == other); - } - -private: - qt_execution_context *context_; -}; - - -static_assert(net::execution::is_executor_v); -``` - -Now all that remains is to write a subclass of some Qt Widget so that we can dispatch some work against it. - -```cpp -class test_widget : public QTextEdit -{ - Q_OBJECT -public: - using QTextEdit::QTextEdit; - -private: - void - showEvent(QShowEvent *event) override; - - void - hideEvent(QHideEvent *event) override; - - net::awaitable - run_demo(); -}; - -void -test_widget::showEvent(QShowEvent *event) -{ - net::co_spawn( - qt_executor(), [this] { - return run_demo(); - }, - net::detached); - - QTextEdit::showEvent(event); -} - -void -test_widget::hideEvent(QHideEvent *event) -{ - QWidget::hideEvent(event); -} - -net::awaitable -test_widget::run_demo() -{ - using namespace std::literals; - - auto timer = net::high_resolution_timer(co_await net::this_coro::executor); - - for (int i = 0; i < 10; ++i) - { - timer.expires_after(1s); - co_await timer.async_wait(net::use_awaitable); - this->setText(QString::fromStdString(std::to_string(i + 1) + " seconds")); - } - co_return; -} - -``` - -Here is the code for [stage 1](https://github.com/madmongo1/blog-october-2020/tree/stage-1) - -And here is a screenshot of the app running: - -![app running](/images/posts/richard/2020-october/stage-1.png) - -## All very well... - -OK, so we have a coroutine running in a Qt application. This is nice because it allows us to express an event-driven -system in terms of procedural expression of code in a coroutine. - -But what if the user closes the window before the coroutine completes? - -This application has created the window on the stack, but in a larger application, there will be multiple windows and -they may open and close at any time. It is not unusual in Qt to delete a closed window. If the coroutine continues to -run once the windows that's hosting it is deleted, we are sure to get a segfault. - -One answer to this is to maintain a sentinel in the Qt widget implementation, which prevents the continuation of the -coroutine if destroyed. A `std::shared_ptr/weak_ptr` pair would seem like a sensible solution. Let's create an updated -version of the executor: - -```cpp -struct qt_guarded_executor -{ - qt_guarded_executor(std::weak_ptr guard, - qt_execution_context &context - = qt_execution_context::singleton()) noexcept - : context_(std::addressof(context)) - , guard_(std::move(guard)) - {} - - qt_execution_context &query(net::execution::context_t) const noexcept - { - return *context_; - } - - static constexpr net::execution::blocking_t - query(net::execution::blocking_t) noexcept - { - return net::execution::blocking.never; - } - - static constexpr net::execution::relationship_t - query(net::execution::relationship_t) noexcept - { - return net::execution::relationship.fork; - } - - static constexpr net::execution::outstanding_work_t - query(net::execution::outstanding_work_t) noexcept - { - return net::execution::outstanding_work.tracked; - } - - template - static constexpr auto - query(net::execution::allocator_t) noexcept - { - return std::allocator(); - } - - static constexpr auto query(net::execution::allocator_t) noexcept - { - return std::allocator(); - } - - template - void - execute(F f) const - { - if (auto lock1 = guard_.lock()) - { - context_->post([guard = guard_, f = std::move(f)]() mutable { - if (auto lock2 = guard.lock()) - f(); - }); - } - } - - bool - operator==(qt_guarded_executor const &other) const noexcept - { - return context_ == other.context_ && !guard_.owner_before(other.guard_) - && !other.guard_.owner_before(guard_); - } - - bool - operator!=(qt_guarded_executor const &other) const noexcept - { - return !(*this == other); - } - -private: - qt_execution_context *context_; - std::weak_ptr guard_; -}; -``` - -Now we'll make a little boilerplate class that we can use as a base class in any executor-enabled object in Qt: - -```cpp -struct has_guarded_executor -{ - using executor_type = qt_guarded_executor; - - has_guarded_executor(qt_execution_context &ctx - = qt_execution_context::singleton()) - : context_(std::addressof(ctx)) - { - new_guard(); - } - - void - new_guard() - { - static int x = 0; - guard_ = std::shared_ptr(std::addressof(x), - // no-op deleter - [](auto *) {}); - } - - void - reset_guard() - { - guard_.reset(); - } - - executor_type - get_executor() const - { - return qt_guarded_executor(guard_, *context_); - } - -private: - qt_execution_context *context_; - std::shared_ptr guard_; -}; -``` - -And we can modify the `test_widget` to use it: - -```cpp -class test_widget - : public QTextEdit - , public has_guarded_executor -{ - ... -}; - -void -test_widget::showEvent(QShowEvent *event) -{ - // stop all existing coroutines and create a new guard - new_guard(); - - // start our coroutine - net::co_spawn( - get_executor(), [this] { return run_demo(); }, net::detached); - - QTextEdit::showEvent(event); -} - -void -test_widget::hideEvent(QHideEvent *event) -{ - // stop all coroutines - reset_guard(); - QWidget::hideEvent(event); -} -``` - -Now we'll update the application to allow the creation and deletion of our widget. For this I'll use the QMdiWindow -and add a menu with an action to create new widgets. - -We are now able to create and destroy widgets at will, with no segfaults. - -![MDI app running](/images/posts/richard/2020-october/stage-2.png) - -If you look at the code, you'll also see that I've wired up a rudimentary signal/slot device to allow the coroutine to -be cancelled early. - -```cpp - // test_widget.hpp - - void - listen_for_stop(std::function slot); - - void - stop_all(); - - std::vector> stop_signals_; - bool stopped_ = false; - - // test_widget.cpp - - void - test_widget::listen_for_stop(std::function slot) - { - if (stopped_) - return slot(); - - stop_signals_.push_back(std::move(slot)); - } - - void - test_widget::stop_all() - { - stopped_ = true; - auto copy = std::exchange(stop_signals_, {}); - for (auto &slot : copy) slot(); - } - - void - test_widget::closeEvent(QCloseEvent *event) - { - stop_all(); - QWidget::closeEvent(event); - } - - net::awaitable - test_widget::run_demo() - { - using namespace std::literals; - - auto timer = net::high_resolution_timer(co_await net::this_coro::executor); - - auto done = false; - - listen_for_stop([&] { - done = true; - timer.cancel(); - }); - - while (!done) - { - for (int i = 0; i < 10; ++i) - { - timer.expires_after(1s); - auto ec = boost::system::error_code(); - co_await timer.async_wait( - net::redirect_error(net::use_awaitable, ec)); - if (ec) - { - done = true; - break; - } - this->setText( - QString::fromStdString(std::to_string(i + 1) + " seconds")); - } - - for (int i = 10; i--;) - { - timer.expires_after(250ms); - auto ec = boost::system::error_code(); - co_await timer.async_wait( - net::redirect_error(net::use_awaitable, ec)); - if (ec) - { - done = true; - break; - } - this->setText(QString::fromStdString(std::to_string(i))); - } - } - co_return; - } - -``` - -Apparently I am told that it's been a long-believed myth that Asio "doesn't do cancellation". This is of course, -nonsense. - -Here's the code for [stage 2](https://github.com/madmongo1/blog-october-2020/tree/stage-2) - -## State of the Art - -It's worth mentioning that I wrote and tested this demo using clang-9 and the libc++ version of the standard library. -I have also successfully tested clang-11 with coroutines (and concepts). As I understand it, recent versions of -Visual Studio support both well. GCC 10 - although advertising support for coroutines - has given me trouble, exhibiting -segfaults at run time. - -Apple Clang, of course, is as always well behind the curve with no support for coroutines. If you want to try this code -on a mac, it's entirely possible as long as you ditch the Apple compiler and use the homebrew's clang: -``` -brew install llvm -``` -Clang will then be available in `/usr/local/opt/bin` and you will need to set your `CMAKE_CXX_COMPILER` CMake variable -appropriately. For completeness, it's worth mentioning that I also installed Qt5 using homebrew. You will need to -set `Qt5_DIR`. Something like this: - - ``` - cmake -H. -Bmy_build_dir -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/clang++ -DQt5_DIR=/usr/local/opt/qt5/lib/cmake/Qt5 -``` - -### Going further - -Ok, so what if we want our Qt application to interact with some asio-based service running in another thread? - -For this I'm going to create a few boilerplate classes. The reason is that we're going to have multiple threads running -and each thread is going to be executing multiple coroutines. Each coroutine has an associated executor and that -executor is dispatching completion handlers (which for our purposes advance the progress of the coroutines) in one of -the threads assigned to it. - -It is important that coroutines are able to synchronise with each other, similar to the way that threads synchronise -with each other. - -In fact, it's reasonable to use the mental model that a coroutine is a kind of "thread". - -In standard C++, we have the class `std::condition_variable` which we can wait on for some condition to be fulfilled. -If we were to produce a similar class for coroutines, then coroutines could co_await on each other. This could form the -basis of an asynchronous event queue. - -First the condition_variable, implemented in terms of cancellation of an Asio timer to indicate readiness (thanks -to Chris Kohlhoff - the author of Asio - for suggesting this and saving me having reach for another library or worse, -write my own awaitable type!): - -```cpp -struct async_condition_variable -{ -private: - using timer_type = net::high_resolution_timer; - -public: - using clock_type = timer_type::clock_type; - using duration = timer_type::duration; - using time_point = timer_type::time_point; - using executor_type = timer_type::executor_type; - - /// Constructor - /// @param exec is the executor to associate with the internal timer. - explicit inline async_condition_variable(net::any_io_executor exec); - - template - [[nodiscard]] - auto - wait(Pred pred) -> net::awaitable; - - template - [[nodiscard]] - auto - wait_until(Pred pred, time_point limit) -> net::awaitable; - - template - [[nodiscard]] - auto - wait_for(Pred pred, duration d) -> net::awaitable; - - auto - get_executor() noexcept -> executor_type - { - return timer_.get_executor(); - } - - inline void - notify_one(); - - inline void - notify_all(); - - /// Put the condition into a stop state so that all future awaits fail. - inline void - stop(); - - auto - error() const -> error_code const & - { - return error_; - } - - void - reset() - { - error_ = {}; - } - -private: - timer_type timer_; - error_code error_; - std::multiset wait_times_; -}; - -template -auto -async_condition_variable::wait_until(Pred pred, time_point limit) - -> net::awaitable -{ - assert(co_await net::this_coro::executor == timer_.get_executor()); - - while (not error_ and not pred()) - { - if (auto now = clock_type::now(); now >= limit) - co_return std::cv_status::timeout; - - // insert our expiry time into the set and remember where it is - auto where = wait_times_.insert(limit); - - // find the nearest expiry time and set the timeout for that one - auto when = *wait_times_.begin(); - if (timer_.expiry() != when) - timer_.expires_at(when); - - // wait for timeout or cancellation - error_code ec; - co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec)); - - // remove our expiry time from the set - wait_times_.erase(where); - - // any error other than operation_aborted is unexpected - if (ec and ec != net::error::operation_aborted) - if (not error_) - error_ = ec; - } - - if (error_) - throw system_error(error_); - - co_return std::cv_status::no_timeout; -} - -template -auto -async_condition_variable::wait(Pred pred) -> net::awaitable -{ - auto stat = co_await wait_until(std::move(pred), time_point::max()); - boost::ignore_unused(stat); - co_return; -} - -template -auto -async_condition_variable::wait_for(Pred pred, duration d) - -> net::awaitable -{ - return wait_until(std::move(pred), clock_type::now() + d); -} - -async_condition_variable::async_condition_variable(net::any_io_executor exec) - : timer_(std::move(exec)) - , error_() -{} - -void -async_condition_variable::notify_one() -{ - timer_.cancel_one(); -} - -void -async_condition_variable::notify_all() -{ - timer_.cancel(); -} - -void -async_condition_variable::stop() -{ - error_ = net::error::operation_aborted; - notify_all(); -} -``` - -For our purposes this one is a little too all-singing and all-dancing as it allows for timed waits from multiple -coroutines. This is not needed in our example, but I happened to have the code handy from previous experiments. -You will notice that I have marked the coroutines as `[[nodiscard]]`. This is to ensure that I don't forget to -`co_await` them at the call site. I can't tell you how many times I have done that and then wondered why my program -mysteriously freezes mid run. - -Having built the condition_variable, we now need some kind of waitable queue. I have implemented this in terms of some -shared state which contains an `async_condition_variable` and some kind of queue. I have made the implementation of the -queue a template function (another over-complication for our purposes). The template represents the strategy for -accumulating messages before they have been consumed by the client. The strategy I have used here is a FIFO, which means -that every message posted will be consumed in the order in which they were posted. But it could just as easily be a -priority queue, or a latch - i.e. only storing the most recent message. - -The code to describe this machinery is a little long to put inline, but by all means look at the code: -- [basic_connection](https://github.com/madmongo1/blog-october-2020/blob/stage-3/src/basic_connection.hpp) -- [basic_distributor](https://github.com/madmongo1/blog-october-2020/blob/stage-3/src/basic_distributor.hpp) -- [basic_shared_state](https://github.com/madmongo1/blog-october-2020/blob/stage-3/src/basic_shared_state.hpp) - -The next piece of machinery we need is the actual service that will be delivering messages. The code is more-or-less -a copy/paste of the code that was in our widget because it's doing the same job - delivering messages, but this time -via the basic_distributor. - -- [message_service.hpp](https://github.com/madmongo1/blog-october-2020/blob/stage-3/src/message_service.hpp) -- [message_service.cpp](https://github.com/madmongo1/blog-october-2020/blob/stage-3/src/message_service.cpp) - -Note that the message_service class is a pimpl. Although it uses a shared_ptr to hold the impl's lifetime, it is itself -non-copyable. When the message_service is destroyed, it will signal its impl to stop. The impl will last a little longer -than the handle, while it shuts itself down. - -The main coroutine on the impl is called `run()` and it is initiated when the impl is created: - -```cpp -message_service::message_service(const executor_type &exec) - : exec_(exec) - , impl_(std::make_shared(exec_)) -{ - net::co_spawn( - impl_->get_executor(), - [impl = impl_]() -> net::awaitable { co_await impl->run(); }, - net::detached); -} -``` -Note that the `impl` shared_ptr has been captured in the lambda. Normally we'd need to be careful here because the -lambda is just a class who's `operator()` happens to be a coroutine. This means that the actual coroutine can outlive the -lambda that initiated it, which means that `impl` could be destroyed before the coroutine finishes. For this reason -it's generally safer to pass the impl to the coroutine as an argument, so that it gets decay_copied into the -coroutine state. -However, in this case we're safe. `net::co_spawn` will actually copy the lambda object before invoking it, guaranteeing -- with asio at least - that the impl will survive the execution of the coroutine. - -And here's the `run()` coroutine: - -```cpp -net::awaitable -message_service_impl::run() -{ - using namespace std::literals; - - auto timer - = net::high_resolution_timer(co_await net::this_coro::executor); - - auto done = false; - - listen_for_stop([&] { - done = true; - timer.cancel(); - }); - - while (!done) - { - for (int i = 0; i < 10 && !done; ++i) - { - timer.expires_after(1s); - auto ec = boost::system::error_code(); - co_await timer.async_wait( - net::redirect_error(net::use_awaitable, ec)); - if (ec) - break; - message_dist_.notify_value(std::to_string(i + 1) + " seconds"); - } - - for (int i = 10; i-- && !done;) - { - timer.expires_after(250ms); - auto ec = boost::system::error_code(); - co_await timer.async_wait( - net::redirect_error(net::use_awaitable, ec)); - if (ec) - break; - message_dist_.notify_value(std::to_string(i)); - } - } -} -``` -Notice the `done` machinery allowing detection of a stop event. Remember that a stop event can arrive at any time. The -first this coroutine will hear of it is when one of the timer `async_wait` calls is canceled. Note that the lambda -passed to `listen_for_stop` _is not actually part of the coroutine_. It is a separate function that just happens to -refer to the same state that the coroutine refers to. The communication between the two is via the timer cancellation -and the `done` flag. This communication is guaranteed not to race because both the coroutine and the lambda are executed -by the same `strand`. - -Finally we need to modify the widget: - -```cpp -net::awaitable -test_widget::run_demo() -{ - using namespace std::literals; - - auto service = message_service(ioexec_); - auto conn = co_await service.connect(); - - auto done = false; - - listen_for_stop([&] { - done = true; - conn.disconnect(); - service.reset(); - }); - - while (!done) - { - auto message = co_await conn.consume(); - this->setText(QString::fromStdString(message)); - } - co_return; -} -``` - -This coroutine will exit via exception when the distributor feeding the connection is destroyed. This will happen when -the impl of the service is destroyed. - -Here is the final code for [stage 3](https://github.com/madmongo1/blog-october-2020/tree/stage-3). - -I've covered quite a few topics here and I hope this has been useful and interesting for people interested in exploring -coroutines and the think-async mindset. - -There are a number of things I have not covered, the most important of which is improving the (currently very basic) -`qt_guarded_executor` to improve its performance. At the present time, whether you call `dispatch` or `post` referencing -this executor type, a post will actually be performed. Perhaps next month I'll revisit and add the extra machinery to -allow `net::dispatch(e, f)` to offer straight-through execution if we're already on the correct Qt thread. - -If you have any questions or suggestions I'm happy to hear them. You can generally find me in the `#beast` channel -on [cpplang slack](https://cppalliance.org/slack/) or if you prefer you can either email [me](mailto:hodges.r@gmail.com) -or create an issue on [this repo](https://github.com/madmongo1/blog-october-2020/issues). diff --git a/_posts/2020-12-22-RichardsDecemberUpdate.md b/_posts/2020-12-22-RichardsDecemberUpdate.md deleted file mode 100644 index 62db8804e..000000000 --- a/_posts/2020-12-22-RichardsDecemberUpdate.md +++ /dev/null @@ -1,475 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's November/December Update -author-id: richard ---- - -# A Coroutine Websocket Using Boost Beast - -This month I thought I would present a little idea that I had a few months ago. - -Boost.Beast is a very comprehensive and competent websocket implementation, but it is not what you might call -"straightforward" to use unless you are already wise in the ways of Asio. - -Beast's documentation and design makes no apology for this. There is a disclaimer in the -[documentation](https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/using_io/asio_refresher.html): -> To use Beast effectively, a prior understanding of Networking is required. - -This is worth taking seriously (and if you are not fully aware of how Asio works with respect to the posting of -completion handlers onto the associated executor, this page is worth studying). - -## The Interface - -I wanted to model my websocket object's interface roughly on the javascript websocket connection interface. There will -be a few differences of course, because the javascript version uses callbacks (or continuations) and I will be using -C++ coroutines that execute on an Asio executor. In underlying implementation, these concepts are not actually that far -apart, since Asio awaitables are actually implemented in terms of the normal asio completion token/handler interaction. - -Furthermore, I want my WebSocket's connection phase to be cancellable. - -My websocket interface will look something like this: - -```cpp -namespace websocket -{ - struct message - { - bool is_binary() const; - bool is_text() const; - std::string_view operator*() const; - }; - - struct event - { - bool is_error() const; - bool is_message() const; - error_code const& error() const; - message const& message() const; - }; - - struct connection - { - /// Schedule a frame to be sent at some point in the near future - void - send(std::string_view data, bool as_text = true); - - /// Suspend and wait until either there is a message available or an error - net::awaitable - consume(); - - /// Close the websocket and suspend until it is closed. - net::awaitable - close(beast::websocket::close_reason = /* a sensible default */); - - /// Send the close frame to the server but don't hang around to wait - /// for the confirmation. - void - drop(beast::websocket::close_reason = /* a sensible default */); - - /// If consume() exits with an error of beast::websocket::error::closed then this - /// will return the reason for the closure as sent by the server. - /// Otherwise undefined. - beast::websocket::close_reason - reason() const; - }; - - net::awaitable - connect(std::string url, - connect_options options /* = some default */); -} -``` - -The idea here is to keep the interface as lightweight and as simple as possible. The websocket connection will run on -the executor of the coroutine that created it. Any commands sent to the websocket will be executor safe. That is, -internally their work will be dispatched to the websocket connection's executor. The exception to this will be the -close_reason method, which must only be called once the connection's consume coroutine has returned an error event. -It is a guarantee that once `consume` returns an event that is an error, it will never return anything else, and no -other method on the connection will mutate its internal state. In this condition, it is legal to call the `reason` -method. - -A typical use would look like this: - -```cpp - // default options - auto ws = co_await websocket::connect("wss://echo.websocket.org"); - - for(;;) - { - auto event = co_await ws.consume(); - if (event.is_error()) - break; - else - on_message(std::move(event.message())); - } -``` - -The above code example does not provide any means to write to the websocket. But it would be trivial to either spawn -another coroutine to handle the writer, or call a function in order to signal some orthogonal process that the websocket -was ready. - -```cpp - // default options - auto ws = co_await websocket::connect("wss://echo.websocket.org"); - - on_connected(ws); // The websocket object should be shared-copyable - - for(;;) - { - auto event = co_await ws.consume(); - if (event.is_error()) { - on_close(); - break; - } - else - on_message(std::move(event.message())); - } -``` - -Another way to visualise a websocket is exactly as javascript's websocket connection does, using callbacks or -continuations in order to notify user code that the websocket has received data or closed. It would be trivial to wrap -our coroutine version in order to provide this functionality. We would need to spawn a coroutine in order to run -the `consume()` loop and then somehow signal it to stop if the websocket was disposed of. - -User code might then start to look something like this: - -```cpp - websocket::connect("wss://echo.websocket.org", options) - .on_connect([](websocket::connection ws) - { - run_my_loop(ws); - }); - -void run_my_loop(websocket::connection ws) -{ - bool closed = false; - ws.on_close([&]{ closed = true; }); - ws.on_error([&]{ closed = true; }); - ws.on_message([&](websocket::message msg){ process_message(msg); }); - - // some time later - ws.send("Hello, World!"); -} -``` - -With this style of interface we would need some means of passing the executor on which the continuations would be -invoked. A reasonable place to do this might be the `options` parameter. - -In the JavaScript style interface, it would be important to be able to detect when the websocket has gone out of scope -and ensure that it closes correctly, otherwise we'll have a rogue resource out there with no handle by which we can -close it. This argues that the actual `websocket::connection` class should be a handle to an internal implementation -and that the destructor of the handle should ensure that the implementation is signalled so that it can `drop` the -connection and shutdown cleanly. Under the covers, we're implementing this websocket in Boost.Beast. As with all Asio -objects, there could be (probably will be) asynchronous operations in progress at the time the websocket handle goes -out of scope. - -Thinking this through, it means that: - - The implementation is going to live longer than the lifetime of the last copy of the handle owning the implementation. - - There needs to be some mechanism to cancel the underlying implementation's operations. - -Coroutines can be visualised as threads of execution. In the world of threads (e.g. `std::thread`) we have primitives -such as `std::stop_token` and `std::condition_variable`. The C++ Standard Library does not yet have these primitives -for coroutines. And if it did it would be questionable whether they would be suitable for networking code where -coroutines are actually built on top of Asio composed operations. Does Asio itself provide anything we can use? - -## Asio's Hidden Asynchronous condition_variable - -The answer is surprisingly, yes. But not in the form I was expecting when I asked Chris Kohlhoff (Asio's author and -maintainer) about it. It turns out that asio's timer models an asynchronous version of a condition variable perfectly. -Consider: - -Given: -```cpp -auto t = net::steady_timer(co_await net::this_coro::executor); -t.expires_at(std::chrono::stready_clock::time_point::max()); -``` - -Then we can write: - -```cpp -template -net::awaitable -wait(net::steady_timer& t, Pred predicate) -{ - error_code ec; - while(!ec && !predicate()) - { - co_await t.async_wait(net::redirect_error(net::use_awaitable, ec)); - if (ec == net::error::operation_aborted) - // timer cancelled - continue; - else - throw std::logic_error("unexpected error"); - } -} - -void -notify(net::steady_timer& t) -{ - // assuming we are executing on the same executor as the wait() - t.cancel(); -} -``` - -Which gives us a simple asynchronous condition_variable (this one does not implement timeouts, but it would be trivial -to extend this code to accommodate them). - -## Asynchronous Stop Token - -The `std::stop_token` is a welcome addition to the standard, but it is a little heavyweight for asynchronous code that -runs in an executor, which is already thread-safe by design. A simple in-executor stop source can be implemented -something like this: - -```cpp -namespace async { -namespace detail { -struct shared_state { - void stop() - { - if (!std::exchange(stopped_, true)) - { - auto sigs = std::move(signals_); - signals_.clear(); - for(auto& s : sigs) - s(); - } - } - - std::list> signals_; - bool stopped_ = false; -}; -} -struct stop_source { - void stop() { - impl_->stop(); - } - std::shared_ptr impl_; -} - -struct stop_connection -{ -}; - -struct stop_token -{ - stop_token(stop_source& source) - : impl_(source.impl_) { - } - - bool - stopped() const { return !impl_ || impl_->stopped_; } - - stop_connection - connect(std::function slot); - - std::shared_ptr impl_; -} -} -``` - -The use case would look something like this: - -```cpp - -net::awaitable -something_with_websocket(async::stop_token token) -{ - // passing the stop token allows the connect call to abort early - // if the owner of the stop_source wants to end the use of the - // websocket before it is connected - auto ws = websocket::connect("wss://echo.websocket.org", - websocket::connect_options { .stop_token = token }); - - // connect a slot to the stop token which drops the connection - auto stopconn = token.connect([&] { ws.drop(); }; - - for(;;} { - auto event = co_await ws.consume(); - // ...etc - } - -} -``` - -Now, armed with both a `stop_token` and a `condition_variable`, we gain a great deal of flexibility with programs -running on an Asio-style executor. - -So let's build a little chat app to talk the the echo bot. - -## Coding style when using Asio coroutines. - -I mentioned earlier that I like to decompose objects with complex lifetimes into an impl and handle. My personal -programming style for naming the components is as follows: - -### The implementation - -This is the class that implements the complex functionality that we want. I generally give this class an `_impl` suffix -and apply the following guidelines: -- The impl does not control its own lifetime. -- Calls to the impl are expected to already be executing on the correct thread or strand, and in the case of - multi-threaded code, are expected to have already taken a lock on any mutex. - -This is a personal preference which I find tends to lower the complexity of the object, since the interface functions -do not have to manage more than one concern, and deadlocks etc are not possible. - -### The lifetime - -When holding an object in shared_ptr, we get a chance to intercept the destruction of the last handle. At this point -we do not have to destroy the implementation, but can allow it to shut down gracefully before destruction. -In order to do this, particularly with an object that is referenced by internal coroutines, I have found that it's -useful to separate the public lifetime of the object, and its internal lifetime, which may be longer than the public -one. - -A convenient, if not especially efficient way to do this is to hold two shared_ptr's in the handle. One being a -shared_ptr which has a custom destructor - the lifetime ptr, and one being a normal shared_ptr to the -implementation which can be copied in order to extend its private lifetime while it shuts down. -It is the responsibility of the custom deleter to signal the implementation that it should start shutting down. - -In this case, the websocket connection's public handle may look something like this: - -```cpp -namespace websocket { - -struct connection_lifetime -{ - connection_lifetime(std::shared_ptr&& adopted) - : impl_(std::move(adopted)) - , lifetime_(new_lifetime(impl_)) - { - } - - static std::shared_ptr - new_lifetime(std::shared_ptr const& impl) - { - static int useful_address; - auto deleter = [impl](int*) noexcept - { - net::co_spawn(impl->get_executor(), - [impl]() -> net::awaitable - { - co_await impl->stop(); - }, net::detached); - }; - - return std::shared_ptr(&useful_address, deleter); - } - - std::shared_ptr impl_; - std::shared_ptr lifetime_; -}; - -struct connection -{ - connection(connection_lifetime l); -}; -} -``` - -The interesting part here is in the function `new_lifetime`. There are a few things going on here. -First, we are capturing the internal lifetime of our `connection_impl` and storing it in the deleter of the lifetime -pointer. This of course means that the private implementation will live at least as long as the public lifetime. -Secondly, the deleter does not actually delete anything. It merely captures a copy of the impl pointer and runs a -coroutine on the impl to completion before releasing the impl pointer. The idea is that this coroutine will not complete -until all internal coroutines within the implementation have completed. The provides the fortunate side effect that -operations running inside the impl do not have to capture the impl's lifetime via shared_from_this. -It turns out that this aids composability, since subordinate coroutines within the implementation can be written as free -functions, and ported to other implementations that may not involve a shared_ptr. -It also means that the impl itself can be composed, since it has no restrictions on lifetime semantics. -i.e. If I wanted to implement a JSON-RPC connection by deriving from the websocket::connection_impl, I do not have to -be concerned about translating shared_ptrs internally in the derived class. - -# Once it's all put together - -Finally, having created all the primitives (which I really should start collating into a library), we can test our -little websocket chat client, which becomes a very simple program: - -Here's main: -```cpp -int -main() -{ - net::io_context ioctx; - - net::co_spawn( - ioctx.get_executor(), [] { return chat(); }, net::detached); - - ioctx.run(); -} -``` - -And here's the chat() coroutine: - -```cpp -net::awaitable< void > -chat() -{ - // connect the websocket - auto ws = co_await websocket::connect("wss://echo.websocket.org"); - - // spawn the coroutine to read console input and send it to the websocket - auto stop_children = async::stop_source(); - net::co_spawn( - co_await net::this_coro::executor, - [stop = async::stop_token(stop_children), ws]() mutable { - return do_console(std::move(stop), std::move(ws)); - }, - net::detached); - - // read events from the websocket connection. - for (;;) - { - auto event = co_await ws.consume(); - if (event.is_error()) - { - if (event.error() == beast::websocket::error::closed) - std::cerr << "peer closed connection: " << ws.reason() - << std::endl; - else - std::cerr << "connection error: " << event.error() << std::endl; - break; - } - else - { - std::cout << "message received: " << event.message() << std::endl; - } - } - - // at this point, the stop_source goes out of scope, - // which will cause the console coroutine to exit. -} -``` - -And finally, the do_console() coroutine. Note that I have used asio's posix interface to collect console input. -In order to run compile in a WIN32 environment, we'd need to do something different (suggestions welcome via PR!). - -```cpp -net::awaitable< void > -do_console(async::stop_token stop, websocket::connection ws) -try -{ - auto console = asio::posix::stream_descriptor( - co_await net::this_coro::executor, ::dup(STDIN_FILENO)); - auto stopconn = stop.connect([&] { console.cancel(); }); - - std::string console_chars; - while (!stop.stopped()) - { - auto line_len = - co_await net::async_read_until(console, - net::dynamic_buffer(console_chars), - '\n', - net::use_awaitable); - auto line = console_chars.substr(0, line_len - 1); - console_chars.erase(0, line_len); - std::cout << "you typed this: " << line << std::endl; - ws.send(line); - } -} -catch(...) { - // error handling here -} -``` - -If you'd like to look into the complete code, submit a PR or offer some (probably well-deserved) criticism, you will -find the [code repository here](https://github.com/madmongo1/blog-december-2020). diff --git a/_posts/2021-01-01-RichardsNewYearUpdate.md b/_posts/2021-01-01-RichardsNewYearUpdate.md deleted file mode 100644 index 556519c2c..000000000 --- a/_posts/2021-01-01-RichardsNewYearUpdate.md +++ /dev/null @@ -1,516 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's New Year Update - Reusable HTTP Connections -author-id: richard ---- - -# Reusable HTTP(S) Connections - -Something I am often asked by users of Boost Beast is how to code a client which effectively re-uses a pool of HTTP -connections, in the same way a web browser does. - -The premise is straightforward - if our client is going to be making multiple calls to a web server (or several of them) -then it makes sense that once a connection has been used for one request, it is returned to a connection pool so that -a subsequent request can make use of it. - -It also makes sense to have a limit on the number of concurrent connections that can be open against any one host. -Otherwise, if the client needs to make multiple requests at the same time, it will end up creating new connections in -parallel and lose the efficiency of re-using an existing connection. - -From these requirements, we can start to think about a design. - -Firstly, we can imagine a connection cache, with connections kept in a map keyed on host + scheme + port (we can't -re-use an HTTP port for an HTTPS request!). - -When a request needs a connection, it will either create a new one (connection limit per host not met) or will wait -for an existing connection to become available (which implies a condition variable). - -Once a request has a connection to use, it will send the HTTP request and wait for the response. - -At this stage, there is a possibility that the active connection which has been allocated could have been idle since -the last time it was used. In TCP there is no way to atomically check whether the remote host has closed the -connection (or died). The only way to know is to actually read from the socket with a timeout. If the remote host has -shutdown the socket, we will be notified as soon as the RST frame arrives at our host. If the remote host has stopped -working or the network is bad, we'll be notified by the timeout. - -Thus if our read operation results in an error and we have inherited the connection from the cache, we ought to re-try -by reopening the connection to the remote host and repeating the write/read operations. However, if there is an error -reported on the subsequent attempt, then we can conclude that this is a legitimate error to be reported back to the -caller. - -In simplified pesudocode, the operation might look something like this (assuming we report transport errors as -exceptions): - -```cpp -response -read_write(connection& conn) -{ - response resp; - - auto retry = false; - - if(conn.is_initialised()) - retry = true; - else - conn.connect(...); - - for(;;) - { - conn.write(request); - auto err = conn.read(resp); - if (err) - { - if(!std::exchange(retry, false)) - throw system_error(err); - request.clear(); - conn.disconnect(); - } - else - break; - } - - return resp; -} -``` - -# Structuring the Code - -## General Principles - -In my previous [blog](https://cppalliance.org/richard/2020/12/22/RichardsDecemberUpdate.html) I mentioned my preference -for writing the implementation of a class in such a way that it does not need to take care of its own lifetime or -mutual exclusion. These concerns are deferred to a wrapper or handle. Methods on the handle class take care of -marshalling the call to the correct executor (or thread) and preserving the implementation's lifetime. The -implementation need only concern itself with the logic of handling the request. - -Here's an example about which I'll go into more detail later: -```cpp -net::awaitable< response_type > -connection_cache::call(beast::http::verb method, - const std::string & url, - std::string data, - beast::http::fields headers, - request_options options) -{ - // DRY - define an operation that performs the inner call. - auto op = [&] { - return impl_->call(method, - url, - std::move(data), - std::move(headers), - std::move(options)); - }; - - // deduce the current executor - auto my_executor = co_await net::this_coro::executor; - - // either call directly or via a spawned coroutine - co_return impl_->get_executor() != my_executor - ? co_await op() - : co_await net::co_spawn(impl_->get_executor(), op, net::use_awaitable); -} -``` - -## Coroutines - -In this implementation I will be providing a C++20 coroutine interface over Asio executors once again. I am using -coroutines because they are easier to write when compared to Asio's composed operations, but are fundamentally the -same thing in a prettier package. - -## Mutual Exclusion - -For mutual exclusion I will be embedded an asio strand into each active object. The advantage of doing so means that no -thread of execution is ever blocked which means we can limit the number of threads in the program to the number of -free CPUs giving us maximum throughput of work. In reality of course, one thread is more than enough computing power -for almost all asynchronous programs. It's therefore better to think in terms of one _executor_ per program component, -with the implicit guarantee that a given executor will only perform work on one thread at a time. -Thinking this way allows us to write code in a way that is agnostic of whether the final program is compiled to be -single or multi-threaded. - -## But What About Single-threaded Programs? - -In order that I don't need to rewrite code when should I decide to make a single-threaded program multi-threaded or vice -versa, I have a couple of little utility functions and types defined in -[config.hpp](https://github.com/madmongo1/blog-new-year-2021/blob/master/src/config.hpp) - -Specifically: -```cpp -namespace net -{ -using namespace asio; - -using io_executor = io_context::executor_type; - -#ifdef MULTI_THREADED - -using io_strand = strand< io_executor >; - -inline io_strand -new_strand(io_executor const &src); - -inline io_strand -new_strand(io_strand const &src); - -#else - -using io_strand = io_context::executor_type; - -inline io_strand -new_strand(io_executor const &src); - -#endif - -inline io_executor -to_io_executor(any_io_executor const &src); -} // namespace net -``` - -Any object type in the program which _would require its own strand_ in a multi-threaded program will simply use the type -`io_strand` whether the program is compiled for single-threaded operation or not. Any code that would notionally -need to construct a new strand simply calls `new_strand(e)` where `e` is either a strand or a naked executor. - -Any code that needs access to the notional underlying executor would call `to_io_executor(e)`. - -## Determinism - -Since we're using asio's executors for scheduling, it means that we can use asio's timer as a deterministic, ordered -asynchronous condition variable, which means that requests waiting on the connection pool will be offered free -connections in the order that they were requested. This guarantee is implicit in the way that the timers' `cancel_one()` -method is specified. - -As we'll see later, asio's timers also make it trivial to implement an asynchronous semaphore. In this case we use one -to ensure that requests are handled concurrently but no more than some upper limit at any one time. - -## Interface - -I'm going to create a high-level concept called a `connection_cache`. The interface will be something like this: - -```cpp -struct connection_cache -{ - using response_ptr = std::unique_ptr< response >; - - net::awaitable< response_ptr > - rest_call( - verb method, - std::string const& url, - std::optional data = std::nullopt, - headers hdrs = {}, - options opts = {}); -}; -``` - -There are a few things to note here. - - - The return type of the coroutine is a unique_ptr to a response. A natural question might be whether the response - should simply be returned by value. However, in practice I have found that there are a number of practical reasons - why it's often better to return the response as a pointer. Firstly it allows conversion to a - `shared_ptr` in environments where the response might be passed through a directed acyclic graph. - Secondly, would allow a further enhancement in that having finished with the response, the client could post it back - to the cache, meaning that it could be cached for re-use. - - The only two required arguments are the method and url. All others can be defaulted. - - An optional string may be passed which contains the payload of a POST request. This is passed by value because, - as we'll see later,the implementation will want to move this into the request object prior to initiating - communications. I have chosen a string type for two reasons - - text in the form of JSON or XML is the most common form of messaging in REST calls. - - strings can contain binary data with no loss of efficiency. - - `hdrs` is simply a list of headers which should be set in the request. Again, these are passed by value as they will - be moved into the request object. - - The last parameter of the call is an as-yet undefined type called `options`. This will allow us to add niceties like - timeout arguments, a stop_token, redirect policies, a reference to a cookie store and so on. - -When called, `rest_call` will attempt to reuse an existing connection. If a connection is not available, it will create -a new one if we are under the connection threshold for the deduced host and if not, it will wait until a connection is -available. - -Furthermore, the number of concurrent requests will be throttled to some upper limit. - -Transport failures will be reported as an exception (of type `system_error`) and a successful response (even if a 400 -or 500) will be returned in the `response_ptr`. That is to say, as long as the transport layer works out, the code will -take the non-exceptional path. - -## Implementation details - -### URL Parsing - -Among the things I am often asked about in the Beast slack channel and in the -[Issue Tracker](https://github.com/boostorg/beast/issues) is why there is no URL support in Beast. -The is that Beast is a reasonably low level library that concerns itself with the HTTP (and WebSocket) -protocols, plus as much buffer and stream management as is necessary to implement HTTP over Asio. -The concept of a URL the subject of its own RFCs and is a higher level concern. -The C++ Alliance is working on [Boost.URL](https://github.com/CPPAlliance/url) but it is not ready for publishing yet. -In the meantime, I found a nifty regex on the internet that more-or-less suffices for our needs: - -```cpp - std::tuple< connection_key, std::string > - parse_url(std::string const &url) - { - static const auto url_regex = std::regex( - R"regex((http|https)://([^/ :]+):?([^/ ]*)((/?[^ #?]*)\x3f?([^ #]*)#?([^ ]*)))regex", - std::regex_constants::icase | std::regex_constants::optimize); - auto match = std::smatch(); - if (not std::regex_match(url, match, url_regex)) - throw system_error(net::error::invalid_argument, "invalid url"); - - auto &protocol = match[1]; - auto &host = match[2]; - auto &port_ind = match[3]; - auto &target = match[4]; - /* - auto &path = match[5]; - auto &query = match[6]; - auto &fragment = match[7]; - */ - return std::make_tuple( - connection_key { .hostname = host.str(), - .port = deduce_port(protocol, port_ind), - .scheme = deduce_scheme(protocol, port_ind) }, - target.str()); - } -``` -### Exceptions in Asynchronous Code Considered Harmful - -I hate to admit this, because I am a huge fan of propagating errors as exceptions. This is because the combination of -RAII and exception behaviour handling makes error handling very slick in C++. However, coroutines have two rather -unpleasant limitations: -- You can't call a coroutine in a destructor. -- You can't call a coroutine in an exception handling block. - -There are workarounds. Consider: - -```cpp -my_component::~my_component() -{ - // destructor - net::co_spawn(get_executor(), - [impl = impl_]()->net::awaitable - { - co_await impl->shutdown(); - // destructor of *impl happens here - }, net::detached); -} -``` -This is the destructor of a wrapper object that contains a `shared_ptr` to its implementation, `impl_`. In this case -we can detect the destruction of `my_component` and use this to spawn a new coroutine of indeterminate lifetime that -takes care of shutting down the actual implementation and then destroying it. - -This solves the problem of RAII but it mandates that we must author objects that will be used in coroutines as -a handle-body pair. - -We can similarly get around the "no coroutine calls in exception handlers" limitation if we're prepared to stomach code -like this: - -```cpp -net::awaitable -my_coro() -{ - std::function on_error; - try { - co_await something(); - } - catch(...) - { - // set up error_handler - on_error = [ep = std::current_exception] { - return handler_error_coro(ep); - }; - } - // perhaps handle the error here - if (on_error) - co_await on_error(); -} -``` - -I think you'll agree that this is a revolting solution to an unforgivable omission in the language. Not only is it -untidy, confusing, difficult to teach and error-prone, it also turns exception handling into same fiasco that is -enforced checking of return codes. - -To add insult to injury, the error handling code in this function takes up 5 times as many lines as the logic! - -Therefore my recommendation is that in asynchronous coroutine code, it's better to avoid exceptions and have coroutines -either return a tuple of (error_code, possible_value) or a variant containing error-or-value. - -For example, here is some code from the `connection_impl` in my example project: - -```cpp -net::awaitable< std::tuple< error_code, response_type > > -connection_impl::rest_call(request_class const & request, - request_options const &options) -{ - auto response = std::make_unique< response_class >(); - - auto ec = stream_.is_open() - ? co_await rest_call(request, *response, options) - : net::error::basic_errors::not_connected; - - if (ec && ec != net::error::operation_aborted) - { - ec = co_await connect(options.stop); - if (!ec) - ec = co_await rest_call(request, *response, options); - } - - if (ec || response->need_eof()) - stream_.close(); - - co_return std::make_tuple(ec, std::move(response)); -} -``` - -### Ensuring that a Coroutine Executes on the Correct Executor - -In playing with asio coroutines I stumbled upon something that has become an idiom. - -Consider the situation where a `connection` class is implemented in terms of a handle and body. The body contains its -own executor. In a multi-threaded build, this executor will be a `strand` while in a single threaded-build we would want -it to be simply an `io_context::executor_type` since there will be no need for any of the thread guards implicit in a -strand. - -Now consider that the implementation has a member coroutine called (say) `call`. There are two scenarios in which -this member will be called. The first is where the caller is executing in the same executor that is associated with -the implementation, the second is where the caller is in its own different executor. -In the latter case, we must `post` or `spawn` the execution of the coroutine onto the implementation's executor in order -to ensure that it runs in the correct sequence with respect to other coroutines initiated against it. - -The idiom that occurred to me originally was to recursively spawn a coroutine to ensure the call happened on the -correct executor: - -```cpp -net::awaitable< response_type > -connection_cache::call(beast::http::verb method, - const std::string & url, - std::string data, - beast::http::fields headers, - request_options options) -{ - auto my_executor = co_await net::this_coro::executor; - - if (impl_->get_executor() == my_executor) - { - co_return co_await impl_->call(method, - url, - std::move(data), - std::move(headers), - std::move(options)); - } - else - { - // spawn a coroutine which recurses on the correct executor. - // wait for this coroutine to finish - co_return co_await net::co_spawn( - impl_->get_executor(), - [&]() -> net::awaitable< response_type > { - return call(method, - url, - std::move(data), - std::move(headers), - std::move(options)); - }, - net::use_awaitable); - } -} -``` - -However, this does have the drawback that a code analyser might see the possibility of infinite recursion. - -After discussing this with [Chris](https://github.com/chriskohlhoff/asio/), Asio's author, a better solution was found: - -```cpp -net::awaitable< response_type > -connection_cache::call(beast::http::verb method, - const std::string & url, - std::string data, - beast::http::fields headers, - request_options options) -{ - // DRY - define an operation that performs the inner call. - auto op = [&] { - return impl_->call(method, - url, - std::move(data), - std::move(headers), - std::move(options)); - }; - - // deduce the current executor - auto my_executor = co_await net::this_coro::executor; - - // either call directly or via a spawned coroutine - co_return impl_->get_executor() != my_executor - ? co_await op() - : co_await net::co_spawn(impl_->get_executor(), op, net::use_awaitable); -} -``` - -There are a few things to note: -- All lifetimes are correct even though the `op` takes arguments by reference. This is because whichever path we take, -our outer coroutine will suspend on the call to `op`. -- Note that in the slow path, `op` is captured by value in the call to `co_spawn`. Had we written: -`: co_await net::co_spawn(impl_->get_executor(), op(), net::use_awaitable);` then `op` would have been called _during_ - the setup of the call to `co_spawn`, which would result in `impl_->call(...)` being called on the wrong - executor/thread. - -# Talk is Cheap - -TL;DR. Enough talk, where's the code? - -It's on github at [https://github.com/madmongo1/blog-new-year-2021](https://github.com/madmongo1/blog-new-year-2021). - -## Final Notes. - -Before signing off I just wanted to cover a few of the features I have completed in this example and a few that I -have not. - -### What's Done - -- There is a limit on the number of concurrent connections to a single host. Host here is defined as a tuple of the -transport scheme (i.e. http or https), port and hostname. This is currently defaulted to two, but would be trivial to - change. - -- There is a limit on the number of concurrent requests across all hosts. This defaults to 1000. Simultaneous requests -numbering more than this will be processed through what is an asynchronous semaphore, implemented like this: - ```cpp - while (request_count_ >= max_concurrent_requests_) - { - error_code ec; - co_await concurrent_requests_available_.async_wait( - net::redirect_error(net::use_awaitable, ec)); - } - - ++request_count_; - - ... - ... request takes place here - ... - - if (--request_count_ < max_concurrent_requests_) - { - concurrent_requests_available_.cancel_one(); - } - - ``` - -### What's Not Done - -- HTTP 300 Redirect handling. I consider this to be a higher level concern than connection caching. -- LRU Connection recycling. At the moment, the program will allow the number of connections to grow without limit if - an unlimited number of different hosts are contacted. In a production system we would need to add more active state to - each connection and have some logic to destroy old unused connections to make way for new ones. -- The `stop_token` is not executor-aware. I have left the stop_token in for exposition but it should not be activated - by a different executor to the one where the connection to it has been created at present. If you're interested to - see how this will look when complete, please submit an issue against the github repo and I'll update the code and add - a demonstration of it. -- A Cookie Jar and HTTP session management. Again, these are higher level concerns. The next layer up would take care of -these plus redirect handling. -- The CMakeLists project in the repo has been tested with GCC 10.2 and Clang-11 on Fedora Linux. Microsoft developers - may need to make the odd tweak to get things working. I'm more than happy to accept PRs. -- Setting up of the `ssl::context` to check peer certificates. Doing this properly is complex enough to warrant another - blog in its own right. - -Thanks for reading. I hope you've found blog useful. Please by all means get in touch by: -- raising an [issue](https://github.com/madmongo1/blog-new-year-2021/issues) -- Contacting me in the #beast channel of [CppLang Slack](https://cppalliance.org/slack/) -- email [hodges.r@gmail.com](mailto:hodges.r@gmail.com) - diff --git a/_posts/2021-01-15-DroneCI.md b/_posts/2021-01-15-DroneCI.md deleted file mode 100644 index a7dee0b51..000000000 --- a/_posts/2021-01-15-DroneCI.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -layout: post -nav-class: dark -categories: sam -title: Drone CI -author-id: sam ---- -# Overview - -A message currently appears (mid-January 2021) at the top of the travis-ci.org website. - -"Please be aware travis-ci.org will be shutting down in several weeks, with all accounts migrating to travis-ci.com. Please stay tuned here for more information." - -The transition has not been a smooth one, with long, disruptive delays occurring on existing builds, and lack of clear communication from the company. Many were unaware of the impending change. Some informative posts about the topic are [Travis CI's new pricing plan threw a wrench in my open source works](https://www.jeffgeerling.com/blog/2020/travis-cis-new-pricing-plan-threw-wrench-my-open-source-works) and [Extremely poor official communication of the .org shutdown](https://travis-ci.community/t/extremely-poor-official-communication-of-the-org-shutdown/10568). - -The C++ Alliance has decided to implement an in-house CI solution and make the new service available for Boost libraries also. - -# Selection Process - -The first step was choosing which software to use. There are truly a surprising number of alternatives. An extensive review was conducted, including many Continuous Integration services from [awesome-ci](https://github.com/ligurio/awesome-ci) which lists more than 50. Coincidentally, Rene Rivera had recently done an analysis as well for [ci_playground](https://github.com/bfgroup/ci_playground), and his example config files eventually became the basis for our new config files. - -The top choices: -Appveyor -Azure Pipelines -BuildBot -CircleCI -CirrusCI -Drone -Github Actions -Semaphore -Shippable -TeamCity - -From this list, Appveyor and Drone seemed the most promising to start with. Both allow for 100% self-hosting. - -## Appveyor - -The appeal of Appveyor is that the config files are basic yaml, and everything runs in a Docker container. It sounds perfect. However, after experimentation there were a few issues. -1. Appveyor was originally designed for Microsoft Windows, with .NET and Powershell being key ingredients. While it can run on Linux, it's not a native Linux application. Most of the CI testing done by Boost and CPPAlliance runs on Linux. -2. Specifically, the Docker experience on Windows is not nearly as smooth as Linux. I encountered numerous complexities when setting up Appveyor Windows Docker containers. -3. Bugs in their app. - -Due to a combination of those reasons, Appveyor was not the best choice for this project. -## Drone - -Within the first day or two experimenting with Drone, it became clear that this was an excellent CI framework: -1. Simple, usable UI -2. Easy installation -3. Linux and Docker native -4. Small number of processes to run -5. Integrates with PostgreSQL, MySQL, Amazon S3 -6. Autoscale the agents -7. Badges on github repos - -The main drawback with Drone is the absence of "matrix" builds. Their alternative for matrices is jsonnet or starlark, which are flexible scripting languages. I was apprehensive about this point, thinking that the end-user would prefer simple yaml files - exactly like Travis. And, in fact, that is probably the case. Basic yaml files are easier to understand. However, on balance, this was the only noticable problem with Drone, and everything else seemed to be in order. The resulting starlark files do have a matrix-like configuration where each job can be customized. - -To discuss [Starlark](https://docs.bazel.build/versions/master/skylark/language.html) for a moment - it's a subset of python, and therefore easy to learn. [Why would I use Starlark instead of just Python?](https://pypi.org/project/pystarlark/) "Sandboxing. The primary reason this was written is for the "hermetic execution" feature of Starlark. Python is notoriously difficult to sandbox and there didn't appear to be any sandboxing solutions that could run within Python to run Python or Python-like code. While Starlark isn't exactly Python it is very very close to it. You can think of this as a secure way to run very simplistic Python functions. Note that this library itself doesn't really provide any security guarantees and your program may crash while using it (PRs welcome). Starlark itself is providing the security guarantees." - - -### Running a drone server - -Create a script startdrone.sh with these contents: -``` -#!/bin/bash - -docker run \ - --volume=/var/lib/drone:/data \ - --env=DRONE_GITHUB_CLIENT_ID= \ - --env=DRONE_GITHUB_CLIENT_SECRET= \ - --env=DRONE_RPC_SECRET= \ - --env=DRONE_TLS_AUTOCERT= \ - --env=DRONE_SERVER_HOST= \ - --env=DRONE_SERVER_PROTO= \ - --env=DRONE_CONVERT_PLUGIN_ENDPOINT= \ - --env=DRONE_CONVERT_PLUGIN_SECRET= \ - --env=DRONE_HTTP_SSL_REDIRECT= \ - --env=DRONE_HTTP_SSL_TEMPORARY_REDIRECT= \ - --env=DRONE_S3_BUCKET= \ - --env=DRONE_LOGS_PRETTY= \ - --env=AWS_ACCESS_KEY_ID= \ - --env=AWS_SECRET_ACCESS_KEY= \ - --env=AWS_DEFAULT_REGION= \ - --env=AWS_REGION= \ - --env=DRONE_DATABASE_DRIVER= \ - --env=DRONE_DATABASE_DATASOURCE= \ - --env=DRONE_USER_CREATE= \ - --env=DRONE_REPOSITORY_FILTER= \ - --env=DRONE_GITHUB_SCOPE= \ - --publish=80:80 \ - --publish=443:443 \ - --restart=always \ - --detach=true \ - --name=drone \ - drone/drone:1 -``` - -Fill in the variables. (Many of those are secure keys which shouldn't be published on a public webpage.) -Then, run the script. - -``` -./startdrone.sh -``` - -Drone is up and running. - -Next, the starlark plugin. Edit startstarlark.sh: -``` -#!/bin/bash - -docker run -d \ - --volume=/var/lib/starlark:/data \ - --env= \ - --publish= \ - --env=DRONE_DEBUG= \ - --env=DRONE_SECRET= \ - --restart=always \ - --name=starlark drone/drone-convert-starlark -``` -and run it: - -``` -./startstarlark.sh -``` - -Starlark is up and running. Finally, the autoscaler. -``` -#!/bin/bash - -docker run -d \ - -v /var/lib/autoscaler:/data \ - -e DRONE_POOL_MIN= \ - -e DRONE_POOL_MAX= \ - -e DRONE_SERVER_PROTO= \ - -e DRONE_SERVER_HOST= \ - -e DRONE_SERVER_TOKEN= \ - -e DRONE_AGENT_TOKEN= \ - -e DRONE_AMAZON_REGION= \ - -e DRONE_AMAZON_SUBNET_ID= \ - -e DRONE_AMAZON_SECURITY_GROUP= \ - -e AWS_ACCESS_KEY_ID= \ - -e AWS_SECRET_ACCESS_KEY= \ - -e DRONE_CAPACITY_BUFFER= \ - -e DRONE_REAPER_INTERVAL= \ - -e DRONE_REAPER_ENABLED= \ - -e DRONE_ENABLE_REAPER= \ - -e DRONE_AMAZON_INSTANCE= \ - -e DRONE_AMAZON_VOLUME_TYPE= \ - -e DRONE_AMAZON_VOLUME_IOPS= \ - -p \ - --restart=always \ - --name=autoscaler \ - drone/autoscaler -``` -Start the autoscaler. -``` -./startautoscaler.sh -``` - -Windows autoscaler is still experimental. For now, both Windows and Mac servers have been installed manually and will be scaled individually. Because they are less common operating systems, with most boost builds running in Linux, the CPU load on these other machines is not as significant. - -# Configs - -The real complexities appear when composing the config files for each repository. After manually porting .travis.yml for [https://github.com/boostorg/beast]( https://github.com/boostorg/beast) and [https://github.com/boostorg/json](https://github.com/boostorg/json), the next step was creating a Python script which automates the entire process. - -## Drone Converter - -A copy of the script can be viewed at [https://github.com/CPPAlliance/droneconverter-demo](https://github.com/CPPAlliance/droneconverter-demo) - -The idea is to be able to go into any directory with a .travis.yml file, and migrate to Drone by executing a single command: - -``` -cd boostorg/accumulators -droneconverter -``` - -The converter ingests a source file, parses it with PyYAML, and dumps the output in Jinja2 templates. The method to write the script was by beginning with any library, such as [boostorg/array](https://github.com/boostorg/array), and just get that one working. Then, move on to others, [boostorg/assign](https://github.com/boostorg/assign), [boostorg/bind](https://github.com/boostorg/bind), etc. Each library contains a mix of travis jobs which are both similar and different to the previously translated libraries. Thus, each new travis file presents a new puzzle to solve, but hopefully in a generalized way that will also work for all repositories. - -Versions of [clang](https://clang.llvm.org/) ranging from 3.3 to 11 are targeted in the tests. While later releases such as clang-6 or clang-7 usually build right away without errors, the earlier versions in the 3.x series were failing to build for a variety of reasons. First of all, those versions are not available on Ubuntu 16.04 and later, which means being stuck on Ubuntu 14.04, preventing an across-the-board upgrade to a newer Ubuntu. Then, if a standard "base" clang is simultaneously installed, such as clang-7 or 9, this seems to cause other dependent packages and libraries to be installed, which conflict with clang-3. The solution was to figure out what travis does, and copy it. Travis images have downloaded and installed clang-7 into a completely separate directory, not using the ordinary system packages. Then the extra directory /usr/local/clang-7.0.0/bin has been added to the $PATH. - -Some .travis.yml files have a "matrix" section. Others have "jobs". Some .travis files place all the logic in one main "install" script. Others refer to a variety of script sections including "install", "script", "before_install", "before_script", "after_success", which may or may not be shared between the 20 jobs contained in the file. Some job in travis were moving (with the mv command) their source directory, which is baffling and not usually permitted in Jenkins or Drone. This must be converted to a copy command instead. "travis_wait" and "travis_retry" must be deleted, they are travis-specific. Many travis variables such as TRAVIS_OS_NAME or TRAVIS_BRANCH need to be set and/or replaced in the resulting scripts. Environment variables in the travis file might be presented as a list, or a string, without quotes, or with single quotes, or double quotes, or single quotes embedded in double quotes, or double quotes embedded in single quotes, or missing entirely and included as a global env section at the top of the file. The CXX variable, which determines the compiler used by boost build, isn't always apparent and could be derived from a variety of sources, including the COMPILER variable or the list of packages. The list of these types of conversion fixes goes on and on, you can see them in the droneconverter program. - -Apple Macs Developer Tools depends on Xcode, with an array of possible Xcode version numbers from 6 to 12, and counting. Catalina only runs 10 and above. High Sierra will run 7-9. None of these will operate without accepting the license agreement, however the license agreement might reset if switching to a newer version of Xcode. The presence of "Command Line Tools" seems to break some builds during testing, however everything works if "Command Line Tools" is missing and the build directly accesses Xcode in the /Applications/Xcode-11.7.app/Contents/Developer directory. On the other hand, the package manager "brew" needs "Command Line Tools" (on High Sierra) or it can't install packages. A solution which appears to be sufficiently effective is to “mv CommandLineTools CommandLineTools.bck”, or the reverse, when needed. - -Drone will not start on a Mac during reboot unless the Drone user has a window console sessions running too. So, Apple is not an ideal command-line-only remote server environment. - -# Drone Deployments - -Pull requests with the new drone configs were rolled out to all those boost repositories with 100% travis build success rate and 100% drone build success rate, which accounts for about half of boost libraries. The other half of boost libraries are either missing a .travis.yml file, or they have a couple of travis/drone jobs which are failing. These should be addressed individually, even if only to post details about it in the pull requests. Ideally, attention should be focused on these failing tests, one by one, until the jobs attain a 100% build success rate and the badge displays green instead of red. The [long tail](https://en.wikipedia.org/wiki/Long_tail) distribution of edge-cases require individual attention and rewritten tests. - -# Github Actions - -The droneconverter script is being enhanced to also generate Github Actions config files. A first draft, tested on a few boost libraries, is building Linux jobs successfully. Ongoing. diff --git a/_posts/2021-01-31-RichardsJanuaryUpdate.md b/_posts/2021-01-31-RichardsJanuaryUpdate.md deleted file mode 100644 index 85997b5d4..000000000 --- a/_posts/2021-01-31-RichardsJanuaryUpdate.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's January Update -author-id: richard ---- - -# A year in the C++ Alliance - -January marks one year since I joined the C++ Alliance and started maintaining the Boost.Beast library. - -It's a pleasure to work with interesting and fun people who are passionate about writing C++, and in particular good -C++. - -During the past year I have spent some time attending ISO WG21 meetings online as an alternate representative of the -C++ Alliance. Prior to joining the organisation, during my life as a developer I always felt that the standards -committee did the developer community a disservice. Without knowing much about the inner workings, it seemed to me that -the committee lived in an Ivory Tower. So my intention was to see if there was a way to bring something useful to the table -as a keen a prolific writer of C++ in the financial sector. In particular I had a personal interest in the -standardisation of asynchronous networking, forked from the wonderful Asio library. - -I ended the year feeling no less jaded with the entire standards process, concluding that there is not much I can do to -help. - -I feel it's important to say that the committee is attended by very bright, passionate people who clearly enjoy the C++ -language as much as I do. - -What I think does not work, at least from the point of view of delivering useful progress, is the process of committee -itself. During my commercial life there has been one fundamental truth, which is that things go well when there is focus -of attention and the taking of personal responsibility. It seems to me that committees in general undermine these -important fundamentals. The upshot of this is that in my mind, C++ developers are not going to get the tools they need, -in the timescales they need, if they wait for the slow grind of WG21's wheels of stone. - -It is to me noteworthy that many of the libraries I actually use (fmt, spdlog, boost, jwt, openssl, and so on) have been -in some way standardised or are in the process of being standardised, but always in a lesser form than the original, -created by a small, passionate team of individuals who enjoyed autonomy and freedom of design. - -Even now, if a feature is available in the standard and as a 3rd party library, I will almost always choose the third -party version. It will generally have more features and a design undamaged by a process that externalises costs. - -Which brings me back to my old C++ mantra, proven true for me over the past 15 years or so, *Boost is the -Standard*. Having said this, I must in fairness mention the wonderful fmtlib and spdlog libraries, and the gargantuan -Qt, without which a back-end developer like me would never be able to get anything to display on a screen in a cross- -platform manner. - -In the end I find myself in the same place I was a year ago: My view is that the only thing C++ needs is a credible -dependency management tool. Given that, the developer community will produce and distribute all needed libraries, and -the most popular will naturally become the standard ones. - -# The Year Ahead - -Therefore, it is my intention this year to do what I can to bring more utility to the Boost ecosystem, where one -person can make a useful impact on the lives of developers, and taking personal responsibility for libraries is the -norm. - -## The Big Three - -It is my view that there are a number of areas where common use cases have not been well served in Boost. These are of -course JSON, Databases and HTTP clients. - -### JSON - -At the end of 2020, Vinnie and the team finally brought a very credible JSON library to Boost, which I have used to -write some cryptocurrency exchange connectors. On the whole, it's proven to be a very pleasant and -intuitive API. In particular the methods to simultaneously query the presence of a value and return a pointer on success -with `if_contains`, `if_string` etc. have seen a lot of use and result in code that is readable and neat. - -Currently, Boost.JSON favours performance over accuracy with respect to parsing floating point numbers (sometimes a -number is 1 ULP different to that which you'd expect from `std::strtod`). I had a little look to see if there was -a way to address this easily. It transpired not. Parsing decimal representations of floating point numbers into a binary -representation that will round-trip correctly is a hard problem, and beyond my level of mathematical skill. -There is currently work underway to address this in the JSON repo. - -There is also work in progress to provide JSON-pointer lookups. This will be a welcome addition as it will mean I can -throw away my jury-rigged "path" code and use something robust. - -### MySQL - -A very common database in use in the web world is of course MySQL. I have always found the official connectors somewhat -uninteresting, particularly as there is no asynchronous connector compatible with Asio. - -That was until a certain Rubén Pérez decided to write an asio-compatible mysql connector from scratch, using -none of the code in the Oracle connector. Rubén has started the arduous journey of getting his -[library](https://anarthal.github.io/boost-mysql/index.html) ready for Boost review. - -I have been asked to be the review manager for this work, something I am happy to do as, whether or not the admission is -ultimately accepted, I think the general principle of producing simple, self-contained libraries that meet a -common requirement is to be encouraged. - -If this library is successful, I would hope that others will rise to the challenge and provide native connectors for -other common database systems. - -### HTTP Client - -I mentioned in an earlier blog that an HTTP Client with a similar interface to Python's Requests library woudl be worked -on. As it happens, other priorities took over last year. This year I will be focussing efforts on getting this library -in shape for a proof of concept. - -### I mean Big Four - -Redis is ubiquitous in server environments. A quick search for Redis C++ clients turned up this -[little gem](https://github.com/basiliscos/cpp-bredis). I'd like to find time to give this a try at some point. - diff --git a/_posts/2021-02-15-dmitrys-january-update.md b/_posts/2021-02-15-dmitrys-january-update.md deleted file mode 100644 index d5057dac0..000000000 --- a/_posts/2021-02-15-dmitrys-january-update.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: post -nav-class: dark -categories: dmitry -title: Dmitry's January Update -author-id: dmitry ---- - -# Dmitry's January Update - -In January 2021 I started working on improving Boost.JSON for The C++ Alliance. -The time during this first month was mostly spent on getting familiar with the -project, particularly with its list of issues on GitHub. - -It turns out, half of the open issues are related to documentation. For -example, the section about conversions might need a rewrite, and related -entries in the reference need to provide more information. There should be more -examples for parsing customizations. There also needs to be a separate section -dedicated to library's named requirements. There was also a bug in coversion -customization logic that was fixed by me this month. - -The next two large blocks are issues related to optimization opportunities and -dealing with floating point numbers (some issues are present in both groups). -The next group is issues related to build and continuous integration. A couple -of build bugs were fixed in January. - -The final group consists of feature requests, mostly for convenient ways to -access items inside `json::value`. And this month I started implementing one -such feature -- Json.Pointer support. The work is still in the early stages, -though. diff --git a/_posts/2021-02-20-EmilsJanuaryUpdate.md b/_posts/2021-02-20-EmilsJanuaryUpdate.md deleted file mode 100644 index 91a37b9db..000000000 --- a/_posts/2021-02-20-EmilsJanuaryUpdate.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: post -nav-class: dark -categories: emil -title: Emil's January Update -author-id: emil ---- - -# History - -This is my first entry on the C++ Alliance web site. I am happy to have been invited to join the organization. - -I first talked to Vinnie on Slack, and was surprised to find that he is practically a neighbor. So we got in touch, had breakfast together, talked about C++ and life, the universe and everything. We became friends, he took care of our cats when we flew to Europe (pre-Covid), and I brought back some [Rakia](https://en.wikipedia.org/wiki/Rakia#Bulgaria) (next time I'll bring him some of my father's home-made stuff!) - -# (Boost) URL - -My immediate focus after joining the C++ Alliance is to work on what will hopefully become Boost.URL. C++ has long been missing a solid standard-conforming library for working with URLs, we often get contacted by impatient users asking about the state of things. - -Building on the foundation already in place and requiring only C++11, the library is being developed to the highest standards of portability, safety and efficiency. We're also careful to keep the design simple, with a lot of attention paid to minimizing physical coupling so that compilation is as fast as possible. - -# About - -I started coding in middle school on the Bulgarian-made Apple ][ clone [Правец-82](https://www.zdnet.com/article/how-these-communist-era-apple-ii-clones-helped-shape-central-europes-it-sector/) (named after -- you guessed it -- the birth place of the leader of the Bulgarian Communist Party at the time). This has resulted in a lot of useless information being engraved on my brain, I still remember some of the opcodes of the 6502 CPU. :) - -After graduating the Sofia University I moved to L.A. to work in the video game industry where I've spent most of my professional career, starting with the original Playstation all the way to modern consoles and handhelds. For the last few years I've shifted to projects that utilize my background in realtime graphics engines in areas that are not directly connected to gaming. - -Pretty much all of my projects have been written in C++. This includes several Boost libraries, the latest one being LEAF, a Lightweight Error Augmentation Framework for C++11. I'll probably post about error handling at some later time, as this is one of my key areas of interest. - -Thanks for reading. - -Emil Dotchevski diff --git a/_posts/2021-03-30-RichardsMarchUpdate.md b/_posts/2021-03-30-RichardsMarchUpdate.md deleted file mode 100644 index 848a2c12e..000000000 --- a/_posts/2021-03-30-RichardsMarchUpdate.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's February/March Update -author-id: richard ---- - -# Boost.PropertyTree is back in the saddle! - -Last month I took over the maintenance of the Boost Property Tree library. - -This library is in ubiquitous use but has been without a full time maintainer for some time. -There was a backlog of commits on develop that had not made it to master for the boost releases. -This is changing in upcoming 1.76 release. Property Tree PRs and issues will get more attention going forward. -In addition, Property Tree has gained CI courtesy of GitHub Actions. The test status can be seen -[here](https://github.com/boostorg/property_tree) - -# Beast CI - -Beast has acquired two new continuous integration pipelines. Drone and Github Actions. -Drone is the canonical source of truth for the Linux/Windows badge on the -[readme page](https://github.com/boostorg/beast) while GitHub Actions provides a more comprehensive matrix of -targets, C++ standards and compiler versions which I use during the development cycle. - -# Boost Version 1.76 - -The 1.76 release of boost is imminent. As far as Beast and Property Tree is concerned, this is a maintenance release. - -# Other Activities - -Much of the past two months has been devoted to resolving user queries, and examining suggestions for changes to Beast -and Property Tree. - -Changes to Property tree are not taken likely. It is used in a great deal of legacy code in the wild and there are few -tests. -It would be a shame to break existing code noisily, or worse, silently. -For better or worse, the behaviour is probably not going to change very much going forward until there are more tests in -place. - -Accepting changes to beast's behaviour or interface is something we consider very carefully for a different reason. -Beast is already a complex library. Like Asio upon which it is built, it is already extremely configurable, with a -myriad (actually potentially an unbounded set) of completion tokens, completion handlers and buffer types. - -Something I am often asked is whether we would consider a "multi-send" interface. For example: - -```cpp -std::vector messages; -ws.write(buffer, messages); -``` - -Arguably there are some efficiencies available here, since we could potentially build all the resulting websocket frames -in one pass, and send as one IO operation. - -There are some issues however. One is buffer space. What do we do if the buffer runs out of space while be are half way -through building the frames? Fail the operation, return a partial failure? Block? -Also, what do we do if the ensuing IO operation(s) partially fail(s)? Asio currently has no protocol to report a partial -failure. We would have to create such a protocol, test it and then teach it. - -In reality people's requirements are often different. Some people may require confirmation of each frame within a time -period, some will want all-or-nothing delivery and some are happy with partial delivery. - -One approach is to start providing options for such things as timeouts, partial completion, etc. But the feeling here is -that this starts to dictate to users of the library how they write code, which we feel is outside the remit of Beast. - -Perhaps there is a case for a function that separates out the building of a websocket frame from the sending of it. -I'll give it some thought. diff --git a/_posts/2021-04-30-RichardsAprilUpdate.md b/_posts/2021-04-30-RichardsAprilUpdate.md deleted file mode 100644 index 149678088..000000000 --- a/_posts/2021-04-30-RichardsAprilUpdate.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's April Update -author-id: richard ---- - -# A new responsibility - -Last month I took over the maintenance of the Boost Property Tree library. - -This library is in ubiquitous use but has been without a full time maintainer for some time. -There was a backlog of commits on develop that had not made it to master for the boost releases. -This is changing in upcoming 1.76 release. Property Tree PRs and issues will get more attention going forward. -In addition, Property Tree has gained CI courtesy of GitHub Actions. The test status can be seen -[here](https://github.com/boostorg/property_tree) - -# Beast CI - -Beast has acquired two new continuous integration pipelines. Drone and Github Actions. -Drone is the canonical source of truth for the Linux/Windows badge on the -[readme page](https://github.com/boostorg/beast) while GitHub Actions provides a more comprehensive matrix of -targets, C++ standards and compiler versions which I use during the development cycle. - -# Boost Version 1.76 - -The 1.76 release of boost is imminent. As far as Beast and Property Tree is concerned, this is a maintenance release. - diff --git a/_posts/2021-05-30-RichardsMayUpdate.md b/_posts/2021-05-30-RichardsMayUpdate.md deleted file mode 100644 index a67da3004..000000000 --- a/_posts/2021-05-30-RichardsMayUpdate.md +++ /dev/null @@ -1,468 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's May 2021 Update -author-id: richard ---- - -# The Month in Review - -It's been a month of minor maintenance fixes, and a fair amount of support requests via -the [C++ Alliance Slack workspace](https://cppalliance.org/slack/). - -## Property_tree - -On the maintenance front, there are a number of historic pull requests in the property_tree repo which need working -through. Some of these take some unravelling and a degree of care, as I am still new to this venerable library and I -have the impression that it is used fairly ubiquitously in many code bases of varying pedigree. - -Currently I have no way of reaching out to users (not knowing exactly who they are) so the only way to know whether a -change is going to break someone's build is to release it, by which time it is too late. - -I think the answer here is to start building out more test cases. Property_tree only recently gained CI, and so far I -have not gotten around to adding test coverage. No doubt I'll get to this in due course. - -## Beast - -There are a lot of eager developers out there keen to use Beast and Asio, which is encouraging. The less encouraging -thing is the amount of time I find myself spending giving ad-hoc support to people who have hit the Asio mental brick -wall (which I remember when learning this fantastic library all too well). - -I have written blogs in this series before covering some of the topics I think are important and developers often -misunderstand, but there is more to do. - -With this in mind, an idea has been germinating over the past few months, which finally started to develop into a new -library this month. I'll come back to this later. - -## Asio - -A few months ago I attended a WG21 meeting where a formal means of providing cancellation to asynchronous operations was -proposed. A few people at that meeting, myself included, were concerned that the proposal in its current form would -constrain the development style of asynchronous programs, making the fundamental objects a little more complex than they -often need to be. - -I have recognised that Asio needs a formal task cancellation mechanism for some time, this being the basis of the async -cancellation_token mentioned in a previous blog. - -I have been able to get some of Chris Kohlhoff's valuable time to discuss this to see whether there is a way to get -effortless cancellation into Asio without impacting performance or compiled size when cancellation is not required. - -Chris, as he is wont to do, made the rather brilliant connection that in Asio, a 1-shot cancellation token can be -associated with each asynchronous completion handler, with the default token type being a zero-cost abstraction of a -null cancellation token - i.e. one that will never invoke the stop callback. - -The general idea being that if you want an operation to be cancellable, you would invoke it like this: - -```cpp -// This is the signal object that you would use to -// cancel any operation that depends on one of its slots -asio::cancellation_signal sig; - -// some IO Object -timer t(ioc, chronons::seconds(5)); - -// perform an asynchronous operation bound to the cancellation signal -t.async_wait( - bind_cancellation_slot(sig.slot(), - [](system::error_code ec) - { - // if the signal is invoked, the timer's asynchronous operation will notice - // and the operation will complete with ec equal to asio::errors::operation_aborted - }); - -// signal the cancellation -sig.emit(): -``` - -The interesting thing about this is that the cancellation slot is associated with the asynchronous operation's handler. -This is not only useful for a library-provided asynchronous operation such as a timer wait. Because of the existence of -a function called `get_associated_cancellation_slot(handler)`, the current slot is available in any context where user -code has access to the current asynchronous completion handler. - -One such place is in a user-defined composed operation, and therefore by extension, a c++ coroutine running in the -context of an Asio executor. - -This now becomes possible: - -```cpp - -asio::awaitable -my_coro(some_async_op& op) -{ - // The cancellation state allowed us to detect whether cancellation has been requested - // It also allows us to register our own cancellation slot - auto cs = asio::this_coro::cancellation_state; - - // Create a new slot from the cancellation state and register a callback which will - // invoke our own custom cancel signal on the some_async_op - // note: A - auto slot = cs.slot(); - slot.emplace([&]{ op.cancel(); }); - - // continue to wait on the some_async_op - co_await op.wait_to_finish(); -} -``` - -This coroutine could be invoked in a couple of ways: - -```cpp - -// In this case the cancellation state is a no-op cancellation. -// the code at note A above will do nothing. This coroutine is not cancellable. -co_await asio::co_spawn(exec, - my_coro(op), - asio::use_awaitable); - -// In this case, the coroutine has become cancellable because the code at note A will actually -// create a functioning slot and register the lambda. -// The coroutine is cancellable through the cancellation signal sig. -asio::cancellation_signal sig; -co_await asio::co_spawn(exec, - my_coro(op), - asio::bind_cancellation_signal( - asio::use_awaitable, sig)); -``` - -This code is experimental at the moment, but is available -[on the generic-associators branch of Boost.Asio](https://github.com/boostorg/asio/tree/generic-associators). - -# The Project du Jour - -Coming back to the "Asio is hard at the beginning" meme, I was speaking to my son recently. He works with a number of -languages, including Python, Go and C++. - -During a conversation about these he mentioned that Go was a very uninspiring language (to him) but it was very easy to -get fairly complex asynchronous programs functioning reliably in a short amount of time. - -I asked him what the single most effective feature of the language was, to which he replied, "channels". - -For anyone who does not already know, a golang channel is simply a multi-producer, multi-consumer ring buffer with an -asynchronous interface. - -It has the following behaviour: - -- Producer coroutines will suspend when providing values to the channel if the ring buffer is full and there is no - consumer pending a consume operation. -- Consumer coroutines will suspend when consuming if the ring buffer is empty and there is no pending producer operation - in progress. -- The ring buffer capacity is specified upon construction, and may be zero. Producers and consumers of a zero-sized - channel will only make progress if there is a corresponding pair of producer and consumer pending at the same time. In - this way, the channel also acts as a coroutine synchronisation primitive. -- Finally, the channel may be closed. A closed channel will allow a consumer to consume remaining values in the ring - buffer, but it will not allow a producer to provide more values, whether into the ring buffer or directly to a pending - consume operation. A consume operation against an empty, closed channel will yield a default-constructed object plus a - boolean false indicating that there are no more values to consume. - -There are some other nice features in Go, such as the select keyword which interact with channels in a pleasing way, but -for now I'll focus on how we might implement the channel in asynchronous C++. - -The rationale here being: - -- Channels make writing complex asynchronous interactions simple. -- Make simple things simple is the mantra to which I subscribe. -- Perhaps C++ enthusiasts would benefit from an implementation of channels. -- Given the flexibility of C++, we might be able to do a better job than Go, at least in terms of giving the programmer - some choice over implementation tradeoffs. -- Maybe a little library offering this functionality in a simple, reusable way would be a useful addition to Boost. - -I put some feelers out in the CppLang slack. So far the response to the idea has been only positive. So I decided to -make a start. - -TLDR - you can monitor how far I am getting by looking at -the [Github repository](https://github.com/madmongo1/boost_channels). - -## Approach - -I wanted the channels library to be built on top of Asio. The reason for this is that I happen to think that the Asio -executor model is very elegant, and allows the programmer to transpose the same fundamental idea onto a number of -different concurrency strategies. For example, thread pools, IO loop, threads and futures, and so on. - -Asio's completion tokens allow the adaptation of asynchronous initiating functions to any or all of these strategies and -I wanted to make sure that the library will provide this functionality. - -Furthermore, asynchronous programs become complex quickly. Asio is a natural fit for IO, but does not provide the -primitives that programmers often find they need to create rich programs. - -It is my hope that this channels library provides people with a useful tool to make high performance, highly concurrent -programs easier to write in C++. - -## Design Decisions - -I have elected to write library in two sections. The first will contain the basic objects to handle the concurrent -communication and asynchronous completions. These objects will not be thread-safe, just like any other object in Asio. - -The second will be a thread-safe interface written in terms of the first. The truth is that Asio objects do not need to -be thread-safe if programmers use the correct discipline vis-a-vis strands and ensuring that work is dispatched to the -correct strand. Another truth is that many programmers just want things to be easy. So why not provide an easy-mode -interface too? - -## Comparison - -OK, so let's take a simple Go program and see how we could express that in terms of Asio and C++ coroutines. Now I'm no -expert, so I'm sure there are many ways to improve this program. It's about the third Go program I've ever written. -Please by all means let me know. - -```go -package main - -import ( - "fmt" - "sync" -) - -func produce(wg *sync.WaitGroup, c chan<- string) { - defer wg.Done() - c <- "The" - c <- "cat" - c <- "sat" - c <- "on" - c <- "the" - c <- "mat" - close(c) -} - -func consume(wg *sync.WaitGroup, name string, c <-chan string) { - defer wg.Done() - for { - s, more := <-c - if more { - fmt.Println(name, ":", s) - } else { - fmt.Println(name, ": Channel closed", name) - break - } - } -} - -// Main function -func main() { - var wg sync.WaitGroup - wg.Add(4) - c := make(chan string) - go consume(&wg, "a", c) - go consume(&wg, "b", c) - go consume(&wg, "c", c) - go produce(&wg, c) - wg.Wait() -} -``` - -And this is how I would envision it would look in the first cut of the C++ version: - -```cpp -auto -produce(channels::channel< std::string > &c) - -> asio::awaitable< void > -{ - constexpr auto wait = asio::use_awaitable; - co_await c.async_send("The", wait); - co_await c.async_send("cat", wait); - co_await c.async_send("sat", wait); - co_await c.async_send("on", wait); - co_await c.async_send("the", wait); - co_await c.async_send("mat", wait); - c.close(); -} - -auto -consume(std::string_view name, channels::channel< std::string > &c) - -> asio::awaitable< void > -{ - auto ec = channels::error_code(); - auto tok = asio::redirect_error(asio::use_awaitable, ec); - for (;;) - { - auto s = co_await c.async_consume(tok); - if (ec) - { - std::cout << name << " : " << ec.message() << "\n"; - break; - } - else - std::cout << name << " : " << s << "\n"; - } -} - -int -main() -{ - auto ioc = asio::io_context(); - auto c = channels::channel< std::string >(ioc.get_executor()); - - asio::co_spawn(ioc, consume("a", c), asio::detached); - asio::co_spawn(ioc, consume("b", c), asio::detached); - asio::co_spawn(ioc, consume("c", c), asio::detached); - asio::co_spawn(ioc, produce(c), asio::detached); - - ioc.run(); -} -``` - -One example of the output of the Go program (the order is actually nondeterministic) is: - -```text -a : The -a : cat -b : sat -b : mat -b : Channel closed b -a : on -a : Channel closed a -c : the -c : Channel closed c -``` - -while the output of the C++ program is a more deterministic: - -```text -a : The -b : cat -c : sat -a : on -b : the -c : mat -a : Channel is closed -b : Channel is closed -c : Channel is closed -``` - -I'm not an expert in Go by any means but I imagine the nondeterminism in the Go program is in part due to the fact that -the goroutine implementation is allowed to take shortcuts to consume data synchronously if it's available. The Asio -model requires that each completion handler is invoked as-if by a call to `post(handler)`. In this program, these posts -are being made to a single-threaded io_context and so are being executed sequentially, preserving the order of -invocation during execution. - -If this program were multi-threaded, it might be a different story. But this will have to wait until the basic -single-threaded implementation is complete. - -## Implementation Details - -The implementation of the channel is actually fairly straightforward. The asynchronous initiation interfaces are -standard asio, e.g.: - -```cpp -template < class ValueType, class Executor > -template < BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code)) SendHandler > -BOOST_ASIO_INITFN_RESULT_TYPE(SendHandler, void(error_code)) -channel< ValueType, Executor >::async_send(value_type value, - SendHandler &&token) -{ - if (!impl_) [[unlikely]] - BOOST_THROW_EXCEPTION(std::logic_error("channel is null")); - - return asio::async_initiate< SendHandler, void(error_code) >( - [value = std::move(value), this](auto &&handler) { - auto send_op = detail::create_channel_send_op( - std::move(value), - this->impl_->get_executor(), - std::forward< decltype(handler) >(handler)); - impl_->notify_send(send_op); - }, - token); -} -``` - -The macros are supplied by Asio and simply ensure that the most up-to-date compiler facilities are used to ensure that -the completion token/handler has the correct signature. `BOOST_ASIO_INITFN_RESULT_TYPE` deduces the return type of the -selected specialisation of `async_initiate`. It is what ensures that `async_send` returns an awaitable when the -completion token is of type `asio::use_awaitable`, or a `std::future` if we were to pass in `asio::use_future`. - -The actual work of the send is performed in the implementation class: - -```cpp - void - notify_send(detail::channel_send_op_concept< ValueType > *send_op) - { - // behaviour of send depends on the state of the implementation. - // There are two states, running and closed. We will be in the closed - // state if someone has called `close` on the channel. - // Note that even if the channel is closed, consumers may still consume - // values stored in the circular buffer. However, new values may not - // be send into the channel. - switch (state_) - { - case state_running: - [[likely]] if (consumers_.empty()) - { - // In the case that there is no consumer already waiting, - // then behaviour depends on whether there is space in the - // circular buffer. If so, we store the value in the send_op - // there and allow the send_op to complete. - // Otherwise, we store the send_op in the queue of pending - // send operations for later processing when there is space in - // the circular buffer or a pending consume is available. - if (free()) - push(send_op->consume()); - else - senders_.push(send_op); - } - else - { - // A consumer is waiting, so we can unblock the consumer - // by passing it the value in the send_op, causing both - // send and consume to complete. - auto my_receiver = std::move(consumers_.front()); - consumers_.pop(); - my_receiver->notify_value(send_op->consume()); - } - break; - case state_closed: - // If the channel is closed, then all send operations result in - // an error - [[unlikely]] send_op->notify_error(errors::channel_closed); - break; - } - } -``` - -An interesting feature of the send operation class is that when it is instructed to complete, it must: - -- Move the value out of itself, -- Move the completion handler out of itself, -- Destroy itself, returning memory back to the allocator. -- Post the completion handler to the correct executor. -- Return the value. - -The order is important. Later on we will be adding Asio allocator awareness. In order to maximise efficiency, Asio -asynchronous operations must free their memory back to the allocator before completing. This is so that during the -execution of the completion handler, the same memory that was just freed into asio's special purpose allocators will be -allocated and used to compose the next completion handler. This memory will be at the head of the allocator's list of -free blocks (and therefore found first) and it will be in cached memory, having just been touched. - -```cpp -template < class ValueType, class Executor, class Handler > -auto -basic_channel_send_op< ValueType, Executor, Handler >::consume() -> ValueType -{ - // move the result value to the local scope - auto result = std::move(this->value_); - - // move the handler to local scope and transform it to be associated with - // the correct executor. - auto handler = ::boost::asio::bind_executor( - std::move(exec_), - [handler = std::move(handler_)]() mutable { handler(error_code()); }); - - // then destroy this object (equivalent to delete this) - destroy(); - - // post the modified handler to its associated executor - asio::post(std::move(handler)); - - // return the value from the local scope to the caller (but note that NRVO - // will guarantee that there is not actually a second move) - return result; -} -``` - -That's all for now. I'll add extra blog entries as and when I make any significant progress to the library. - -In the meantime, I'm always happy to receive queries by email or as issues in the github repo. - -Thanks for reading. - -Richard Hodges
-for C++ Alliance
-[hodges.r@gmail.com](mailto:hodges.r@gmail.com) diff --git a/_posts/2021-10-10-RichardsOctoberUpdate.md b/_posts/2021-10-10-RichardsOctoberUpdate.md deleted file mode 100644 index 058dcd633..000000000 --- a/_posts/2021-10-10-RichardsOctoberUpdate.md +++ /dev/null @@ -1,455 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's October Update -author-id: richard ---- - -# Aims and Objectives - -This blog is presented in two sections. - -The first is a general discussion about completion tokens. - -The second is a practical demonstration of a production-grade completion token which adds significant utility to any -asynchronous operation that supports the new cancellation feature that arrived in Asio 1.19 (Boost 1.77). - -This blog ships with an accompanying github repository in case you want to play with examples. -The repository is [here](https://github.com/madmongo1/blog-2021-10). - -# Asio and the Power of Completion Tokens - -Asio (available [standalone](https://think-async.com/Asio/) and [in Boost](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio.html)) -defines a pattern for writing asynchronous operations. There have been a few examples in my blogs of custom composed -operations written in terms of several asynchronous operations made available by the library. - -Another often overlooked feature of Asio is the ability to define a customisation point which defines the "behaviour -during initiation and the result type" of the asynchronous initiating function. - -But what does that mean? - -Well, consider the following code: - -```c++ -/* void */ asio::async_read(sock, buffer, "\r\n", [](error_code ec, std::size_t n) { /* handler */ }); -``` - -This is a verbatim (you could say old-style) asynchronous initiating function which will read from the socket into the -buffer until either: -- The buffer is full, or -- the sequence `\r\n` is found in the input stream, or -- There is some other IO error. - -Whichever happens, the lambda is called in the context of the _associated executor_ of the socket. - -(Let's call this "_the operation_") - -The operation is started immediately and the lambda will be invoked at some point in the future once the operation is -complete. The initiating function returns `void`. - -Now consider: - -```c++ -auto n = co_await asio::async_read(sock, "\r\n", asio::use_awaitable); -``` - -This code is using the same _initiating function_ to invoke initiate the same _asynchronous operation_. However, this -time instead of providing a _Completion Handler_ we have provided a _Completion Token_. - -The only difference in the two invocations is the presence of the token. The actual asynchronous operation is the same -in both cases. - -However, now invocation of _the operation_ has been modified such that: -- The initiating function returns an `asio::awaitable` which can be `co_await`ed. -- The initiating function has been transformed into a C++20 coroutine. -- The operation will not commence until the returned awaitable has been `co_await`ed. - -We can say that the completion token has implemented a customisation point at both the initiation step and the -completion step. - -(For great notes on completion step I would recommend reading one of the [many excellent papers](https://isocpp.org/files/papers/P2444R0.pdf), -[follow-ups](https://isocpp.org/files/papers/P2469R0.pdf) or -[videos](https://www.youtube.com/watch?v=icgnqFM-aY4&t=1129s)), published by Chris Kohlhoff - the author of Asio. - -Here is another interesting example: - -```c++ -using asio::experimental::deferred; -using asio::use_awaitable; - -auto my_op = asio::async_read(sock, "\r\n", deferred); -... -auto n = co_await my_op(use_awaitable); -``` - -In this case, the `async_read` initiating function has been invoked with the `deferred` _completion token_. This token -has two side effects: -- The _asynchronous operation_ is not actually initiated, and -- It changes the return type to be an invocable object which when called will behave as if you called the initiating function. - -The returned invocable object is a unary function object whose argument is a _completion token_, which means that the -operation can be further customised at the point of use. You can think of it as an asynchronous packaged tasks awaiting -one last customisation before use. - -Note that as long as the packaged asynchronous operation is started with reference arguments or lightweight copyable -arguments, it can be re-used and copied. All arguments of Asio and Beast initiating functions -conform to this principle. The original design decision of passing buffers and return values by reference to -asynchronous operations was to ensure that when operations are composed, they do not allocate memory - the caller can -specify the memory management strategy. It so happens that this design decision, taken something like 16 years ago, -has enabled efficient composition of completion tokens. - -Finally, on the subject of `deferred`, deferring a deferred initiating function yields the same deferred initiating -function. I guess one way to think about completion tokens is that they are transforms or higher order functions -for manipulating the initiation and result types of asynchronous operations. - -example: - -```c++ -asio::awaitable -reader(asio::ip::tcp::socket sock) -{ - using asio::experimental::deferred; - using asio::use_awaitable; - using asio::experimental::as_tuple; - - // An easy but not efficient read buffer - std::string buf; - - // created the deferred operation object - auto deferred_read = async_read_until( - sock, - asio::dynamic_buffer(buf), - "\n", - deferred); - - // deferring a deferred operation is a no-op - auto deferred_read2 = deferred_read(deferred); - - // tokens are objects which can be composed and stored for later - // The as_tuple token causes the result type to be reported as a - // tuple where the first element is the error type. This prevents - // the coroutine from throwing an exception. - const auto my_token = as_tuple(use_awaitable); - - bool selector = false; - for(;;) - { - // use each deferred operation alternately - auto [ec, n] = co_await [&] { - selector = !selector; - if (!selector) - return deferred_read(my_token); - else - return deferred_read2(my_token); - }(); - if (ec) - { - std::cout << "reader finished: " << ec.message() << "\n"; - break; - } - auto view = std::string_view(buf.data(), n - 1); - std::cout << "reader: " << view << "\n"; - buf.erase(0, n); - } -} -``` - -A table of completion tokens packaged with Asio is presented here: - -|token|Initiation Policy|Completion Behaviour/Result Type|Notes| -|-----|-----------------|--------------------------------|-----| -| [detached](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/detached.html) | Initiate immediately | Ignore all errors and results | When used with `co_spawn`, causes the spawned asynchronous chain of coroutines to have behaviour analogous to a detached thread. | -| [experimental::deferred](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/experimental__deferred.html) | Do not initiate | Return a function object which when invoked with a completion token, behaves as if the original initiating function was called with that same token | Analogous to an asynchronous packaged task. | -| [use_future](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/use_future.html) | Initiate immediately | Return a std::future which will yield the completion arguments | | -| [use_awaitable](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/use_awaitable.html) | Initiate when awaited | Return an awaitable object yield the completion arguments when `co_await`ed| | -| [yield_context](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/yield_context.html) | Initiate immediately | Yield the current stackful coroutine. Once the operation is complete, resume and return the handler arguments | | -| [as_tuple(token)](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/experimental__as_tuple.html) | Initiate as indicated by the supplied `token` | Modify the completion arguments to be a single tuple of all arguments passed to the completion handler. For example, `void(error_code, size_t)` becomes `void(tuple)`. In practical terms this token ensures that partial success can be communicated through `use_future`, `use_awaitable` and `yield`| Very useful when used with `yield`, `use_future` or `use_awaitable` if we'd like to handle the error without exception handling or when a partial success must be detected. For example, the error_code might contain `eof` but `size` might indicate that 20 bytes were read into the buffer prior to detecting the end of stream condition. | -| [redirect_error(token, &ec)](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/redirect_error.html) | Initiate as indicated by the supplied `token` | For operations whose first completion handler argument is an `error_code`, modify the completion handler argument list to remove this argument. For example, `void(error_code, size_t)` becomes `void(size_t)`. The error code is redirected to object referenced by `ec`| Similar to the above use, but allows overwriting the same `error_code` object which can be more elegant in a coroutine containing multiple calls to different initiating functions. | -| [experimental::as_single(token)](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/experimental__as_single.html) | Initiate as indicated by the supplied `token` | Similar to `as_tuple` except in the case where the only argument to the completion handler is an error. In this case, the completion handler arguments are unaltered. | Experience of use suggests to me that this token is less useful than `redirect_error` and `as_tuple`. | -| [experimental::append(token, values...)](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/experimental__append.html) | Initiate as indicated by the supplied `token` | When the completion handler is invoked, the `values...` arguments are appended to the argument list. | Provides a way to attaching more information to a completion handler invocation. [examples](https://github.com/madmongo1/blog-2021-10/blob/master/append_prepend.cpp)| -| [experimental::prepend(token, values...)](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/experimental__prepend.html) | Initiate as indicated by the supplied `token` | When the completion handler is invoked, the `values...` arguments are prepended to the argument list. | Provides a way to attaching more information to a completion handler invocation. [examples](https://github.com/madmongo1/blog-2021-10/blob/master/append_prepend.cpp) | - -# A Custom Completion Token - -All very interesting and useful, no doubt. But what if we wanted to do something more clever. - -The other day I was speaking to Chris about timed cancellation. Now there are ways of doing timed cancellation that in -Chris' view are correct and maximally performant (covered in [this video](https://www.youtube.com/watch?v=hHk5OXlKVFg)). -However many users don't need maximum performance. What they often want is maximum teachability or maintainability. - -So I posed the question: "Imagine I wanted a function which took any existing Asio composed asynchronous operation and -produced a new composed operation which represented that same operation with a timeout. How would I do that?" - -For example, imagine we had a deferred read operation: - -```c++ - auto read_op = async_read(stream, buffer, deferred); -``` - -Which we can invoke in a coroutine like so: - -```c++ - co_await read_op(use_awaitable); -``` - -imagine we could write: - -```c++ - co_await with_timeout(read_op, 5s, use_awaitable); -``` - -or to write it out in full: - -```c++ - co_await with_timeout( - async_read(stream, buffer, deferred), - 5s, - use_awaitable); -``` - -The answer that came back was to me quite surprising: "It starts with a completion token". - -Which means that the way to achieve this is to write the `with_timeout` function in terms of a composition of completion -tokens: - -```c++ -template -auto with_timeout(Op op, std::chrono::milliseconds timeout, CompletionToken&& token) -{ - return std::move(op)(timed(timeout, std::forward(token))); -} -``` - -In the above code, `timed` is a function that returns a parameterised completion token. It will look something like this: -```c++ -template -timed_token -timed(std::chrono::milliseconds timeout, CompletionToken&& token) -{ - return timed_token{ timeout, token }; -} -``` -The actual token type would look like this: -```c++ -template -struct timed_token -{ - std::chrono::milliseconds timeout; - CompletionToken& token; -}; -``` - -So far, so simple. But how will this work? - -Well, remember that a completion token controls the injection of logic around an asynchronous operation. So somehow by -writing the token, we will get access to the packaged operation prior to it being initiated and we get access to the -following building blocks of the async operation provided by Asio's initiation pattern: -- The _initiation_ - this is a function object that will actually initiate the packaged asynchronous operation, and -- The _initiation arguments_ - the arguments that were supplied to the initial initiation function. In our example above, -these would be `stream` and `buffer` - -Note that the _initiation_ is an object that describes how to launch the underlying asynchronous operation, plus -associated data such as the [_associated executor_](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/get_associated_executor.html), -[_associated allocator_](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/get_associated_allocator.html) -and [_associated cancellation slot_](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/get_associated_cancellation_slot.html). - -In Asio, the customisation point for initiating an asynchronous operation with a given completion token is the template -class [`async_result`](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/async_result.html). - -Here is the specialisation: -```c++ -// Specialise the async_result primary template for our timed_token -template -struct asio::async_result< - timed_token, // specialised on our token type - Signatures...> -{ - // The implementation will call initiate on our template class with the - // following arguments: - template - static auto initiate( - Initiation&& init, // This is the object that we invoke in order to - // actually start the packaged async operation - timed_token t, // This is the completion token that - // was passed as the last argument to the - // initiating function - InitArgs&&... init_args) // any more arguments that were passed to - // the initiating function - { - // we will customise the initiation through our token by invoking - // async_initiate with our own custom function object called - // timed_initiation. We will pass it the arguments that were passed to - // timed(). We will also forward the initiation created by the underlying - // completion token plus all arguments destined for the underlying - // initiation. - return asio::async_initiate( - timed_initiation{}, - t.token, // the underlying token - t.timeout, // our timeout argument - std::forward(init), // the underlying operation's initiation - std::forward(init_args)... // that initiation's arguments - ); - } -}; -``` - -It's a bit of a wall of text, but most of that is due to my comments and C++'s template syntax. In a nutshell, what this -class is doing is implementing the function which wraps the initiation of the underlying operation (i.e the async_read) -in an outer custom initiation which is going to add a timeout feature. - -All that remains is to define and implement `timed_initiation<>`, which is nothing more than a function object. We could -have written it inline as a lambda, but for clarity it has been broken out into a separate template. - -[`async_initate`](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/async_initiate.html) looks -complicated but in actual fact is doing a simple transformation: - -Given: -- `tok` is a _CompletionToken_ -- `Signatures...` is a type pack of function signatures that are required to be supported by a _CompletionHandler_ built -from `tok`. -- `initiation` is a function object -- `args...` is a set of arbitrary arguments - -`async_initiate` is a helper function which calls `async_result<>::initiate()`. Calling this will first transform `tok` -into a _CompletionHandler_ which we will call `handler`. Then it will simply call `initiation(handler, args...)`. i.e. -it will invoke the `initiation` with the correct completion handler and any other arguments we happen to give it. - -```c++ -// Note: this is merely a function object - a lambda. -template -struct timed_initiation -{ - template < - typename CompletionHandler, - typename Initiation, - typename... InitArgs> - void operator()( - CompletionHandler handler, // the generated completion handler - std::chrono::milliseconds timeout, // the timeout specified in our completion token - Initiation&& initiation, // the embedded operation's initiation (e.g. async_read) - InitArgs&&... init_args) // the arguments passed to the embedded initiation (e.g. the async_read's buffer argument etc) - { - using asio::experimental::make_parallel_group; - - // locate the correct executor associated with the underling operation - // first try the associated executor of the handler. If that doesn't - // exist, take the associated executor of the underlying async operation's handler - // If that doesn't exist, use the default executor (system executor currently) - auto ex = asio::get_associated_executor(handler, - asio::get_associated_executor(initiation)); - - // build a timer object and own it via a shared_ptr. This is because its - // lifetime is shared between two asynchronous chains. Use the handler's - // allocator in order to take advantage of the Asio recycling allocator. - auto alloc = asio::get_associated_allocator(handler); - auto timer = std::allocate_shared(alloc, ex, timeout); - - // launch a parallel group of asynchronous operations - one for the timer - // wait and one for the underlying asynchronous operation (i.e. async_read) - make_parallel_group( - // item 0 in the group is the timer wait - asio::bind_executor(ex, - [&](auto&& token) - { - return timer->async_wait(std::forward(token)); - }), - // item 1 in the group is the underlying async operation - asio::bind_executor(ex, - [&](auto&& token) - { - // Finally, initiate the underlying operation - // passing its original arguments - return asio::async_initiate( - std::forward(initiation), token, - std::forward(init_args)...); - }) - ).async_wait( - // Wait for the first item in the group to complete. Upon completion - // of the first, cancel the others. - asio::experimental::wait_for_one(), - - // The completion handler for the group - [handler = std::move(handler), timer]( - // an array of indexes indicating in which order the group's - // operations completed, whether successfully or not - std::array, - - // The arguments are the result of concatenating - // the completion handler arguments of all operations in the - // group, in retained order: - // first the steady_timer::async_wait - std::error_code, - - // then the underlying operation e.g. async_read(...) - auto... underlying_op_results // e.g. error_code, size_t - ) mutable - { - // release all memory prior to invoking the final handler - timer.reset(); - // finally, invoke the handler with the results of the - // underlying operation - std::move(handler)(std::move(underlying_op_results)...); - }); - } -}; -``` - -Now that the token and its specialisation of `async_result` is complete, we can trivially write a timed read from console -that won't throw as a coroutine in one line: - -```c++ - // using the completion token direct - auto [ec1, n1] = co_await async_read_until(in, dynamic_buffer(line), '\n', - as_tuple(timed(5s, use_awaitable))); - - // using the function form - auto [ec2, n2] = co_await with_timeout( - async_read_until(in, asio::dynamic_buffer(line), '\n', deferred), - 5s, - as_tuple(use_awaitable)); -``` - -The full code for this example is [here](https://github.com/madmongo1/blog-2021-10/blob/master/timed.cpp). -Note that this example is written in terms of a posix console stream. -To demonstrate on Windows, you would need to replace the `posix::stream_descriptor in(co_await this_coro::executor, ::dup(STDIN_FILENO));` -with a stream type compatible with Windows, such as a socket or named pipe... or even adapt the example to use a Beast -`http::async_read` - and presto! You have a ready-made HTTP server which applies a timeout to reading messages. - -Update 2021-10-11: I have since modified the example so that on windows a local tcp socket pair is created and a -coroutine is spawned to handle the input side of things. The demo now compiles and runs with MSVC2019. - -# A Note on Performance - -It is important that I point out that this example token has been written with ease of use as the primary motivating -factor. There is a pessimisation in its design in that use of the token allocates a new timer for every -asynchronous operation where the timeout is being applied. This of course becomes completely un-necessary if we redesign -the token so that we pass a reference to an existing timer to its construction function. - -The call-site would then look more like this: -```c++ - auto timer = asio::steady_timer(co_await this_coro::executor, 5s); - auto [ec1, n1] = co_await async_read_until(in, dynamic_buffer(line), '\n', - as_tuple(use_deadline(timer, use_awaitable))); -``` - -Writing it this way would actually result in a simpler initiation and would ensure that the general Asio principle of -giving the caller control over object lifetimes and allocation strategies is maintained. - -Another way to avoid repeated allocations of the timer while retaining the "easy mode" interface is to make use of -Asio's execution context service facility. In this way timers would be cached in the service object associated with the -associated executor's [`execution_context`](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/reference/execution_context.html). - -Asio was originally designed for highly scalable and latency-sensitive applications such as used in the finance, -automotive and defence industries. Out of the box it provides the basic building blocks with which to assemble -performance and memory-critical applications. However as it has become more widely adopted there is a growing demand for -"easy mode" interfaces for people who just want to get things done. - -This message has not gone unheard. I would expect a number of interesting new features to be added to the library in -short order. - -Thanks for reading. - -Richard Hodges
-for C++ Alliance
-[hodges.r@gmail.com](mailto:hodges.r@gmail.com) diff --git a/_posts/2022-03-12-RFRM-on-CppCast.md b/_posts/2022-03-12-RFRM-on-CppCast.md deleted file mode 100644 index 8c5611da9..000000000 --- a/_posts/2022-03-12-RFRM-on-CppCast.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: post -nav-class: dark -categories: rene -title: CppCast; New C++ Scope and Debugging Support -author-id: rene ---- - -This week I got the opportunity to chat with Rob and Jason about the -[open letters](https://github.com/grafikrobot/cpp_scope) to the C++ committee -I wrote on [CppCast](https://cppcast.com/standard-tooling-debugging/). -The letters propose to expand the scope of the C++ standards work to include -the ecosystem of tools and related technologies. diff --git a/_posts/2022-08-10-RichardsAugustUpdate.md b/_posts/2022-08-10-RichardsAugustUpdate.md deleted file mode 100644 index 1d2d950a3..000000000 --- a/_posts/2022-08-10-RichardsAugustUpdate.md +++ /dev/null @@ -1,677 +0,0 @@ ---- -layout: post -nav-class: dark -categories: richard -title: Richard's August Update -author-id: richard ---- - -# Beast and HTTP Redirect - -Some months ago, I was asked how to handle HTTP redirect responses in beast. -Characteristically, I took a moment to model how I would do that in my head, waved my hands and kind of explained it and -that was that. - -Then more recently, someone else asked how beast websockets would handle a redirect response when performing a websocket -handshake. Now I'm pretty sure that websocket clients have no requirement at all to follow redirects. I believe the -WebSocket specification does not allow for such things, but I thought it would be an interesting exercise to cover the -topic and provide a working code example and cover it in a blog post. - -There are a few reasons I decided to do this: -- Redirects are going to be important for any client-side framework written on Beast. -- There are a few new features in Asio which I thought it would be interesting to showcase. - -## Code Repositiory - -The code for this blog can be found [here](https://github.com/madmongo1/blog-2022-Aug-websock-redirect). -I have tested it on Fedora 36 and GCC-12. The code requires at least boost-1.80.0.beta1, because it takes advantage of -the new change in Asio, which allows the deferred object returned by the `asio::deferred` completion token to be -directly `co_await`ed. This provides a significant improvement in performance for operations that don't need the full -functionality of the `asio::awaitable<>` type. - -## Handling a Redirect - General Case - -Redirects can be followed with the following generalised algorithm: - -``` - -set redirect count to 0 - -while not connected, no egregious errors and redirect limit has not been exceeded: - - crack URL - resolve the FQDN of the host specified in the URL - connect to the host - - if URL indicates HTTPS: - negotiate TLS - endif - - send http request (upgrade request for websocket) - - await response - - if response is 200ish: - exit success - elseif response is redirect: - increment redirect count - update URL with data found in Location header field - continue - else - exit error - endif - -endwhile - -``` - -## Handling a Redirect in C++ with Beast - -It turns out that the entire process can be handled in one coroutine. -Now remember that an HTTP connection can redirect to an HTTPS connection. So the "connection" type returned from a -coroutine that creates a connection, having taken into account any redirects, must handle both transport types. - -It's worth mentioning at this point that if you're writing for a modern Linux kernel, TLS is now supported natively -by the berkley sockets interface. This means that programs need no longer generate one code path for SSL and one for -TCP. If this is interesting to you, there is some documentation [here](https://www.kernel.org/doc/html/v5.12/networking/tls.html). -When I get a moment I will create a modified copy of this program that uses Kernel TLS. However, for now, we do it the -old-fashioned portable way. - -### A connection abstraction - -First we define some useful types for our variant implementation -```cpp -struct websock_connection -{ - using ws_stream = beast::websocket::stream< tcp::socket >; - using wss_stream = beast::websocket::stream< ssl::stream< tcp::socket > >; - using var_type = boost::variant2::variant< ws_stream, wss_stream >; -``` -...provide TLS and SSL constructors... - -```cpp - websock_connection(tcp::socket sock) - : var_(ws_stream(std::move(sock))) - { - } - - websock_connection(ssl::stream< tcp::socket > stream) - : var_(wss_stream(std::move(stream))) - { - } -``` - -...provide access to the underlying (optional) SSL and TCP streams... - -```cpp - tcp::socket & - sock(); - - ssl::stream< tcp::socket > * - query_ssl(); - -``` - -... provide functions that return awaitables for the high level functions we will need... - -```cpp - asio::awaitable< void > - try_handshake(error_code &ec, - beast::websocket::response_type &response, - std::string hostname, - std::string target); - - asio::awaitable< std::size_t > - send_text(std::string const &msg); - - asio::awaitable< std::string > - receive_text(); - - asio::awaitable< void > - close(beast::websocket::close_reason const &reason); - -``` -...and finally the implementation details... -```cpp - var_type var_; - beast::flat_buffer rxbuffer_; -}; -``` -The implementation of the various member functions are then all defined in terms of `visit`, e.g.: - -```cpp -asio::awaitable< std::size_t > -websock_connection::send_text(std::string const &msg) -{ - using asio::use_awaitable; - - return visit( - [&](auto &ws) - { - ws.text(); - return ws.async_write(asio::buffer(msg), use_awaitable); - }, - var_); -} -``` -Note that this function is not actually a coroutine. Since it doesn't maintain any state during the async operation, -the function can simply return the `awaitable` to the calling coroutine. This saves the creation of a coroutine frame -when we don't need it. - -The interface and implementation for this class can be found in `websocket_connection.[ch]pp` in the git repo linked -above. - -### Moveable `ssl::stream`? - -You may have noticed something in this constructor: -```cpp - websock_connection(ssl::stream< tcp::socket > stream) - : var_(wss_stream(std::move(stream))) - { - } -``` -I have `std::move`'d the ssl stream into the WebSocket stream. Until a few versions ago, asio ssl streams were not -moveable, which caused all kinds of issues when wanting to, for example, upgrade an SSL stream connection to a secure -websocket stream. - -The Beast library has two workarounds for this: -1. Beast provides its own version of ssl::stream, and -2. `beast::websocket::stream` has a specialisation defined which holds a reference to a stream. - -These are probably now un-necessary and could arguably be deprecated. - -## The algorithm in C++20 - -```cpp -asio::awaitable< std::unique_ptr< websock_connection > > -connect_websock(ssl::context &sslctx, - std::string urlstr, - int const redirect_limit = 5) -{ - using asio::experimental::deferred; - - // for convenience, take a copy of the current executor - auto ex = co_await asio::this_coro::executor; - - // number of redirects detected so far - int redirects = 0; - - // build a resolver in order tp decode te FQDNs in urls - auto resolver = tcp::resolver(ex); - - // in the case of a redirect, we will resume processing here -again: - fmt::print("attempting connection: {}\n", urlstr); - - // decode the URL into components - auto decoded = decode_url(urlstr); -``` -This part of the code builds a unique pointer to an initialised `websocket_connection` object, initialised with either -an SSL stream or a TCP stream as indicated by the result of cracking the URL. For brevity I have used a regex to crack -the URL, but you should check out Vinnie Falco's new Boost.URL candidate library [here](https://github.com/CPPAlliance/url). -Vinnie will be looking for reviewers during this library's submission to Boost later this month, so do keep an eye out -in the Boost mailing list. - -```cpp - // build the appropriate websocket stream type depending on whether the URL - // indicates a TCP or TLS transport - auto result = decoded.transport == transport_type::tls - ? std::make_unique< websock_connection >( - ssl::stream< tcp::socket >(ex, sslctx)) - : std::make_unique< websock_connection >(tcp::socket(ex)); - -``` - -Here we are awaiting a connect operation with the result of awaiting a resolve operation. Note the use of -`asio::experimental::deferred`. `deferred` is quite a versatile completion token which can be used to: -- return an lightweight awaitable, as demonstrated here, -- return a function object which may be later called multiple times with another completion handler; - effectively creating a curried initiation, -- be supplied with a completion handler up front in order to create a deferred sequence of chained asynchronous - operations; allowing simple composed operations to be built quickly and easily. - -```cpp - // connect the underlying socket of the websocket stream to the first - // reachable resolved endpoint - co_await asio::async_connect( - result->sock(), - co_await resolver.async_resolve( - decoded.hostname, decoded.service, deferred), - deferred); -``` - -In the case that the endpoint we are connecting to is secure, we must do the SSL/TLS handshake: - -```cpp - // if the connection is TLS, we will want to update the hostname - if (auto *tls = result->query_ssl(); tls) - { - if (!SSL_set_tlsext_host_name(tls->native_handle(), - decoded.hostname.c_str())) - throw system_error( - error_code { static_cast< int >(::ERR_get_error()), - asio::error::get_ssl_category() }); - co_await tls->async_handshake(ssl::stream_base::client, deferred); - } - - // some variables to receive the result of the handshake attempt - auto ec = error_code(); - auto response = beast::websocket::response_type(); -``` -The function try_handshake simply initiates the form of websocket handshake operation which preserves the -http response returned from the server. We will need this in case the websocket connection response is actually a -redirect. - -```cpp - // attempt a websocket handshake, preserving the response - fmt::print("...handshake\n"); - co_await result->try_handshake( - ec, response, decoded.hostname, decoded.path_etc); - - // in case of error, we have three scenarios, detailed below: - if (ec) - { - fmt::print("...error: {}\n{}", ec.message(), stitch(response.base())); - auto http_result = response.result_int(); - switch (response.result()) - { -``` - -And here is the code that handles the actual redirect. Note that in this simplistic implementation, I am replacing the -URL with the `Location` field in the web server's response. In reality, the returned URL could be a relative URL which -would need to be merged into the original URL. [Boost.URL](https://github.com/CPPAlliance/url) handles this nicely. -Once that library is available I'll upgrade this example. - -```cpp - case beast::http::status::permanent_redirect: - case beast::http::status::temporary_redirect: - case beast::http::status::multiple_choices: - case beast::http::status::found: - case beast::http::status::see_other: - case beast::http::status::moved_permanently: - // - // Scenario 1: We have been redirected - // - if (response.count(beast::http::field::location)) - { - if (++redirects <= redirect_limit) - { - // perform the redirect by updating the URL and jumping to - // the goto label above. - auto &loc = response[beast::http::field::location]; - urlstr.assign(loc.begin(), loc.end()); - goto again; - } - else - { - throw std::runtime_error("too many redirects"); - } - } - else - { - // - // Scenario 2: we have some other HTTP response which is not an - // upgrade - // - throw system_error(ec, - stitch("malformed redirect\r\n", response)); - } - break; - - default: - // - // Scenario 3: Some other transport error - // - throw system_error(ec, stitch(response)); - } - } - else - { - // - // successful handshake - // - fmt::print("...success\n{}", stitch(response.base())); - } - - co_return result; -} -``` - -So with that written, all we need to do is write a simple coroutine to connect, chat and disconnect in order to test: - -```cpp -asio::awaitable< void > -comain(ssl::context &sslctx, std::string initial_url) -{ - auto connection = co_await connect_websock(sslctx, initial_url, 6); - co_await echo(*connection, "Hello, "); - co_await echo(*connection, "World!\n"); - co_await connection->close(beast::websocket::close_reason( - beast::websocket::close_code::going_away, "thanks for the chat!")); - co_return; -} -``` - -## A Simple Http/WebSocket Server - -In order to test this code, I put together a super-simple web server, which is included in the repo and run as part of -the demo program. - -This web server runs two coroutines, each with its own acceptor. One is the acceptor for HTTP/WS connections and the other -is for HTTPS/WSS connections. Of course I could have used beast's -[flex helper](https://www.boost.org/doc/libs/1_79_0/libs/beast/doc/html/beast/ref/boost__beast__async_detect_ssl.html) -to auto-deduce WS/WSS on the same port, but I wanted to keep the implementation as simple as possible. - -The HTTP server is very simple. All it does is redirect the caller to the same `Target` on the WSS server: - -```cpp -asio::awaitable< void > -serve_http(tcp::socket sock, std::string https_endpoint) -{ - using asio::experimental::deferred; - - auto rxbuf = beast::flat_buffer(); - auto parser = beast::http::request_parser< beast::http::empty_body >(); - co_await beast::http::async_read(sock, rxbuf, parser, deferred); - - static const auto re = std::regex("(/websocket-\\d+)(/.*)?", - std::regex_constants::icase | - std::regex_constants::optimize); - auto match = std::cmatch(); - auto &request = parser.get(); - if (std::regex_match( - request.target().begin(), request.target().end(), match, re)) - { - co_await send_redirect( - sock, fmt::format("{}{}", https_endpoint, match[0].str())); - } - else - { - co_await send_error( - sock, - beast::http::status::not_found, - fmt::format("resource {} is not recognised\r\n", - std::string_view(request.target().data(), - request.target().size()))); - } -} -``` - -The WSS server is minutely more complex. It looks for a URL of the form `/websocket-(\d+)(/.*)?` where group 1 is the -"index number" of the request. If the index number is 0, the websocket request is accepted and we head off into a chat -coroutine for the remainder of the connection. If it is non-zero, then the index is decremented, the URL is -reconstructed with the new index, and the redirect response is sent back. - -So if for example you requested `http://some-server/websocket-2/bar`, you would be redirected along the following path: -- `https://some-server/websocket-2/bar` (first http to https transition) -- `https://some-server/websocket-1/bar` -- `https://some-server/websocket-0/bar` (websocket handshake accepted on this URL) - -Here's the code: - -```cpp -asio::awaitable< void > -serve_https(ssl::stream< tcp::socket > stream, std::string https_fqdn) -{ - try - { - using asio::experimental::deferred; - - co_await stream.async_handshake(ssl::stream_base::server, deferred); - - auto rxbuf = beast::flat_buffer(); - auto request = beast::http::request< beast::http::string_body >(); - co_await beast::http::async_read(stream, rxbuf, request, deferred); - - auto &sock = stream.next_layer(); - if (beast::websocket::is_upgrade(request)) - { - static const auto re = std::regex( - "/websocket-(\\d+)(/.*)?", - std::regex_constants::icase | std::regex_constants::optimize); - auto match = std::cmatch(); - if (std::regex_match(request.target().begin(), - request.target().end(), - match, - re)) - { - auto index = ::atoi(match[1].str().c_str()); - if (index == 0) - { - auto wss = - beast::websocket::stream< ssl::stream< tcp::socket > >( - std::move(stream)); - co_await wss.async_accept(request, deferred); - co_await run_echo_server(wss, rxbuf); - // serve the websocket - } - else - { - // redirect to the next index down - auto loc = fmt::format("{}/websocket-{}{}", - https_fqdn, - index - 1, - match[2].str()); - co_await send_redirect(stream, loc); - } - } - else - { - co_await send_error(stream, - beast::http::status::not_found, - "try /websocket-5\r\n"); - } - } - else - { - co_await send_error( - stream, - beast::http::status::not_acceptable, - "This server only accepts websocket requests\r\n"); - } - } - catch (system_error &e) - { - fmt::print("serve_https: {}\n", e.code().message()); - } - catch (std::exception &e) - { - fmt::print("serve_https: {}\n", e.what()); - } -``` - -The `run_echo_server` coroutine is about as simple as it gets. Note the use of `deferred` as a completion token in order -to create the lightweight awaitable type. - -```cpp -asio::awaitable< void > -run_echo_server(beast::websocket::stream< ssl::stream< tcp::socket > > &wss, - beast::flat_buffer &rxbuf) -{ - using asio::experimental::deferred; - - for (;;) - { - auto size = co_await wss.async_read(rxbuf, deferred); - auto data = rxbuf.cdata(); - co_await wss.async_write(data, deferred); - rxbuf.consume(size); - } -} -``` - -## An Example of Cancellation - -The server is trivial, but there is one little feature I wanted to demonstrate. - -The purpose of the demo is: -- spin up a web server -- connect to the web server a few times and have a chat with it -- exit the program - -This then leaves the issue of causing the web server to shut down so as to release its ownership of the underlying -io_context run operation. i.e. if the io_context doesn't run out of work, the call to `io_context::run()` won't return. - -I have taken advantage of the fact that when coroutines are spawned with an associated cancellation slot, the -cancellation slot tree propagates down through all child coroutines and asio operations. - -So it becomes as simple as: - -Define a cancellation signal: - -```cpp - auto stop_sig = asio::cancellation_signal(); -``` - -Run the server, passing in the cancellation signal's slot: -```cpp - svr.run(stop_sig.slot()); -``` - -When the client code has completed, it simply needs to cause the signal to emit: - -```cpp - co_spawn(ioc, - comain(ioctx, initial_url), - [&](std::exception_ptr ep) - { - ``` -We emit the signal regardless of whether the client ended in an error or not - we want to stop the server in either case -```cpp - stop_sig.emit(asio::cancellation_type::all); - try - { - if (ep) - std::rethrow_exception(ep); - } - catch (std::exception &e) - { - fmt::print("client exception: {}\n", e.what()); - } - }); - -``` - -Within the server, we spawn the internal coroutines bound to the cancellation slot. This will cause the slot to -propagate the signal into the subordinate coroutines, causing whatever they are doing to complete with an -`operation_aborted` error. - -```cpp -void -server::run(asio::cancellation_slot stop_slot) -{ -``` -`awaitable_operators` makes dealing with parallel coroutines extremely simple. -```cpp - using namespace asio::experimental::awaitable_operators; - using asio::bind_cancellation_slot; - using asio::co_spawn; - using asio::use_awaitable; - - fmt::print("server starting\n"); - - auto handler = [](std::exception_ptr ep) - { - try - { - if (ep) - std::rethrow_exception(ep); - } - catch (asio::multiple_exceptions &es) - { - print_exceptions(es.first_exception()); - } - catch (std::exception &e) - { - print_exceptions(e); - } - }; -``` - -Here we are creating an outer coroutine which represents the simultaneous execution of the two inner coroutines, -`http_server` and `wss_server`. The completion token of this outer coroutine is bound to the supplied cancellation slot. -When this slot is invoked, it will propagate the signal into the two subordinate coroutines. - -```cpp - co_spawn(get_executor(), - http_server(tcp_acceptor_, tls_root_) && - wss_server(sslctx_, tls_acceptor_, tls_root_), - bind_cancellation_slot(stop_slot, handler)); -} -``` - -## Final output - -Here is an example of the output generated by this program, tracking the various redirects and correct shutdown of all -IO operations. - -```text -$ ~/github/madmongo1/blog-2022-Aug-websock-redirect/cmake-build-debug/blog-2022-aug-websock-redirect -Initialising -server starting -attempting connection: ws://127.0.0.1:38503/websocket-4 -...handshake -...error: The WebSocket handshake was declined by the remote peer -HTTP/1.1 301 Moved Permanently -Location: wss://127.0.0.1:45141/websocket-4 -Connection: close -Content-Length: 54 - -attempting connection: wss://127.0.0.1:45141/websocket-4 -...handshake -...error: The WebSocket handshake was declined by the remote peer -HTTP/1.1 301 Moved Permanently -Location: wss://127.0.0.1:45141/websocket-3 -Connection: close -Content-Length: 54 - -attempting connection: wss://127.0.0.1:45141/websocket-3 -...handshake -...error: The WebSocket handshake was declined by the remote peer -HTTP/1.1 301 Moved Permanently -Location: wss://127.0.0.1:45141/websocket-2 -Connection: close -Content-Length: 54 - -attempting connection: wss://127.0.0.1:45141/websocket-2 -...handshake -...error: The WebSocket handshake was declined by the remote peer -HTTP/1.1 301 Moved Permanently -Location: wss://127.0.0.1:45141/websocket-1 -Connection: close -Content-Length: 54 - -attempting connection: wss://127.0.0.1:45141/websocket-1 -...handshake -...error: The WebSocket handshake was declined by the remote peer -HTTP/1.1 301 Moved Permanently -Location: wss://127.0.0.1:45141/websocket-0 -Connection: close -Content-Length: 54 - -attempting connection: wss://127.0.0.1:45141/websocket-0 -...handshake -...success -HTTP/1.1 101 Switching Protocols -Upgrade: websocket -Connection: upgrade -Sec-WebSocket-Accept: N5wCr5WUOM6LxN8I4If7oR8QW3A= -Server: Boost.Beast/330 - -Hello, World! -serve_https: The WebSocket stream was gracefully closed at both endpoints -http_server: Operation canceled -wss_server: Operation canceled -Finished - -Process finished with exit code 0 -``` - -# Final Note - -I have of course cut many corners in this demonstration. The error handling is a bit ropey and I haven't considered -timeouts, connection re-use, etc. - -But hopefully this will be useful to anyone reading. - -Until next time. diff --git a/_posts/2022-11-16-KlemensBoost181.md b/_posts/2022-11-16-KlemensBoost181.md deleted file mode 100644 index c19101182..000000000 --- a/_posts/2022-11-16-KlemensBoost181.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -layout: post -nav-class: dark -categories: boost-release -title: New in Boost 1.81 -author-id: klemens ---- - -# New Library: Url - -Boost.url has been released. And it's [awesome](https://www.boost.org/doc/libs/master/libs/url/doc/html/url/overview.html). - -# Json & Describe - -Boost.json is now integrated with Boost.describe. That means that any `class`, `struct`, or `enum` -that has describe annotations can be directly serialized to and from json. - -You can fine examples [here](https://www.boost.org/doc/libs/master/libs/describe/doc/html/describe.html#example_to_json). - -Additionally, `variant2` is also supported. - -# Unordered - -Unordered got a new map type, [`unordered_flat_map`](https://www.boost.org/doc/libs/master/libs/unordered/doc/html/unordered.html#unordered_flat_map), -and the corresponding set type [`unordered_flat_set`](https://www.boost.org/doc/libs/master/libs/unordered/doc/html/unordered.html#unordered_flat_set). - -These two containers lay out the map in a flat array, instead of being node-based. -On modern CPU, this open addressing can lead to significant performance increases, -due to improved cache-usage. - -Joaquín, the author, has written a detailed [blog post](https://bannalia.blogspot.com/2022/11/inside-boostunorderedflatmap.html), -which is highly recommended. - -# Beast - -## Per operation cancellation - -Beast supports per-operation cancellation now. This is mostly `terminal` cancellation, -i.e. you can't do anything with the io-object but to close it afterwards. -This is still useful for completion methods that automatically wire up cancellations, such as `asio::awaitable`s (for which beast also has examples). - -In a few cases beast does allow `total` cancellation (cancellation without side effects). -This is the case in certain situations with websockets, when the operation gets blocked because of ongoing control messages such as ping or pong. - -Generally it should however be treated as if beast only support `terminal` cancellation due to the protocol limitations. - - -## Adressing the dynamic-buffer disconnect. - -When beast was originally conceived, asio did not have a clear dynamic-buffer concept. -This lead to beast developing it's own buffer types in parallel, which have very close semantics. -Asio however went one step further under the guidance of WG21, and developed a dynamic buffer version 2, -which is much more complicated and a questionable improvement. - -Since boost still supports dynamic buffer v1, unless explicitly told not to, it was little work to -make them compatible. -The major difference is that `asio`'s buffers are passed by copy, while `beast`s need to be passed by reference. - -```cpp -std::string buffer; // < the dynamic buffer will point there -asio::read_until(socket, asio::dynamic_buffer(buffer), '\n'); -``` - - -This surely was the source of many bugs, as the following code compiles fine: - -```cpp -beast::flat_buffer buffer; -asio::read_until(socket, buffer, '\n'); -``` - -When run however, the buffer seems to be empty. The reason is that the buffer gets copied by `read_until`, -meaning the data gets written into a buffer, that will get destroyed. - -To help with that, beast now provides a `buffer_ref` class that captures by reference and can then freely be copied: - -```cpp -beast::flat_buffer buffer; -asio::read_until(socket, ref(buffer), '\n'); -``` - -`ref` is a function to do the proper template resolution. - -# Asio - -## Semantic changes - -Asio's semantic requirements have changed slightly regarding `post` and executors. -When a composed operation runs into an error before it's first op, -a common pattern is to `post` once, to avoid recursion. -Usually this post will happen on the executor of the completion handler, -since this is the handler that we need to invoke the handler on anyhow. - -```cpp -void run_my_op( - tcp::socket & sock, // io_exec - thread_pool::executor_type work_exec, - std::function my_completion) -{ - async_read_my_message(sock, asio::bind_executor(work_exec, my_completion)); -} - -``` - -In the above code `async_read_my_message` is a composed operation that gets one message from the socket, which runs on `io_exec` and it's suppoesed to invoke the completion on `work_exec`. - -Let's say, our `async_read_my_message` op, checks if `sock.is_open` and if not, wants to immediately complete. -This seems ok-ish, but what happenes if the `io_exec` isn't running? In any other case, -the operation will only complete if `io_exec` is running, except for the early error. -Thus the correct executor to `post` to is `io_exec`, after which the completion gets `dispatch`ed to `work_exec. - -Because of this, the executor requirements for associated executor are not relaxed, -so that it does not need to support `post`. -The precise requirements can be found in the documentation for the polymorphic wrappers -[any_completion_executor](https://www.boost.org/doc/libs/master/doc/html/boost_asio/reference/any_completion_executor.html) and [any_io_executor](https://www.boost.org/doc/libs/master/doc/html/boost_asio/reference/any_io_executor.html). - -Additionally, `async_compose` provides a handle (commonly called `self`) with a `get_io_executor()` member. - -*Note that beast is not yet compliant with this as of 1.81.* - -## `any_completion_handler` - -Another interesting addition to asio is the `any_completion_handler` class, -that can be used to type-erase completion handlers (not to be confused with tokens). - -Introducing a minor run-time overhead, it can speed up compile times, -because heavy async operations can be moved into source files and only built once. - -At the same time, it can be wrapped in an async_initiate statement, -allowing the use of all completion tokens, e.g. `use_awaitable`. - -E.g.: - -```cpp -// this goes into the source file -void my_async_write_impl(asio::ip::tcp::socket & sock, - asio::const_buffer buffer, - asio::any_completion_handler cpl) -{ - asio::async_write(sock, buffer, std::move(cpl)); -} - -/// header file - - -template -auto my_async_write(asio::ip::tcp::socket & sock, - asio::const_buffer buffer, - Token && token) -{ - return asio::async_initiate( - [&](asio::any_completion_handler cpl) - { - my_async_write_impl(sock, buffer, std::move(cpl)); - }, - token); -} - -// use it - -co_await my_async_write(my_socket, my_buffer, asio::use_awaitable); - -``` - -Note that the above described semantic changes apply; that is, the associated executor of an `any_completion_handler` cannot be use in `asio::post`. - -## Awaiting async_operations - -The new version also introduces the concept (actual concept in C++20) of an `async_operation`. -It describes an expression that can be inovked with a completion-token, e.g.: - -```cpp -asio::steady_timer tim{co_await asio::this_coro::executor}; -asio::async_operation auto my_op = - [&](auto && token) {return tim.async_wait(std::move(token));} -``` - -The interesting part here is that an async-operation, in addition to being usable in `parallel_group`s or the also new `ranged_parallel_group`, can be directly awaited in `asio::awaitable`'s and `asio::experimental::coro`s. - -```cpp -co_await my_op; -``` - -The nice thing here is that we can avoid the additional coroutine frame (which includes an allocation), -that `use_awaitable` (or `use_coro`) needs in order to return an `awaitable`. - -Additionally, `experimental::promise` has been refactored, so that it doesn't use a `.async_wait` member function, -but `operator()` as well. That is, any `experimental::promise` is an async-operation. - -```cpp -auto p = tim.async_wait(experimental::use_promise); -co_await std::move(p); -``` - -## co_compose - -Another useful feature for library developers that can use C++20 is the experimental `co_composed`, -which is a low-level coroutine based replacement for `async_compose`. - -Consider the following example from the asio docs: - -```cpp -template -auto async_echo(tcp::socket& socket, - CompletionToken&& token) -{ - return boost::asio::async_initiate< - CompletionToken, void(boost::system::error_code)>( - boost::asio::experimental::co_composed( - [](auto state, tcp::socket& socket) -> void - { - state.reset_cancellation_state( - boost::asio::enable_terminal_cancellation()); - - while (!state.cancelled()) - { - char data[1024]; - auto [e1, n1] = - co_await socket.async_read_some( - boost::asio::buffer(data), - boost::asio::as_tuple(boost::asio::deferred)); - - if (e1) - co_yield state.complete(e1); - - if (!!state.cancelled()) - co_yield state.complete( - make_error_code(boost::asio::error::operation_aborted)); - - auto [e2, n2] = - co_await boost::asio::async_write(socket, - boost::asio::buffer(data, n1), - boost::asio::as_tuple(boost::asio::deferred)); - - if (e2) - co_yield state.complete(e2); - } - }, socket), - token, std::ref(socket)); -} -``` - -Writing this as `async_compose` & `asio::coroutine` would look like this: - -{% raw %} -```cpp -struct async_echo_implementation : boost::asio::coroutine -{ - tcp::socket & socket; - - // - can't be a member, sicne this struct gets moved - // - should be allocated using the associated allocator, but this is an example. - std::unique_ptr data{new char[1024]}; - - template - void operator()(Self && self, system::error_code ec = {}, std::size_t n = 0u) - { - reenter(this) - { - while (!self.cancelled()) - { - yield socket.async_read_some( - boost::asio::buffer(data.get(), 1024), - std::move(self)); - - if (ec) - return self.complete(ec); - if (!!self.cancelled()) - return self.complete(boost::asio::error::operation_aborted); - - yield boost::asio::async_write(socket, - boost::asio::buffer(data.get(), n), - std::move(self)); - - if (ec) - return self.complete(ec); - } - } - self.complete({}); - } -}; - -template -auto async_echo(tcp::socket& socket, - CompletionToken&& token) -{ - return boost::asio::async_compose( - async_echo_implementation{{}, socket}, - token, socket); -} -``` -{% endraw %} - -Not only is the state management easier, but it also doesn't need to move the state (i.e. coroutine frame), -that it can become more performant. diff --git a/_posts/2022-12-11-Asio201Deferred.md b/_posts/2022-12-11-Asio201Deferred.md deleted file mode 100644 index ef34a6a15..000000000 --- a/_posts/2022-12-11-Asio201Deferred.md +++ /dev/null @@ -1,236 +0,0 @@ ---- -layout: post -nav-class: dark -categories: asio -title: Asio 201 - deferred -author-id: klemens ---- - -# Asio deferred - -## Aysnc operations - -Asio introduced the concept of an async_operation, which describes a primary expression -that can be invoked with a completion token. In C++20 this is also a language concept. - -```cpp -asio::io_context ctx; -asio::async_operation auto post_op = [&](auto && token){return asio::post(ctx, std::move(token));}; - -auto f = post_op(asio::use_future); -ctx.run(); -f.get(); // void -``` - -Async operations can be used in `parallel_group` and directly `co_await`ed in C++20. - -## `asio::deferred` as a completion token - -Using `asio::deferred` as a completion token, will give you a lazy -`async_operation` as the result value. - -```cpp -asio::io_context ctx; -asio::async_operation auto post_op = asio::post(ctx, asio::deferred); - -auto f = std::move(post_op)(asio::use_future); -ctx.run(); -f.get(); // void -``` - -## deferred expressions - -Additionally, a deferred can be invoked with a function object that returns another deferred expression. E.g.: - -```cpp -asio::io_context ctx; -asio::async_operation auto post_op = asio::post(ctx, asio::deferred); -asio::async_operation auto double_post_op = - asio::post(asio::deferred([&]{return post_op;})); - -auto f = std::move(double_post_op)(asio::use_future); -ctx.run(); -f.get(); // void -``` - -This now will call two posts subsequently. - -Not every deferred expression however is an async_operation, deferred provides multiple utilities. - -## `deferred.values` - -`asio.values` is a deferred expression that just returns values, so that you can modify the completion signature. - -```cpp -asio::io_context ctx; -asio::async_operation auto post_int_op = - asio::post(ctx, - asio::deferred( - [] - { - return asio::deferred.values(42); - } - )); - -auto f = std::move(post_int_op)(asio::use_future); -ctx.run(); -assert(f.get() == 42); // int -``` - -This already can be useful to modify completion signatures, similar to `asio::append` and `asio::prepend`. - -## `deferred.when` - -Next deferred provides a conditional, that takes two deferred expressions. - -```cpp -auto def = asio::deferred.when(condition).then(def1).otherwise(def2); -``` - -This can be used for simple continuations with error handling. -Let's say we want to read some memory from `socket1` and write to `socket2`. - -```cpp -extern asio::ip::tcp::socket socket1, socket2; -char buf[4096]; - -auto forward_op = - socket1.async_read_some( - buf, - asio::deferred( - [&](system::error_code ec, std::size_t n) - { - return asio::deferred - .when(!!ec) // complete with the error and `n` - .then(asio::deferred.values(ec, n)) - .otherwise( - asio::async_write(socket2, - asio::buffer(buf, n), - asio::deferred)); - } - )); -``` - -## Multiple `deferred`s - -Since all the calls with `deferred` yield async_operations, we can combine more than two, just by invoking the resulting expression. Let's say we want to add a delay at the end of the operation above, we can simple add another deferred. - -```cpp -extern asio::ip::tcp::socket socket1, socket2; -extern asio::steady_timer delay; -char buf[4096]; - -auto forward_op = - socket1.async_read_some( - asio::buffer(buf), - asio::deferred( - [&](system::error_code ec, std::size_t n) - { - return asio::deferred - .when(!!ec) // complete with the error and `n` - .then(asio::deferred.values(ec, n)) - .otherwise( - asio::async_write(socket2, - asio::buffer(buf, n), - asio::deferred)); - } - )) - ( - asio::deferred( - [&](system::error_code ec, std::size_t n) - { - return asio::deferred - .when(!!ec) - .then(asio::deferred.values(ec, n)) - .otherwise( - delay.async_wait(asio::append(asio::deferred, n)) - ); - } - ) - ); -``` - -This now gives us a simple composed operation with three steps. -It also gets increasingly unreadable, which is why asio provides - -## `operator|` - -Instead of invoking the deferred expression multiple times, you can also just write this: - -```cpp -extern asio::ip::tcp::socket socket1, socket2; -extern asio::steady_timer delay; -char buf[4096]; - -auto forward_op = - socket1.async_read_some(asio::buffer(buf), asio::deferred) - | asio::deferred( - [&](system::error_code ec, std::size_t n) - { - return asio::deferred - .when(!!ec) // complete with the error and `n` - .then(asio::deferred.values(ec, n)) - .otherwise( - asio::async_write(socket2, - asio::buffer(buf, n), - asio::deferred)); - } - ) - | asio::deferred( - [&](system::error_code ec, std::size_t n) - { - return asio::deferred - .when(!!ec) - .then(asio::deferred.values(ec, n)) - .otherwise( - delay.async_wait(asio::append(asio::deferred, n)) - ); - }); -``` - - -## Readable code - -It should be quite clear that the complexity can get out of hand rather quickly, which is why you should consider separating the continuation functions from the deferred chain. - -This can be achieved with by using `append` to pass pointers to the -io objects, like so: - - -```cpp -auto do_read(asio::ip::tcp::socket * socket1, - asio::ip::tcp::socket * socket2, - char * buf, std::size_t n) - { - return socket1->async_read_some( - asio::buffer(buf, n), - asio::append(asio::deferred, socket1, buf)); - }; - -auto do_write(system::error_code ec, std::size_t n, - asio::ip::tcp::socket * socket2, char * buf) - { - return asio::deferred - .when(!!ec) // complete with the error and `n` - .then(asio::deferred.values(ec, n)) - .otherwise( - asio::async_write(*socket2, - asio::buffer(buf, n), - asio::deferred)); - }; - -template -auto forward_op( - asio::ip::tcp::socket & socket1, - asio::ip::tcp::socket & socket2, - char (&buf)[Size]) -{ - return asio::deferred.values( - &socket1, &socket2, &buf[0], Size) - | asio::deferred(&do_read) - | asio::deferred(&do_write); -} -``` - - -More examples can be found in the [asio repo](https://github.com/chriskohlhoff/asio/tree/master/asio/src/examples/cpp14/deferred). \ No newline at end of file diff --git a/_posts/2023-01-02-Asio201Timeouts.md b/_posts/2023-01-02-Asio201Timeouts.md deleted file mode 100644 index 5f557187c..000000000 --- a/_posts/2023-01-02-Asio201Timeouts.md +++ /dev/null @@ -1,623 +0,0 @@ ---- -layout: post -nav-class: dark -categories: asio -title: Asio 201 - timeouts, cancellation & custom tokens -author-id: klemens ---- - -Since asio added and beast implemented per-operation cancellation, -the way timeouts can be implemented in asio code has changed significantly. - -In this article, we'll go from simple timeouts to building our own timeout completion token helper. - -# Cancellation - -A timeout is a defined interval after which a cancellation -will be triggered, if an action didn't complete by then. - -Timeouts *can* be a way of handling runtime errors, but one should generally be prudent about their usage. Indiscriminate application -of timeouts with intervals based on the programmer's feelings can -lead to bad code and odd behavior. - -## Previous solutions - -Previous to per-operation cancellation, one could only cancel all operations on a given io-object. E.g.: - -```cpp -extern asio::ip::tcp::socket sock; -extern std::string read_buffer, write_buffer; - -asio::async_read(sock, asio::dynamic_buffer(read_buffer), asio::detached); -asio::async_write(sock, asio::buffer(write_buffer), asio::detached); - -// cancel both the write and the read, by cancelling all outstanding operations -sock.cancel(); -``` - -Due to the popularity of timeouts, beast provides it's own stream wrappers, `tcp_stream` & `ssl_stream` that (among other things) provide -a timeout using this kind of cancellation based on internal timers. - -## Per operation cancellation - -Per operation cancellation is a much more fine-tuned model; -instead of cancelling all outstanding operations on an io-object, -it cancels particular ones. - -```cpp - -extern asio::ip::tcp::socket sock; -extern std::string read_buffer, write_buffer; - -asio::cancellation_signal cancel_read, cancel_write; - -asio::async_read(sock, asio::dynamic_buffer(read_buffer), asio::bind_cancellation_slot(cancel_read.slot(), asio::detached)); -asio::async_write(sock, asio::buffer(write_buffer), asio::bind_cancellation_slot(cancel_write.slot(), asio::detached)); - -// cancel only the read op with cancellation type terminal -cancel_read.emit(asio::cancellation_type::terminal); -``` - -## Cancellation types - -The different kinds of cancellation are: - - - *terminal*: -Requests cancellation where, following a successful cancellation, the only safe operations on the I/O object are closure or destruction. - - - *partial*: -Requests cancellation where a successful cancellation may result in partial side effects or no side effects. Following cancellation, the I/O object is in a well-known state, and may be used for further operations. - - - *total*: -Requests cancellation where a successful cancellation results in no apparent side effects. Following cancellation, the I/O object is in the same observable state as it was prior to the operation. - -The sender may combine multiple types with `operator|`; the receiver uses the cancellation as a signal he may ignore and he should satisfy the lowest level of cancellation possible. - -## Full example - -To give an example of the cancellation types on a protocol level, -consider the following function (written as a coroutine for simplicity): - -```cpp -// read data from the stream and forward it to the parser -// until one full value is read. -// whatever is leftover goes into the `buf` to be used for the next value. -template -auto async_read_json(Stream & stream, - json::stream_parser & parser, - DynamicBuffer & buf /*< beast style buffer! */) - -> asio::awaitable -{ - - // 0: Nothing happened - while (!parser.done()) - { - // 1: read the next chunk - const std::size_t read_n = - co_await stream.async_read_some( - buf.prepare(4096), asio::use_awaitable); - // 2: move it to the read buffer - buf.commit(read_n); - // 3: write it to the parser - const auto wbuf = buf.cdata(); - const std::size_t writ_n = parser.write(static_cast(wbuf.data()), wbuf.size()); - // 4: remove parsed bytes from the buffer - buf.consume(writ_n); - } - - co_return parser.release(); -} -``` - -*terminal*: this means the data & the stream can only be closed. That is, if the algorithm receives a cancellation in step (1), it can just exit directly, because -the cancellation indicates the caller doesn't care about the data anymore. - -*partial*: this means the operation might have read actual data, but can be resumed later on. If partial cancellation occurs we need to at least transfer the read data into the buffer; in this case however, they should also be sent to the parser, -as the json might be complete and next run async_read_some will prevent us from completing. - -*total*: Total cancellation means no side effects, i.e. nothing was read. This may happen on our first iteration through the loop, if async_read_some gets cancellation before a single byte has been written. - -With this in mind we can rewrite out coroutine to handle cancellation - -note that `awaitable`s have an internal cancellation state. - -```cpp -template -auto async_read_json(Stream & stream, - json::stream_parser & parser, - DynamicBuffer & buf /*< beast style buffer! */) - -> asio::awaitable -{ - // by default awaitables only allow terminal cancellation - // we'll enable all types here: - co_await asio::this_coro::reset_cancellation_state(asio::enable_total_cancellation()); - - while (!parser.done()) - { - // check if we've been cancelled! - asio::cancellation_state cs = co_await asio::this_coro::cancellation_state; - if (cs.cancelled() != asio::cancellation_type::none) - break; - // capture ec, so nothing gets thrown - const auto [ec, read_n] = - co_await stream.async_read_some( - buf.prepare(4096), asio::as_tuple(asio::use_awaitable)); - if (ec == asio::error::operation_aborted) - { - using c_t = asio::cancellation_type; - //update the state - cs = co_await asio::this_coro::cancellation_state; - c_t c = cs.cancelled(); - // total means nothing happened, - // terminal means the data doesn't matter - if ((c & (c_t::total | c_t::terminal)) != c_t::none) - throw system::system_error(ec); - // partial means we need to finish the loop - // so we just do nothing and do NOT reset the filter! - } - else if (ec) // indiscriminately throw everything else - throw system::system_error(ec); - else - // reset it to partial after the first read; - co_await asio::this_coro::reset_cancellation_state( - asio::enable_partial_cancellation()); - - buf.commit(read_n); - const auto wbuf = buf.cdata(); - const std::size_t writ_n = - parser.write(static_cast(wbuf.data()), wbuf.size()); - buf.consume(writ_n); - } - - asio::cancellation_state cs = co_await asio::this_coro::cancellation_state; - if (cs.cancelled() != asio::cancellation_type::none) - throw system::system_error(asio::error::operation_aborted); - - co_return parser.release(); -} -``` - -The above example is complex because it is considering different kinds of cancellation -and when they can be provided to the caller. - -# Timeouts - -Based on the previous discussion, we may now use a timer -and connect it to a cancellation slot to provide a timeout. - -```cpp -asio::awaitable do_read( - asio::ip::tcp::socket &sock, - std::chrono::seconds timeout = std::chrono::seconds(5) -) -{ - asio::steady_timer tim{co_await asio::this_coro::executor, timeout}; - asio::cancellation_signal cancel_read; - std::string read_buffer; - - tim.async_wait( - [&](system::error_code ec) - { - if (!ec) // timer completed without getting cancelled himself - cancel_read.emit(asio::cancellation_type::all); - }); - - co_await asio::async_read(sock, asio::dynamic_buffer(read_buffer), - asio::bind_cancellation_slot(cancel_read.slot(), asio::use_awaitable)); - tim.cancel(); - - co_return read_buffer; -} -``` - -There is a problem in the above code: any cancellation delivered to `do_read` gets ignored. That is, the `awaitable` itself is an async operation that can get cancelled. - -```cpp -extern asio::ip::tcp::socket sock; -asio::cancellation_signal dr_c; -asio::co_spawn(sock.get_executor(), do_read(sock), - asio::bind_cancellation_slot(dr_c.slot(), asio::detached)); -dr_c.emit(asio::cancellation_type::all); // < ignored! -``` - -In order to rectify this, we need to also need to forward the cancellation received by the `awaitable`: - -```cpp -asio::awaitable do_read( - asio::ip::tcp::socket &sock, - std::chrono::seconds timeout = std::chrono::seconds(5) -) -{ - asio::steady_timer tim{co_await asio::this_coro::executor, timeout}; - asio::cancellation_signal cancel_read; - asio::cancellation_slot sl = - (co_await asio::this_coro::cancellation_state).slot(); - - std::string read_buffer; - sl.assign( - [&](asio::cancellation_type ct) - { - // cancel the timer, we don't need it anymore - tim.cancel(); - // forward the cancellation - cancel_read.emit(ct); - }); - - // reset the signal when we're done - // this is very important, the outer signal might fire after we're out of scope! - struct scope_exit - { - asio::cancellation_slot sl; - ~scope_exit() { if(sl.is_connected()) sl.clear();} - } scope_exit_{sl}; - - // regular timeout with a timer. - tim.async_wait( - [&](system::error_code ec) - { - if (!ec) // timer completed without getting cancelled himself - cancel_read.emit(asio::cancellation_type::all); - }); - - // the actual op - co_await asio::async_read(sock, asio::dynamic_buffer(read_buffer), - asio::bind_cancellation_slot(cancel_read.slot(), asio::use_awaitable)); - tim.cancel(); - - co_return read_buffer; -} -``` - -This is getting a bit verbose, so that users might look for alternatives. - -## `parallel_group` / `operator||` - -Thus the easiest way to implement a timeout is with a `parallel_group`. You might have seen the `awaitable_operators` used like this: - -```cpp -using namespace asio::experimental::awaitable_operators; - -extern asio::ip::tcp::socket sock; -extern steady_timer tim; -extern std::string read_buffer; - -co_await ( - asio::async_read(sock, asio::dynamic_buffer(read_buffer), asio::use_awaitable) || tim.async_wait(asio::use_awaitable)); -``` - -The `operator||` runs two awaitables in parallel, waiting for one to finish. When the first completes it cancels the other ones `terminal`y. - -This gives us a timeout, that will always be terminal, and is implement by means of [parallel_group](https://www.boost.org/doc/libs/master/doc/html/boost_asio/reference/experimental__parallel_group/async_wait.html), i.e. similar to this: - -```cpp -co_await - experimental::make_parallel_group( - asio::async_read(sock, asio::dynamic_buffer(read_buffer), asio::deferred), - tim.async_await(asio::deferred) - ).async_wait( - experimental::wait_for_one(), - asio::use_awaitable - ); -``` - -This is fine for many simple solutions & examples, -but it's a very blunt & not terribly efficient way to achieve only terminal cancellation. - -It is important to mention, that a per low level operation timeout might also not be the right approach altogether. On the one hand, it might not be required that a particular single operation (like connect) completes within a certain amount of time, but that a series of operations does so (like resolve + connect + handshake). - -This means choosing where to put timeouts is a task for careful engineering. - -## Watchdogs - -Another popular pattern is a watchdog, -when the requirement is to assure continuous progress. -That is, we want to make sure, that a long running does not get stuck, but every so often does some successful work. -Consider downloading a huge file; we can't really put a timeout on it, but we can check that it did download some bytes every few seconds. - -You would usually use this for complex & long running operations, but for our example, we'll just reuse the -async_read_json function. - -```cpp - -template -auto async_read_json(Stream & stream, - json::stream_parser & parser, - DynamicBuffer & buf, /*< beast style buffer! */ - watchdog & wuff) - -> asio::awaitable -{ - wuff.reset(); - while (!parser.done()) - { - const std::size_t read_n = - co_await stream.async_read_some( - buf.prepare(4096), asio::use_awaitable); - wuff.reset(); - buf.commit(read_n); - const auto wbuf = buf.cdata(); - const std::size_t writ_n = parser.write( - static_cast(wbuf.data()), wbuf.size()); - - buf.consume(writ_n); - } - - co_return parser.release(); -} -``` - -If the `.reset` function on the watchdog isn't called during the watchdog interval, -it will cancel the operation. - -This watchdog can be as simple as this: - -```cpp -struct watchdog -{ - watchdog(asio::any_io_executor exec, std::chrono::milliseconds interval) - : tim(exec, interval), interval(interval) - {} - - asio::steady_timer tim; - std::chrono::milliseconds interval; - asio::cancellation_signal cancel; - void reset() - { - tim.expires_after(interval); - tim.async_wait( - [this](system::error_code ec) - { - if (!ec) - cancel.emit(asio::cancellation_type::terminal); - }); - } -}; -``` - -And we can use it with our awaitable by a simple bind: - -```cpp -extern asio::ip::tcp::socket sock; - -beast::flat_buffer buf; -json::stream_parser parser; - -watchdog wuff{sock.get_executor(), std::chrono::milliseconds(5000)}; -asio::co_spawn(sock.get_executor(), - async_read_json(sock, parser, buf, wuff) - asio::bind_cancellation_slot(wuff.cancel.slot(), asio::detached) - ); -``` - -# A custom timeout token - -While writing your own completion tokens is a bit of a hassle, -it may be worth the effort if an entire application is using it. - -Here, we will write a `timeout` utility that utilizes different timeouts -to fire a sequence of all cancellation types. The idea is that we do not want to use terminal cancellation right away, as we might corrupt data unnecessarily with that. - -Instead we have three intervals. After the first, we try `total` cancellation; -if that doesn't do anything, we wait the second interval and use `partial` cancellation. -If nothing happens after that, we go for `terminal`. - -```cpp -struct timeout_provider; - -// that's our completion token with the timeout attached -template -struct with_timeout -{ - timeout_provider * provider; - Token token; -}; - -// this is the timeout source -struct timeout_provider -{ - timeout_provider( - asio::any_io_executor exec - ) : tim{exec, std::chrono::steady_clock::time_point::max()} {} - - asio::steady_timer tim; - - std::chrono::milliseconds tt_total = std::chrono::milliseconds(2000); - std::chrono::milliseconds tt_partial = std::chrono::milliseconds(3000); - std::chrono::milliseconds tt_partial = std::chrono::milliseconds(5000); - - asio::cancellation_slot parent; - asio::cancellation_signal timeout; - - asio::cancellation_type last_fired{asio::cancellation_type::none}; - - ~timeout_provider() - { - if (parent.is_connected()) - parent.clear(); - } - - // to use it - template - auto operator()(Token && token) - { - return with_timeout>{ - this, std::forward(token) - }; - } - - // set up the timer and get ready to trigger - void arm() - { - last_fired = asio::cancellation_type::none; - tim.expires_after(tt_total); - if (parent.is_connected()) - parent.assign([this](asio::cancellation_type ct){timeout.emit(ct);}); - tim.async_wait( - [this](system::error_code ec) - { - if (!ec) fire_total(); - }); - } - - void fire_total() - { - timeout.emit(last_fired = asio::cancellation_type::total); - tim.expires_after(tt_partial); - tim.async_wait( - [this](system::error_code ec) - { - if (!ec) fire_partial(); - }); - } - - void fire_partial() - { - timeout.emit(last_fired = asio::cancellation_type::partial); - tim.expires_after(tt_terminal); - tim.async_wait( - [this](system::error_code ec) - { - if (!ec) fire_terminal(); - }); - } - - void fire_terminal() - { - timeout.emit(last_fired = asio::cancellation_type::terminal); - } -}; -``` - -The plan is then to use this like so: - -```cpp -asio::awaitable do_read( - asio::ip::tcp::socket &sock, - timeout_provider & timeout) -{ - std::string read_buffer; - co_await asio::async_read(sock, asio::dynamic_buffer(read_buffer), - timeout(asio::use_awaitable)); - co_return read_buffer; -} -``` - -In order to do that we need to provide a custom async_initiate with a -custom token. The reason we need a custom handler is that lazy operations like use_awaitable and deferred still work. - -Before we jump into a rather long piece of code, let's recap how async initiation works. - -We pass a completion token to async_initiate, together with the initiation of our op (e.g. `async_initiate_read`). -The completion token must have a specialization of `async_result` that will call `initiate` with it's completion handler -and return a result value. The handler is usually some internal type, that has associators (e.g. an associated allocator). -For example, `use_awaitable` is a token, `awaitable` the return type of it's initialization and some `detail` type it's handler. - -In order for our timeout to work, we need to wrap the other completion token, and then intercept the call to the initiation -to obtain the handler, and wrap it as well. - -```cpp -// the completion handler -// that's our completion token with the timeout attached -template -struct with_timeout_binder -{ - timeout_provider * provider; - Handler handler; - - template - void operator()(Args && ... args) - { - //cancel the time, we're done! - provider->tim.cancel(); - std::move(handler)(std::forward(args)...); - } -}; - -namespace boost::asio -{ - -// This is the class to specialize when implementing a completion token. -template -struct async_result, Signatures...> -{ - using return_type = typename async_result::return_type; - - // this wrapper goes around the inner initiation, because we need to capture their cancellation slot - template - struct init_wrapper - { - Initiation initiation; - timeout_provider * provider; - - // the forwards to the initiation and lets us access the actual handler. - template - void operator()( - Handler && handler, - Args && ... args) - { - auto sl = asio::get_associated_cancellation_slot(handler); - if (sl.is_connected()) - provider->parent = sl; - provider->arm(); - std::move(initiation)( - with_timeout_binder>{ - provider, - std::forward(handler) - }, std::forward(args)...); - } - } - - // the actual initiation - template - static auto initiate(Initiation && init, - RawToken && token, - Args && ... args) -> return_type - { - return async_result::initiate( - // here we wrap the initiation so we enable the above injection - init_wrapper>(std::forward(init), token.provider), - std::move(token.token), - std::forward(args)...); - } -}; - - -// forward the other associators, such as allocator & executor -template