<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
  <meta charset="utf-8" />
  <meta name="generator" content="pandoc" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
  <title>An Event Model for C++ Executors</title>
  <style>
    html {
      line-height: 1.5;
      font-family: Georgia, serif;
      font-size: 20px;
      color: #1a1a1a;
      background-color: #fdfdfd;
    }
    body {
      margin: 0 auto;
      max-width: 36em;
      padding-left: 50px;
      padding-right: 50px;
      padding-top: 50px;
      padding-bottom: 50px;
      hyphens: auto;
      overflow-wrap: break-word;
      text-rendering: optimizeLegibility;
      font-kerning: normal;
    }
    @media (max-width: 600px) {
      body {
        font-size: 0.9em;
        padding: 1em;
      }
      h1 {
        font-size: 1.8em;
      }
    }
    @media print {
      body {
        background-color: transparent;
        color: black;
        font-size: 12pt;
      }
      p, h2, h3 {
        orphans: 3;
        widows: 3;
      }
      h2, h3, h4 {
        page-break-after: avoid;
      }
    }
    p {
      margin: 1em 0;
    }
    a {
      color: #1a1a1a;
    }
    a:visited {
      color: #1a1a1a;
    }
    img {
      max-width: 100%;
    }
    h1, h2, h3, h4, h5, h6 {
      margin-top: 1.4em;
    }
    h5, h6 {
      font-size: 1em;
      font-style: italic;
    }
    h6 {
      font-weight: normal;
    }
    ol, ul {
      padding-left: 1.7em;
      margin-top: 1em;
    }
    li > ol, li > ul {
      margin-top: 0;
    }
    blockquote {
      margin: 1em 0 1em 1.7em;
      padding-left: 1em;
      border-left: 2px solid #e6e6e6;
      color: #606060;
    }
    code {
      font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace;
      font-size: 85%;
      margin: 0;
    }
    pre {
      margin: 1em 0;
      overflow: auto;
    }
    pre code {
      padding: 0;
      overflow: visible;
      overflow-wrap: normal;
    }
    .sourceCode {
     background-color: transparent;
     overflow: visible;
    }
    hr {
      background-color: #1a1a1a;
      border: none;
      height: 1px;
      margin: 1em 0;
    }
    table {
      margin: 1em 0;
      border-collapse: collapse;
      width: 100%;
      overflow-x: auto;
      display: block;
      font-variant-numeric: lining-nums tabular-nums;
    }
    table caption {
      margin-bottom: 0.75em;
    }
    tbody {
      margin-top: 0.5em;
      border-top: 1px solid #1a1a1a;
      border-bottom: 1px solid #1a1a1a;
    }
    th {
      border-top: 1px solid #1a1a1a;
      padding: 0.25em 0.5em 0.25em 0.5em;
    }
    td {
      padding: 0.125em 0.5em 0.25em 0.5em;
    }
    header {
      margin-bottom: 4em;
      text-align: center;
    }
    #TOC li {
      list-style: none;
    }
    #TOC ul {
      padding-left: 1.3em;
    }
    #TOC > ul {
      padding-left: 0;
    }
    #TOC a:not(:hover) {
      text-decoration: none;
    }
    code{white-space: pre-wrap;}
    span.smallcaps{font-variant: small-caps;}
    span.underline{text-decoration: underline;}
    div.column{display: inline-block; vertical-align: top; width: 50%;}
    div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
    ul.task-list{list-style: none;}
    pre > code.sourceCode { white-space: pre; position: relative; }
    pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
    pre > code.sourceCode > span:empty { height: 1.2em; }
    .sourceCode { overflow: visible; }
    code.sourceCode > span { color: inherit; text-decoration: inherit; }
    div.sourceCode { margin: 1em 0; }
    pre.sourceCode { margin: 0; }
    @media screen {
    div.sourceCode { overflow: auto; }
    }
    @media print {
    pre > code.sourceCode { white-space: pre-wrap; }
    pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
    }
    pre.numberSource code
      { counter-reset: source-line 0; }
    pre.numberSource code > span
      { position: relative; left: -4em; counter-increment: source-line; }
    pre.numberSource code > span > a:first-child::before
      { content: counter(source-line);
        position: relative; left: -1em; text-align: right; vertical-align: baseline;
        border: none; display: inline-block;
        -webkit-touch-callout: none; -webkit-user-select: none;
        -khtml-user-select: none; -moz-user-select: none;
        -ms-user-select: none; user-select: none;
        padding: 0 4px; width: 4em;
        color: #aaaaaa;
      }
    pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa;  padding-left: 4px; }
    div.sourceCode
      {   }
    @media screen {
    pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
    }
    code span.al { color: #ff0000; font-weight: bold; } /* Alert */
    code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
    code span.at { color: #7d9029; } /* Attribute */
    code span.bn { color: #40a070; } /* BaseN */
    code span.bu { color: #008000; } /* BuiltIn */
    code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
    code span.ch { color: #4070a0; } /* Char */
    code span.cn { color: #880000; } /* Constant */
    code span.co { color: #60a0b0; font-style: italic; } /* Comment */
    code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
    code span.do { color: #ba2121; font-style: italic; } /* Documentation */
    code span.dt { color: #902000; } /* DataType */
    code span.dv { color: #40a070; } /* DecVal */
    code span.er { color: #ff0000; font-weight: bold; } /* Error */
    code span.ex { } /* Extension */
    code span.fl { color: #40a070; } /* Float */
    code span.fu { color: #06287e; } /* Function */
    code span.im { color: #008000; font-weight: bold; } /* Import */
    code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
    code span.kw { color: #007020; font-weight: bold; } /* Keyword */
    code span.op { color: #666666; } /* Operator */
    code span.ot { color: #007020; } /* Other */
    code span.pp { color: #bc7a00; } /* Preprocessor */
    code span.sc { color: #4070a0; } /* SpecialChar */
    code span.ss { color: #bb6688; } /* SpecialString */
    code span.st { color: #4070a0; } /* String */
    code span.va { color: #19177c; } /* Variable */
    code span.vs { color: #4070a0; } /* VerbatimString */
    code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
    .display.math{display: block; text-align: center; margin: 0.5rem auto;}
  </style>
</head>
<body>
<header id="title-block-header">
<h1 class="title">An Event Model for C++ Executors</h1>
</header>
<table>
<tbody>
<tr class="odd">
<td>Doc. No.:</td>
<td>P2882R0</td>
</tr>
<tr class="even">
<td>Date:</td>
<td>2023-05-11</td>
</tr>
<tr class="odd">
<td>Reply to:</td>
<td>Detlef Vollmann, dv@vollmann.ch</td>
</tr>
<tr class="even">
<td>Audience:</td>
<td>SG1, (LEWG)</td>
</tr>
</tbody>
</table>
<h2 id="preface">Preface</h2>
<p>This is (currently) a purely exploratory paper, it is not any kind of
proposal!</p>
<p>The goal of this paper is to start a discussion and to find out
whether SG1 (and WG21 as a whole) is willing to provide a communication
mechanism for diverse C++ execution contexts.</p>
<p>And a note on the code in this paper: All code is “slide code”,
i.e. it hasn’t even run through a compiler. The purpose of the code is
to demonstrate specific issues, not to provide a specific solution.</p>
<h2 id="abstract">Abstract</h2>
<p><a href="https://wg21.link/P2300">P2300</a> proposes a framework for
handling asynchronous events and start work on different execution
contexts. However it doesn’t provide a low-level interface how to
communicate and synchronize between such execution contexts.</p>
<p>This paper explores some requirements and design options to provide a
standard mechanism for synchronization across execution contexts and
presents a list of questions that SG1 should answer.</p>
<h2 id="introduction">Introduction</h2>
<p><a href="https://wg21.link/P2300">P2300</a> allows for different
(heterogenious) execution contexts, but it doesn’t specify any
requirements for such execution contexts except that they must provide a
scheduler. This makes it impossible to even implement a
<code>transfer()</code> algorithm as there’s no interface join an
execution agent from a different execution context and no interface to
exchange results between execution contexts.</p>
<p>In <a href="https://wg21.link/P0073R2">P0073R2 “On unifying the
coroutines and resumable functions proposals”</a> Torvald Riegel
proposed an <code>event</code> class to synchronize between different
execution contexts. While <code>event</code> never got a detailled
discussion, it proposed the basic required interface:
<code>block()</code> and <code>signal()</code>. This interface is
probably sufficient for blocking synchronization (and we already have
the atomic interface for non-blocking synchronization).</p>
<h2 id="example">Example</h2>
<p>As an example a simple network speaker implementation could look like
this:</p>
<p>A receiver to receive network packets from a network:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>    SnapClient srv<span class="op">{</span>srvAddr<span class="op">,</span> srvPort<span class="op">};</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">while</span> <span class="op">(</span><span class="kw">true</span><span class="op">)</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="op">{</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>        <span class="bu">std::</span>span<span class="op">&lt;</span><span class="dt">uint8_t</span><span class="op">&gt;</span> buf <span class="op">=</span> srv<span class="op">.</span>receiveWireChunk<span class="op">();</span> <span class="co">// blocks</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>        opusQueue<span class="op">.</span>wait_push<span class="op">(</span>buf<span class="op">);</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span></code></pre></div>
<p>A decoder decodes these packets to PCM frames:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>    <span class="cf">while</span> <span class="op">(</span><span class="kw">true</span><span class="op">)</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>    <span class="op">{</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>        <span class="bu">std::</span>span<span class="op">&lt;</span><span class="dt">uint8_t</span><span class="op">&gt;</span> inBuf<span class="op">;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>        opusQueue<span class="op">.</span>wait_pop<span class="op">(</span>inBuf<span class="op">);</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>        <span class="dt">int</span> samples <span class="op">=</span> opus_decode<span class="op">(</span>decoder<span class="op">,</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>                                  inBuf<span class="op">.</span>data<span class="op">(),</span> inBuf<span class="op">.</span>size<span class="op">(),</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>                                  decodeBuf<span class="op">.</span>data<span class="op">(),</span> maxFrameSamples<span class="op">,</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>                                  <span class="dv">0</span><span class="op">);</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>        <span class="bu">std::</span>span<span class="op"> </span>outBuf<span class="op">(</span>decodeBuf<span class="op">.</span>data<span class="op">(),</span> samples<span class="op">);</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>        pcmQueue<span class="op">.</span>wait_push<span class="op">(</span>outBuf<span class="op">);</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span></code></pre></div>
<p>And a play function sends the PCM frames to an I2S interface:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>    <span class="cf">while</span> <span class="op">(</span><span class="kw">true</span><span class="op">)</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>    <span class="op">{</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>        <span class="bu">std::</span>span<span class="op">&lt;</span><span class="dt">uint8_t</span><span class="op">&gt;</span> inBuf<span class="op">;</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>        pcmQueue<span class="op">.</span>wait_pop<span class="op">(</span>inBuf<span class="op">);</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>        <span class="dt">uint32_t</span> <span class="at">const</span> <span class="op">*</span>start <span class="op">=</span> inBuf<span class="op">.</span>data<span class="op">();</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>        <span class="dt">size_t</span> offset <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>        <span class="cf">while</span> <span class="op">(</span>offset <span class="op">&lt;</span> size<span class="op">)</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a>        <span class="op">{</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>            <span class="dt">size_t</span> bytesDone<span class="op">;</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a>            <span class="co">// i2s_channel_write blocks</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>            i2s_channel_write<span class="op">(</span>tx<span class="op">,</span></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a>                              start <span class="op">+</span> offset<span class="op">,</span></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a>                              size <span class="op">-</span> offset<span class="op">,</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a>                              <span class="op">&amp;</span>bytesDone<span class="op">,</span></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a>                              noTimeout<span class="op">);</span></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a>            offset <span class="op">+=</span> bytesDone<span class="op">;</span></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a>        <span class="op">}</span></span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span></code></pre></div>
<p>These three functions could run in three separate threads (e.g. on a
non-static thread pool).</p>
<p>Another solution might use the sender/receiver framework:</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>    exec<span class="op">::</span>scheduler <span class="kw">auto</span> sched0 <span class="op">=</span> cppos<span class="op">::</span>ContextCore0<span class="op">::</span>LoopScheduler<span class="op">();</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>    exec<span class="op">::</span>scheduler <span class="kw">auto</span> sched1 <span class="op">=</span> cppos<span class="op">::</span>ContextCore1<span class="op">::</span>LoopScheduler<span class="op">();</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>    AudioServer srv<span class="op">(</span>sched0<span class="op">,</span> srvAddr<span class="op">,</span> audioPort<span class="op">);</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>    srv<span class="op">.</span>readPacket<span class="op">()</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>        <span class="op">|</span> bufferedTransfer<span class="op">&lt;</span>netBufferSize<span class="op">&gt;(</span>sched1<span class="op">)</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>        <span class="op">|</span> then<span class="op">(</span>soundDecode<span class="op">)</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>        <span class="op">|</span> bufferedTransfer<span class="op">&lt;</span>pcmBufferSize<span class="op">&gt;(</span>sched0<span class="op">)</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>        <span class="op">|</span> sendI2sChunk<span class="op">()</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>        <span class="op">|</span> runForever<span class="op">();</span></span></code></pre></div>
<p>Here <code>bufferedTransfer</code> uses a queue internally.</p>
<p>Yet another solution might use coroutines:</p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a>CoHandle receive<span class="op">()</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">while</span> <span class="op">(</span><span class="kw">true</span><span class="op">)</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    <span class="op">{</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>        <span class="bu">std::</span>span<span class="op">&lt;</span><span class="dt">uint8_t</span><span class="op">&gt;</span> buf <span class="op">=</span> <span class="cf">co_await</span> srv<span class="op">.</span>coReceiveWireChunk<span class="op">();</span> <span class="co">// blocks</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>        <span class="cf">co_await</span> opusQueuePush<span class="op">.</span>wait_push<span class="op">(</span>buf<span class="op">);</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>CoHandle decode<span class="op">()</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>    <span class="cf">while</span> <span class="op">(</span><span class="kw">true</span><span class="op">)</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>    <span class="op">{</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>        <span class="bu">std::</span>span<span class="op">&lt;</span><span class="dt">uint8_t</span><span class="op">&gt;</span> inBuf<span class="op">;</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>        <span class="cf">co_await</span> opusQueuePull<span class="op">.</span>wait_pop<span class="op">(</span>inBuf<span class="op">);</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>        <span class="dt">int</span> samples <span class="op">=</span> opus_decode<span class="op">(</span>decoder<span class="op">,</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>                                  inBuf<span class="op">.</span>data<span class="op">(),</span> inBuf<span class="op">.</span>size<span class="op">(),</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a>                                  decodeBuf<span class="op">.</span>data<span class="op">(),</span> maxFrameSamples<span class="op">,</span></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a>                                  <span class="dv">0</span><span class="op">);</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a>        <span class="bu">std::</span>span<span class="op"> </span>outBuf<span class="op">(</span>decodeBuf<span class="op">.</span>data<span class="op">(),</span> samples<span class="op">);</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a>        <span class="cf">co_await</span> pcmQueuePush<span class="op">.</span>wait_push<span class="op">(</span>outBuf<span class="op">);</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a>CoHandle play<span class="op">()</span></span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a>    <span class="cf">while</span> <span class="op">(</span><span class="kw">true</span><span class="op">)</span></span>
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a>    <span class="op">{</span></span>
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a>        <span class="bu">std::</span>span<span class="op">&lt;</span><span class="dt">uint8_t</span><span class="op">&gt;</span> inBuf<span class="op">;</span></span>
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a>        <span class="cf">co_await</span> pcmQueuePull<span class="op">.</span>wait_pop<span class="op">(</span>inBuf<span class="op">);</span></span>
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a>        <span class="dt">uint32_t</span> <span class="at">const</span> <span class="op">*</span>start <span class="op">=</span> inBuf<span class="op">.</span>data<span class="op">();</span></span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a>        <span class="dt">size_t</span> offset <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a>        <span class="cf">while</span> <span class="op">(</span>offset <span class="op">&lt;</span> size<span class="op">)</span></span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a>        <span class="op">{</span></span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a>            <span class="dt">size_t</span> bytesDone<span class="op">;</span></span>
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a>            <span class="cf">co_await</span> co_i2s_channel_write<span class="op">(</span>tx<span class="op">,</span></span>
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a>                                          start <span class="op">+</span> offset<span class="op">,</span></span>
<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a>                                          size <span class="op">-</span> offset<span class="op">,</span></span>
<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a>                                          <span class="op">&amp;</span>bytesDone<span class="op">,</span></span>
<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a>                                          noTimeout<span class="op">);</span></span>
<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a>            offset <span class="op">+=</span> bytesDone<span class="op">;</span></span>
<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a>        <span class="op">}</span></span>
<span id="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>A solution using fibers would essentially look like the threads
version.</p>
<p>Currently Standard C++ only provides mechanisms for the threads
version (though the queue is still missing). But as coroutines are
already standardized and fibers on their way there should be a more
generic mechanism.</p>
<h2 id="some-details">Some Details</h2>
<p>Implementing a queue that works for the different solutions is a
challenge. Implementing a queue that only supports coroutines, or only
fibers, or only threads is quite simple.</p>
<p>But if the solution with coroutines turns out to be too slow and the
decoding should be done on a different core, while
<code>receive()</code> and <code>play()</code> remain coroutines, it
gets more difficult.</p>
<p>And the queues used here are completely single-ended, but e.g. for a
mutex that works across all kinds of execution contexts it gets really
complicated.</p>
<p>The <code>event</code> abstraction turns out to be useful. By
implementing such an event class across different execution context it
turns out that it’s probably sufficient for <code>block()</code> to know
about the current (blocking) execution context. If the current execution
context is known statically, that’s no problem. But if the current
execution context is not known statically (e.g. when locking a mutex in
some library class) there must be a mechanism to detect the current
execution context at runtime.</p>
<p>But for <code>signal()</code> (or <code>notify()</code>) the
execution context of the task to be notified must (quite probably) be
known. For this the event object would need to keep a list of all tasks
that are currently blocked on this event. It may be possible to avoid
heap allocation by allocating the space for the respective data on the
stack of the blocking function. But it should be sufficient for the
<code>notify()</code> to know about the receiving context.</p>
<p>If this turns out to be true (i.e. <code>block()</code> only needs to
know the current context and <code>notify()</code> only needs to know
the receiving context(s)) it would avoid the m:n problem that David
Goldblatt mentioned in a reflector thread.</p>
<p>There may be cases where for the <code>block()</code> it should be
known if the respective notifier is on the same execution context
(e.g. for fibers without scheduler), but even then it’s not m:n. And in
general it may not be known at blocking time who will do the
<code>notify()</code> (e.g. for <code>condition_variable</code> or
multi-ended queues).</p>
<h2 id="design-options">Design Options</h2>
<p>While experimenting with a queue implementation that works across
execution contexts it turned out to be useful to split the queue into a
body with no public interface and push and pull front ends that provided
the push and pop functions, respectively. In this implementation the
queue front ends were templated on the execution context. And the
execution contexts provided a <code>block()</code> function that could
be used by the queue front ends. This particular implementation didn’t
need to provide a <code>notify()</code> that was specific to the
execution context, but in general this would be a requirement on the
execution context as well.</p>
<p>This could be generalized to all kinds of synchronization mechanisms,
but knowing the execution context statically is often infeasible.</p>
<p>Detecting the current execution context at runtime may be a
challenge, but it may be possible.</p>
<p>Another design question is if heap allocation for these mechanism
would be allowed or needs to be always avoided. Events need to keep a
list of all tasks blocked on it, and this list is potentially
unbounded.</p>
<p>And this list probably needs to be type erased, which again may
require memory allocation.</p>
<p>In many cases some kind of event loop may be necessary. The
Networking TS provided such an event loop (<code>io_service</code>).
Again, to deal with all kinds of continuations (<em>completion
tokens</em>) it required type erasure.</p>
<h2 id="questions">Questions</h2>
<p>Before investing in more design explorations, SG1 should provide
answers to a number of questions:</p>
<ol type="1">
<li>Should SG1 put more work into a generic synchronization
interface?</li>
<li>Is <code>event</code> a usable abstraction?</li>
<li>Can we require an implementation to detect the top execution context
a thread of execution is running on?
<ol type="a">
<li>The full chain of execution contexts?</li>
</ol></li>
<li>Do we need a list of potentially blocking functions in the standard
library?</li>
<li>Do we want to allow customization for specific pairs of execution
agents?</li>
</ol>
</body>
</html>
