mirror of
https://github.com/boostorg/boostlook.git
synced 2026-02-26 16:42:14 +00:00
567 lines
31 KiB
HTML
567 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
|
|
<style>html.fonts-loading{visibility:hidden;opacity:0}</style>
|
|
<script>document.documentElement.classList.add('fonts-loading');</script>
|
|
|
|
<link rel="preload" href="../../_/font/NotoSansDisplay.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
|
|
<link rel="preload" href="../../_/font/NotoSansDisplay-Italic.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
|
|
<link rel="preload" href="../../_/font/MonaspaceNeon-Var.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
|
|
<link rel="preload" href="../../_/font/MonaspaceXenon-Var.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
|
|
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
var revealed = false;
|
|
|
|
var reveal = function() {
|
|
if (revealed) return;
|
|
revealed = true;
|
|
document.documentElement.classList.remove('fonts-loading');
|
|
};
|
|
|
|
setTimeout(reveal, 3000);
|
|
|
|
if (!('FontFace' in window) || !('fonts' in document)) {
|
|
setTimeout(reveal, 100);
|
|
return;
|
|
}
|
|
|
|
var uiRoot = '../../_';
|
|
var fonts = [
|
|
{
|
|
family: 'Noto Sans',
|
|
url: uiRoot + '/font/NotoSansDisplay.woff2',
|
|
descriptors: { style: 'normal', weight: '100 900', stretch: '62.5% 100%' }
|
|
},
|
|
{
|
|
family: 'Noto Sans',
|
|
url: uiRoot + '/font/NotoSansDisplay-Italic.woff2',
|
|
descriptors: { style: 'italic', weight: '100 900', stretch: '62.5% 100%' }
|
|
},
|
|
{
|
|
family: 'Monaspace Neon',
|
|
url: uiRoot + '/font/MonaspaceNeon-Var.woff2',
|
|
descriptors: { style: 'normal', weight: '400' }
|
|
},
|
|
{
|
|
family: 'Monaspace Xenon',
|
|
url: uiRoot + '/font/MonaspaceXenon-Var.woff2',
|
|
descriptors: { style: 'italic', weight: '400' }
|
|
}
|
|
];
|
|
|
|
var loadPromises = fonts.map(function(f) {
|
|
try {
|
|
var face = new FontFace(f.family, 'url("' + f.url + '")', f.descriptors);
|
|
return face.load().then(function(loaded) {
|
|
document.fonts.add(loaded);
|
|
return loaded;
|
|
}).catch(function() {
|
|
return null;
|
|
});
|
|
} catch (e) {
|
|
return Promise.resolve(null);
|
|
}
|
|
});
|
|
|
|
Promise.all(loadPromises)
|
|
.then(function() {
|
|
return document.fonts.ready;
|
|
})
|
|
.then(reveal)
|
|
.catch(reveal);
|
|
})();
|
|
</script> <title>Layered Abstractions :: Boost Libraries Documentation</title>
|
|
<link rel="canonical" href="https://antora.cppalliance.org/develop/lib/doc/capy/8.design/8a.CapyLayering.html">
|
|
<link rel="prev" href="8.intro.html">
|
|
<link rel="next" href="8b.Separation.html">
|
|
<meta name="generator" content="Antora 3.1.14">
|
|
<link rel="stylesheet" href="../../_/css/boostlook.css">
|
|
<link rel="stylesheet" href="../../_/css/site.css">
|
|
<link rel="stylesheet" href="../../_/css/vendor/tabs.css">
|
|
<script>
|
|
(function() {
|
|
if (window.self !== window.top) return;
|
|
var theme = localStorage.getItem('antora-theme');
|
|
if (!theme && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
theme = 'dark';
|
|
}
|
|
if (theme === 'dark') document.documentElement.classList.add('dark');
|
|
})();
|
|
</script>
|
|
<script>var uiRootPath = '../../_'</script>
|
|
<link rel="icon" href="../../_/img/favicons/favicon.ico" type="image/x-icon">
|
|
<!-- Favicon configuration -->
|
|
<link rel="apple-touch-icon" sizes="180x180" href="../../_/img/favicons/apple-touch-icon.png">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="../../_/img/favicons/favicon-32x32.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="../../_/img/favicons/favicon-16x16.png">
|
|
<link rel="manifest" href="../../_/img/favicons/site.webmanifest">
|
|
<link rel="shortcut icon" href="../../_/img/favicons/favicon.ico">
|
|
</head>
|
|
<body class="article toc2 toc-left">
|
|
<div class="boostlook">
|
|
<script type="module">import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; mermaid.initialize({"startOnLoad":true});</script> <div id="header">
|
|
<div id="toc" class="nav-container toc2" data-component="capy" data-version="">
|
|
<aside class="nav">
|
|
<button class="nav-close"></button>
|
|
<div class="panels">
|
|
<div class="nav-panel-menu is-active" data-panel="menu">
|
|
<nav class="nav-menu">
|
|
<div class="title-row">
|
|
<h3 class="title"><a href="../index.html">Boost.Capy</a></h3>
|
|
<button class="theme-toggle" aria-label="Toggle dark mode" title="Toggle theme" style="display:none">
|
|
<i class="fas fa-sun theme-icon-light"></i>
|
|
<i class="fas fa-moon theme-icon-dark"></i>
|
|
</button> </div>
|
|
<ul class="nav-list">
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../index.html">Introduction</a>
|
|
</li>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../why-capy.html">Why Capy?</a>
|
|
</li>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../quick-start.html">Quick Start</a>
|
|
</li>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../2.cpp20-coroutines/2.intro.html">Introduction To C++20 Coroutines</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../2.cpp20-coroutines/2a.foundations.html">Part I: Foundations</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../2.cpp20-coroutines/2b.syntax.html">Part II: C++20 Syntax</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../2.cpp20-coroutines/2c.machinery.html">Part III: Coroutine Machinery</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../2.cpp20-coroutines/2d.advanced.html">Part IV: Advanced Topics</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../3.concurrency/3.intro.html">Introduction to Concurrency</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../3.concurrency/3a.foundations.html">Part I: Foundations</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../3.concurrency/3b.synchronization.html">Part II: Synchronization</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../3.concurrency/3c.advanced.html">Part III: Advanced Primitives</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../3.concurrency/3d.patterns.html">Part IV: Communication & Patterns</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../4.coroutines/4.intro.html">Coroutines in Capy</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4a.tasks.html">The task Type</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4b.launching.html">Launching Coroutines</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4c.executors.html">Executors and Execution Contexts</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4d.io-awaitable.html">The IoAwaitable Protocol</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4e.cancellation.html">Stop Tokens and Cancellation</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4f.composition.html">Concurrent Composition</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../4.coroutines/4g.allocators.html">Frame Allocators</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../5.buffers/5.intro.html">Buffer Sequences</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5a.overview.html">Why Concepts, Not Spans</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5b.types.html">Buffer Types</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5c.sequences.html">Buffer Sequences</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5d.system-io.html">System I/O Integration</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5e.algorithms.html">Buffer Algorithms</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../5.buffers/5f.dynamic.html">Dynamic Buffers</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../6.streams/6.intro.html">Stream Concepts</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6a.overview.html">Overview</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6b.streams.html">Streams (Partial I/O)</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6c.sources-sinks.html">Sources and Sinks (Complete I/O)</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6d.buffer-concepts.html">Buffer Sources and Sinks</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6e.algorithms.html">Transfer Algorithms</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../6.streams/6f.isolation.html">Physical Isolation</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../7.examples/7.intro.html">Example Programs</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7a.hello-task.html">Hello Task</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7b.producer-consumer.html">Producer-Consumer</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7c.buffer-composition.html">Buffer Composition</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7d.mock-stream-testing.html">Mock Stream Testing</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7e.type-erased-echo.html">Type-Erased Echo</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7f.timeout-cancellation.html">Timeout with Cancellation</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7g.parallel-fetch.html">Parallel Fetch</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7h.custom-dynamic-buffer.html">Custom Dynamic Buffer</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7i.echo-server-corosio.html">Echo Server with Corosio</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="../7.examples/7j.stream-pipeline.html">Stream Pipeline</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="8.intro.html">Design</a>
|
|
</li>
|
|
<ul class="nav-list">
|
|
<li class=" is-current-page" data-depth="2">
|
|
<a class="nav-link" href="8a.CapyLayering.html">Layered Abstractions</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8b.Separation.html">Why Capy Is Separate</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8c.ReadStream.html">ReadStream</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8d.ReadSource.html">ReadSource</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8e.BufferSource.html">BufferSource</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8f.WriteStream.html">WriteStream</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8g.WriteSink.html">WriteSink</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8h.BufferSink.html">BufferSink</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8i.TypeEraseAwaitable.html">Type-Erasing Awaitables</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8j.any_buffer_sink.html">AnyBufferSink</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8k.Executor.html">Executor</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8l.RunApi.html">Run API</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8m.WhyNotCobalt.html">Why Not Cobalt?</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8n.WhyNotCobaltConcepts.html">Why Not Cobalt Concepts?</a>
|
|
</li>
|
|
<li class="" data-depth="2">
|
|
<a class="nav-link" href="8o.WhyNotTMC.html">Why Not TooManyCooks?</a>
|
|
</li>
|
|
</ul>
|
|
<li class="" data-depth="1">
|
|
<a class="nav-link" href="../reference/boost/capy.html">Reference</a>
|
|
</li>
|
|
</ul>
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
</div> <div id="content">
|
|
<article class="doc max-width-reset">
|
|
<div class="toolbar" role="navigation">
|
|
<button class="nav-toggle"></button>
|
|
<nav class="breadcrumbs" aria-label="breadcrumbs">
|
|
<ul>
|
|
<li>
|
|
<a href="../index.html" aria-label="Home: Boost.Capy">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 -960 960 960" fill="#000000" aria-hidden="true"><path d="M160-120v-480l320-240 320 240v480H560v-280H400v280H160Z"/></svg>
|
|
</a>
|
|
</li>
|
|
<li><a href="8.intro.html">Design</a></li>
|
|
<li><a href="8a.CapyLayering.html">Layered Abstractions</a></li>
|
|
</ul>
|
|
</nav>
|
|
<div class="spirit-nav">
|
|
<a accesskey="p" href="8.intro.html">
|
|
<span class="material-symbols-outlined" title="Previous: Design">arrow_back</span>
|
|
</a>
|
|
<a accesskey="u" href="8.intro.html">
|
|
<span class="material-symbols-outlined" title="Up: Design">arrow_upward</span>
|
|
</a>
|
|
<a accesskey="n" href="8b.Separation.html">
|
|
<span class="material-symbols-outlined" title="Next: Why Capy Is Separate">arrow_forward</span>
|
|
</a>
|
|
</div></div>
|
|
<h1 class="page">Layered Abstractions</h1>
|
|
<div id="preamble">
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>C++ async libraries have traditionally forced users into a single abstraction level, and every choice comes with baggage. You go with templates and you get zero overhead, full optimization, and unreadable error messages that scroll for pages. Compile times explode. You cannot hide implementation behind a compilation boundary, so you have no ABI stability. You go with virtual dispatch and you get readable code, stable ABIs, and a runtime cost that every call path pays whether it needs to or not.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This is a false binary. Users need different things at different layers of their system. A protocol parser needs zero-copy buffer access and zero overhead because it runs on every byte of every message. Business logic needs readability because the person maintaining it in two years needs to understand what it does. Library boundaries need ABI stability because you do not want downstream code recompiling every time an internal detail changes.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>One abstraction level cannot serve all of these needs simultaneously. The insight behind Capy’s architecture is that users should choose the abstraction level appropriate to each part of their code, and the library should make that choice natural rather than painful.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_the_three_layers"><a class="anchor" href="#_the_three_layers"></a>The Three Layers</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Capy offers three layers. They coexist. They interoperate. Users pick the one that matches their constraints.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The first layer is concepts. These are the template-based interfaces: <code>ReadStream</code>, <code>WriteStream</code>, <code>BufferSink</code>, <code>BufferSource</code>. Algorithms written against concepts get full optimization. The compiler sees through everything. There is no indirection, no vtable, no allocation overhead. This is what you use for hot inner loops, for protocol parsing, for any path where performance dominates:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">template<BufferSource Src, WriteSink Sink>
|
|
io_task<std::size_t>
|
|
push_to(Src& source, Sink& sink);</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The cost is that templates propagate. Every caller sees the full implementation. Compile times grow. You cannot hide this behind a <code>.cpp</code> file.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The second layer is type-erased wrappers. <code>any_stream</code>, <code>any_read_stream</code>, <code>any_write_stream</code>. These use a vtable internally, similar to <code>std::function</code> but specialized for I/O. You can write an algorithm against <code>any_stream&</code> and it compiles once, lives in a single translation unit, and works with any stream type:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">task<> echo(any_stream& stream)
|
|
{
|
|
char buf[1024];
|
|
for(;;)
|
|
{
|
|
auto [ec, n] = co_await stream.read_some(
|
|
mutable_buffer(buf));
|
|
if(ec.failed())
|
|
co_return;
|
|
co_await write(stream, const_buffer(buf, n));
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The cost is a virtual call per I/O operation. For operations dominated by syscalls and network latency, this cost is invisible. For tight loops over in-memory buffers, it matters.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The third layer is coroutine type erasure via <code>task<></code>. This is the most powerful form of type erasure in the language. Inside a coroutine, when you write <code>co_await</code>, everything in the awaitable becomes type-erased from the perspective of the caller. The caller sees a <code>task<></code>. The implementation is invisible. A pure virtual function returning <code>task<></code> hides the stream type, the buffer strategy, the algorithm, the error handling - everything:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">class connection_base {
|
|
public:
|
|
task<> run();
|
|
protected:
|
|
virtual task<> do_handshake() = 0;
|
|
virtual task<> do_shutdown() = 0;
|
|
};</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The SSL derived class performs a TLS handshake inside <code>do_handshake()</code>. The TCP derived class just connects. The base class implements all the shared business logic against an <code>any_stream&</code> and never knows what is underneath.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This is the layer you use for architectural boundaries. Plugin systems. Transport abstraction. Anything where you want complete separation between the interface and the implementation.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_compilation_boundary_economics"><a class="anchor" href="#_compilation_boundary_economics"></a>Compilation Boundary Economics</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Benchmarks on Corosio gave us hard numbers on the cost of crossing a compilation boundary. The result is intuitive once you see it: the cost is proportional to the size of the coroutine.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>For small, fast coroutines - the kind that do a quick buffer manipulation and return - the overhead of virtual dispatch plus parameter marshalling is a significant percentage of total execution time. For larger operations - data transfers dominated by syscalls, protocol handshakes, connection establishment - the boundary cost vanishes into noise.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This has a direct implication for how you structure code. Use concepts for tight inner operations where the work per call is small. Use type erasure at module boundaries where the work per call is large enough to absorb the overhead. The library does not make this decision for you. You make it based on your profiling data and your architecture requirements.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The user chooses where the boundary falls. Not the library.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_zero_copy_as_a_first_class_concern"><a class="anchor" href="#_zero_copy_as_a_first_class_concern"></a>Zero-Copy as a First-Class Concern</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Buffer sink and buffer source invert the traditional ownership model. Instead of the caller allocating a buffer, filling it with data, and handing it to the library, the library exposes its own internal storage and the caller fills it in place. Zero copies. The data goes directly where it needs to be.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The <code>BufferSink</code> concept formalizes this with three operations. <code>prepare()</code> returns writable buffers from the sink’s internal storage. The caller writes data into those buffers. <code>commit()</code> tells the sink how many bytes were written:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">concept BufferSink =
|
|
requires(T& sink, std::span<mutable_buffer> dest,
|
|
std::size_t n)
|
|
{
|
|
{ sink.prepare(dest) }
|
|
-> std::same_as<std::span<mutable_buffer>>;
|
|
{ sink.commit(n) } -> IoAwaitable;
|
|
};</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This matters at the protocol level. The HTTP parser’s internal buffer is the buffer you write into. The serializer’s internal buffer is the buffer you read from. There is no intermediate copy between the network and the parser, and no intermediate copy between the serializer and the network.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The key detail is that <code>commit()</code> returns an <code>IoAwaitable</code>. When the sink is backed by a socket, <code>commit()</code> suspends and performs the actual write. When the sink is an in-memory buffer - a string, a parser, a test fixture - <code>commit()</code> completes synchronously without creating a coroutine frame. Same code, same API, no overhead for the synchronous case. This is what makes the buffer abstractions practical for both production I/O and testing.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_symmetric_transfer_and_the_pump"><a class="anchor" href="#_symmetric_transfer_and_the_pump"></a>Symmetric Transfer and the Pump</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>Symmetric transfer is the mechanism that allows Corosio to match or beat ASIO callback throughput. When one coroutine completes and its continuation is ready to run, symmetric transfer reuses the same coroutine frame without allocation and bypasses the global work queue entirely. ASIO callbacks always go through the queue. Symmetric transfer skips that step.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The pump mechanism extends this by allowing multiple inline completions before returning to the queue. If a chain of coroutines completes quickly, the pump lets them execute back-to-back without touching the scheduler. For throughput-sensitive workloads like HTTP servers, this is significant.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The trade-off is P99 latency. While the pump is running inline completions, queued work waits. For latency-sensitive workloads, you want to return to the queue more frequently so that every piece of work gets prompt attention. The pump is configurable. You can disable it entirely for HFT-style workloads that care about tail latency, or let it ramp up for servers that care about throughput.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The frame recycler is a per-thread cache of coroutine frames. Chain workloads that allocate and free frames in sequence benefit from this cache. Fan-out workloads that spawn many concurrent tasks can exhaust it. The <code>right_now</code> pattern addresses this for repeated invocations of the same operation: declare a stack object with a one-element frame cache, and repeated calls reuse that cache without touching the recycler at all. <code>when_all</code> could carry its own private frame cache sized to its arity, giving each child a frame from the parent’s stash via a TLS hook. Every use case that you make better can make another use case worse. You have to pay attention to that which is not seen.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_the_type_system_as_architecture"><a class="anchor" href="#_the_type_system_as_architecture"></a>The Type System as Architecture</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>The derived class pattern is the practical application of everything described above. A base class implements business logic against type-erased references. Derived classes carry concrete types and implement the operations that differ between transports.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>Each derived class lives in its own translation unit. The linker only pulls in what is used. Users who need only TCP link only TCP code. Users who need SSL link the SSL translation unit. No variant that pulls in all transport code. No enum and switch that ties everything together. The type system enforces the separation:</p>
|
|
</div>
|
|
<div class="listingblock">
|
|
<div class="content">
|
|
<pre class="highlightjs highlight"><code class="language-cpp hljs" data-lang="cpp">// User who needs only plain TCP
|
|
tcp_connection conn(ctx);
|
|
|
|
// User who needs TLS
|
|
ssl_connection conn(ctx, tls_context);
|
|
|
|
// User who needs runtime transport selection
|
|
multi_connection conn(ctx, config);</code></pre>
|
|
</div>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This extends naturally to testing. Derive a mock connection that uses Capy’s test stream with a fuse for error injection, and a mock timer for deterministic time control. The base class algorithm runs against the mock exactly as it would against a real connection. No conditional compilation, no test-only code paths in production logic, no <code>#ifdef TESTING</code>.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>A database library built this way can express protocol parsing with zero-copy buffer sinks for the hot path, implement connection logic against type-erased streams for maintainability, let users select TCP vs. SSL vs. Unix at the type level for linker efficiency, and test without linking OpenSSL or running a real server. The hot paths use concepts. The cold paths use virtual dispatch. The architectural boundaries use <code>task<></code>. Every user finds the abstraction level they need.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="sect1">
|
|
<h2 id="_choosing_the_right_layer"><a class="anchor" href="#_choosing_the_right_layer"></a>Choosing the Right Layer</h2>
|
|
<div class="sectionbody">
|
|
<div class="paragraph">
|
|
<p>The question that matters is: can a library author look at their problem and immediately see which layer to use? If the answer is yes, the design is working. If they have to think about it, something is wrong.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><strong>Protocol parsing:</strong> use <code>BufferSink</code> and <code>BufferSource</code> concepts as template parameters. Zero copy, zero overhead. Call member functions whose awaitables do all the work, with no coroutine frame allocation. The compiler optimizes everything.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><strong>Connection management:</strong> use concrete types like <code>tcp_socket</code>. These give you <code>connect()</code> and <code>shutdown()</code> - the operations that are transport-specific. But the concrete type is derived from <code>io_stream</code>, a class that models <code>capy::Stream</code>, so you can pass <code>io_stream&</code> to a non-template function for the business logic that sits on top of the connection.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p><strong>Full transport abstraction across a library boundary:</strong> use <code>any_stream</code>. Complete type erasure, but you lose connection management - there is no <code>connect()</code> on an <code>any_stream</code>. This means you have to carefully arrange your code so it genuinely requires a physical separation in the Lakos sense. The protocol logic and the connection logic live in separate components, and the type-erased boundary sits between them.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>The layers compose. An algorithm written against a <code>BufferSource</code> concept can be called from inside a coroutine that is type-erased behind a <code>task<></code>, which is dispatched through a virtual function on a base class that holds an <code>any_stream&</code>. Each layer handles its part. Nothing leaks through the boundaries unless you want it to.</p>
|
|
</div>
|
|
<div class="paragraph">
|
|
<p>This is what it means when we say the user chooses. Capy provides the tools. The user decides where the boundaries go based on what they know about their performance requirements, their compilation budget, and their architecture. The library does not impose a single answer because there is not one.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="edit-this-page">
|
|
<a href="https://github.com/cppalliance/capy/edit/develop/doc/modules/ROOT/pages/8.design/8a.CapyLayering.adoc">Edit this Page</a>
|
|
</div>
|
|
<nav class="pagination">
|
|
<span class="prev"><a href="8.intro.html">Design</a></span>
|
|
<span class="next"><a href="8b.Separation.html">Why Capy Is Separate</a></span>
|
|
</nav>
|
|
</article>
|
|
</div>
|
|
<div id="footer">
|
|
<script id="site-script" src="../../_/js/site.js" data-ui-root-path="../../_"></script>
|
|
<script async src="../../_/js/vendor/highlight.js"></script>
|
|
<script async src="../../_/js/vendor/tabs.js" data-sync-storage-key="preferred-tab"></script>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|