<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.stevenskelton.ca/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.stevenskelton.ca/" rel="alternate" type="text/html" /><updated>2026-01-01T21:56:01-05:00</updated><id>https://www.stevenskelton.ca/feed.xml</id><title type="html">Steven Skelton</title><subtitle>Code examples and implementation details encountered during my software development. Mostly back-end = Scala; mobile = Dart &amp;&amp; Flutter.</subtitle><author><name>Steven Skelton</name></author><entry><title type="html">Flutter gRPC File Transfers</title><link href="https://www.stevenskelton.ca/flutter-grpc-file-transfer/" rel="alternate" type="text/html" title="Flutter gRPC File Transfers" /><published>2024-10-03T00:00:00-04:00</published><updated>2024-10-03T00:00:00-04:00</updated><id>https://www.stevenskelton.ca/flutter-grpc-file-transfer</id><content type="html" xml:base="https://www.stevenskelton.ca/flutter-grpc-file-transfer/"><![CDATA[<p>Modern mobile apps are benefiting from using gRPC with Protobuf to reduce boilerplate code for their client-server 
networking implementation. While directly implemented by gRPC, the library can easily implement all necessary features
for efficient file transfers.<!--more--></p>

<div class="html-bg">
  <strong>Other Posts in this Series</strong>
  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/grpc-file-transfer-zio/" rel="permalink">File Transfers using gRPC and ZIO
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-10-01T00:00:00-04:00">October 1, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          11 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  
</div>

<style>
    .multiplex_responsive_1 { width: 360px; height: 100px; margin-top: 1em; margin-bottom: 1em; }
    @media(min-width: 720px) { .multiplex_responsive_1 { width: 360px; margin: 0; height: 500px; } }
</style>

<div class="toc-container">
    <aside class="sidebar__right">
<nav class="toc">
      <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> Table of Contents</h4></header>
<ul class="toc__menu" id="markdown-toc">
  <li><a href="#grpc-versus-javascript-libraries" id="markdown-toc-grpc-versus-javascript-libraries">gRPC versus JavaScript libraries</a></li>
  <li><a href="#pubspecyaml-dependencies" id="markdown-toc-pubspecyaml-dependencies">pubspec.yaml Dependencies</a></li>
  <li><a href="#flutter-workflow" id="markdown-toc-flutter-workflow">Flutter Workflow</a></li>
  <li><a href="#flutter-code" id="markdown-toc-flutter-code">Flutter Code</a>    <ul>
      <li><a href="#code-file_transfer_change_notifierdart" id="markdown-toc-code-file_transfer_change_notifierdart">Code: file_transfer_change_notifier.dart</a>        <ul>
          <li><a href="#filesendchangenotifier" id="markdown-toc-filesendchangenotifier">FileSendChangeNotifier</a></li>
          <li><a href="#filereceivechangenotifier" id="markdown-toc-filereceivechangenotifier">FileReceiveChangeNotifier</a></li>
        </ul>
      </li>
      <li><a href="#code-file_transfer_progressdart" id="markdown-toc-code-file_transfer_progressdart">Code: file_transfer_progress.dart</a></li>
      <li><a href="#code-file_transfer_grpc_clientdart" id="markdown-toc-code-file_transfer_grpc_clientdart">Code: file_transfer_grpc_client.dart</a></li>
      <li><a href="#flutter-widget-file_transfer_progress_bar_widgetdart" id="markdown-toc-flutter-widget-file_transfer_progress_bar_widgetdart">Flutter Widget: file_transfer_progress_bar_widget.dart</a></li>
      <li><a href="#flutter-widget-file_transfer_widgetdart" id="markdown-toc-flutter-widget-file_transfer_widgetdart">Flutter Widget: file_transfer_widget.dart</a></li>
    </ul>
  </li>
</ul>

    </nav>
</aside>
    <div class="toc-ad"><ins class="adsbygoogle multiplex_responsive_1" style="display:block" data-ad-client="ca-pub-5000348308466945" data-ad-slot="5418078712"></ins>
        <script>
            (adsbygoogle = window.adsbygoogle || []).push({});
        </script></div>
</div>

<h1 id="grpc-versus-javascript-libraries">gRPC versus JavaScript libraries</h1>

<p>Cloud providers have libraries with support for uploading and downloading from cloud storage. These libraries approach
transfers as a large number of HTTP requests, breaking up large files into smaller transfer requests. As requests move
from pending, in progress, to complete, the current status of the transfer can be calculated.</p>

<p>The gRPC approach is similar, however instead of using separate HTTP requests each file chunk is sequentially sent 
over an HTTP/2 stream. The performance of both approaches is comparable, as multiple requests are multiplexed over a 
single HTTP connection closely mirroring how HTTP/2 streaming operates over a single connection. The only real 
difference is encountered when moving from sequential to concurrent operation, ie: sending multiple chunks at a time. 
Request multiplexing directly support this, however multiple stream requests need to be used to implement this using 
gRPC as each stream is inherently sequential and ordered.</p>

<h1 id="pubspecyaml-dependencies">pubspec.yaml Dependencies</h1>

<p>This sample client requires 2 Flutter libraries:</p>
<ul>
  <li><a href="https://pub.dev/packages/grpc">grpc</a> core implementation for networking.</li>
  <li><a href="https://pub.dev/packages/image_picker">image_picker</a> implements an image picker widget to easily select upload files.</li>
</ul>

<h1 id="flutter-workflow">Flutter Workflow</h1>

<p>Adding the complexity of streaming file transfers only benefits larger file transfers. Small transfers that can fit into
the 4MB maximum message size for GRPC would be well served with the more simple approach.</p>

<p>In our demo, we are using a 35MB image originated by the James Webb Space Telescope, 
<a href="https://webbtelescope.org/contents/media/images/2024/128/01J6CXCDNSGF87TZEX379WHDXB">MACS J0417.5-1154 Wide Field</a>
, 4623 X 4623, PNG (35.14 MB).</p>

<figure class="">
  <img src="/assets/images/2024/10/01_select_upload_image.jpg" alt="Step 1: Initial UI screen" class="fighascaption" style="height: 450px;" /><figcaption>
      Step 1: Initial UI screen

    </figcaption></figure>

<figure class="">
  <img src="/assets/images/2024/10/02_image_picker.jpg" alt="Step 2: Select upload file" class="fighascaption" style="height: 450px;" /><figcaption>
      Step 2: Select upload file

    </figcaption></figure>

<figure class="">
  <img src="/assets/images/2024/10/03_upload_progress.jpg" alt="Step 3: File upload to server" class="fighascaption" style="height: 450px;" /><figcaption>
      Step 3: File upload to server

    </figcaption></figure>

<figure class="">
  <img src="/assets/images/2024/10/04_upload_complete.jpg" alt="Step 4: File available on server" class="fighascaption" style="height: 450px;" /><figcaption>
      Step 4: File available on server

    </figcaption></figure>

<figure class="">
  <img src="/assets/images/2024/10/05_download_progress.jpg" alt="Step 5: Download from server to client" class="fighascaption" style="height: 450px;" /><figcaption>
      Step 5: Download from server to client

    </figcaption></figure>

<figure class="">
  <img src="/assets/images/2024/10/06_download_complete.jpg" alt="Step 6: Successful upload and download" class="fighascaption" style="height: 450px;" /><figcaption>
      Step 6: Successful upload and download

    </figcaption></figure>

<h1 id="flutter-code">Flutter Code</h1>

<p>This implementation will use the state management built into Flutter:</p>
<ul>
  <li><a href="https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html">ChangeNotifier</a> and <a href="https://api.flutter.dev/flutter/widgets/ListenableBuilder-class.html">ListenableBuilder</a></li>
  <li><a href="https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html">ValueNotifier</a> and <a href="https://api.flutter.dev/flutter/widgets/ValueListenableBuilder-class.html">ValueListenableBuilder</a></li>
</ul>

<p>These work in the same way, with <code class="language-plaintext highlighter-rouge">ValueNotifier</code> being an implementation inheriting from <code class="language-plaintext highlighter-rouge">ChangeNotifier</code> suitable for
simplified immutable state. The <code class="language-plaintext highlighter-rouge">ChangeNotifier</code> and <code class="language-plaintext highlighter-rouge">ValueNotifier</code> are data classes storing an internal state, which 
are rendered by <code class="language-plaintext highlighter-rouge">ListenableBuilder</code> and <code class="language-plaintext highlighter-rouge">ValueListenableBuilder</code> widgets, with the ability to trigger widget rendering
on state changes.</p>

<p>There are 5 states to watch in this workflow:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">ValueNotifier&lt;XFile?&gt;</code> representing a selected upload image (<code class="language-plaintext highlighter-rouge">XFile</code>) on the client, chosen by <em>ImagePicker</em>.</li>
  <li><code class="language-plaintext highlighter-rouge">ValueNotifier&lt;FileSendChangeNotifier?&gt;</code> represented by either <code class="language-plaintext highlighter-rouge">null</code> when there isn’t an upload, or non-null for an upload.</li>
  <li><code class="language-plaintext highlighter-rouge">FileSendChangeNotifier</code> represents an upload process, either actively uploading or completed.</li>
  <li><code class="language-plaintext highlighter-rouge">ValueNotifier&lt;FileReceiveChangeNotifier?&gt;</code> represented by either <code class="language-plaintext highlighter-rouge">null</code> when there isn’t a download, or non-null for a download.</li>
  <li><code class="language-plaintext highlighter-rouge">FileReceiveChangeNotifier</code> represents a download process, either actively downloading or completed.</li>
</ul>

<h2 id="code-file_transfer_change_notifierdart">Code: file_transfer_change_notifier.dart</h2>

<p>Both uploads and downloads have been modeled using an abstract class <code class="language-plaintext highlighter-rouge">FileTransferChangeNotifier</code>.
This class will track the transfer process, with <code class="language-plaintext highlighter-rouge">update</code> being called as each <code class="language-plaintext highlighter-rouge">FileChunk</code> of the stream is 
processed.</p>

<p>A <code class="language-plaintext highlighter-rouge">close</code> method should be called after a successful transfer has completed.</p>

<p>The implementations for upload and download require different implementations because of how stream processing has
been implemented: uploads process stream elements before they are uploaded (ie: transfer will happen), and downloads
process stream elements after they are downloaded (ie: transfer has happened).</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">FileTransferChangeNotifier</span> <span class="k">with</span> <span class="n">ChangeNotifier</span> <span class="p">{</span>
  <span class="c1">/// State</span>
  <span class="n">FileTransferProgress</span> <span class="n">_progress</span><span class="p">;</span>
  <span class="n">DateTime</span> <span class="n">_lastUpdate</span><span class="p">;</span>

  <span class="c1">/// Used to render UI Widgets</span>
  <span class="n">FileTransferProgress</span> <span class="kd">get</span> <span class="n">progress</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">_progress</span><span class="p">;</span>
  
  <span class="c1">/// `fileChunk` is about to be sent on send, or has been received on receive.</span>
  <span class="kt">void</span> <span class="n">update</span><span class="p">(</span><span class="n">FileChunk</span> <span class="n">fileChunk</span><span class="p">);</span>

  <span class="c1">/// Mark the transfer as successful and complete.</span>
  <span class="c1">/// `filename` argument should be the server reference on send, </span>
  <span class="c1">///  or the local file path if received.</span>
  <span class="kt">void</span> <span class="n">close</span><span class="p">(</span><span class="kt">String</span> <span class="n">filename</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="filesendchangenotifier">FileSendChangeNotifier</h3>

<p>This class implements the upload process, each <code class="language-plaintext highlighter-rouge">update</code> call will mark the current <code class="language-plaintext highlighter-rouge">FileChunk</code> as in progress and all
previous as completed, with <code class="language-plaintext highlighter-rouge">close</code> marking the current <code class="language-plaintext highlighter-rouge">FileChunk</code> as completed.</p>

<h3 id="filereceivechangenotifier">FileReceiveChangeNotifier</h3>

<p>This class implements the download process, each <code class="language-plaintext highlighter-rouge">update</code> call will mark the current <code class="language-plaintext highlighter-rouge">FileChunk</code> as completed, with 
<code class="language-plaintext highlighter-rouge">close</code> unnecessary but for performing the client-server filename mapping should it exist.</p>

<h2 id="code-file_transfer_progressdart">Code: file_transfer_progress.dart</h2>

<p>This class is the immutable data model used by <em>FileSendChangeNotifier</em> and <em>FileReceiveChangeNotifier</em>.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">FileTransferProgress</span> <span class="p">{</span>

  <span class="c1">//Populated when process starts.</span>
  <span class="c1">// null only for an upload that hasn't processed its first FileChunk</span>
  <span class="kd">final</span> <span class="n">DateTime</span><span class="o">?</span> <span class="n">startTimestamp</span><span class="p">;</span>
  
  <span class="c1">//Populated by calling `close`</span>
  <span class="kd">final</span> <span class="n">DateTime</span><span class="o">?</span> <span class="n">endTimestamp</span><span class="p">;</span>
  
  <span class="c1">//Populated from first FileChunk</span>
  <span class="kd">final</span> <span class="kt">int</span> <span class="n">fileSizeInBytes</span><span class="p">;</span>
  <span class="kd">final</span> <span class="kt">int</span> <span class="n">chunkSizeInBytes</span><span class="p">;</span>
  
  <span class="c1">//Calculated when each FileChunk is processed</span>
  <span class="kd">final</span> <span class="kt">int</span> <span class="n">transferredBytes</span><span class="p">;</span>
  <span class="kd">final</span> <span class="kt">int</span> <span class="n">bytesPerSecond</span><span class="p">;</span>
  <span class="kd">final</span> <span class="kt">int</span> <span class="n">secondsRemaining</span><span class="p">;</span>
  
  <span class="c1">//Populated when process starts</span>
  <span class="c1">//Updated by calling `close`</span>
  <span class="kd">final</span> <span class="kt">String</span><span class="o">?</span> <span class="n">filename</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="code-file_transfer_grpc_clientdart">Code: file_transfer_grpc_client.dart</h2>

<p>This class implements the GRPC client; initiating GRPC connections and requests, as well as handling <code class="language-plaintext highlighter-rouge">Stream</code> creation 
to and from the local filesystem.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">FileTransferGrpcClient</span> <span class="p">{</span>
  <span class="c1">/// Code-generated client</span>
  <span class="kd">final</span> <span class="n">FileServiceClient</span> <span class="n">_fileServiceClient</span><span class="p">;</span>
  
  <span class="c1">/// Requests</span>
  <span class="n">Future</span><span class="p">&lt;</span><span class="n">FileSendChangeNotifier</span><span class="p">&gt;</span> <span class="n">upload</span><span class="p">(</span><span class="n">XFile</span> <span class="n">xFile</span><span class="p">)</span>
  <span class="n">FileReceiveChangeNotifier</span> <span class="n">download</span><span class="p">(</span><span class="kt">String</span> <span class="n">serverFilename</span><span class="p">,</span> <span class="n">File</span> <span class="n">output</span><span class="p">)</span>
  
  <span class="c1">/// Static Helpers</span>
  <span class="kd">static</span> <span class="n">Stream</span><span class="p">&lt;(</span><span class="n">Uint8List</span><span class="p">,</span> <span class="kt">int</span><span class="p">)&gt;</span> <span class="n">_calcOffset</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">Stream</span><span class="p">&lt;</span><span class="n">Uint8List</span><span class="p">&gt;</span> <span class="n">input</span><span class="p">)</span>
  <span class="kd">static</span> <span class="n">Stream</span><span class="p">&lt;</span><span class="n">Uint8List</span><span class="p">&gt;</span> <span class="n">_rechunkStream</span><span class="p">(</span><span class="n">Stream</span><span class="p">&lt;</span><span class="n">Uint8List</span><span class="p">&gt;</span> <span class="n">input</span><span class="p">)</span>
  <span class="kd">static</span> <span class="n">Iterable</span><span class="p">&lt;</span><span class="n">Uint8List</span><span class="p">&gt;</span> <span class="n">_rechunkUint8List</span><span class="p">(</span><span class="n">Uint8List</span> <span class="n">list</span><span class="p">,</span> <span class="kt">int</span> <span class="n">length</span><span class="p">)</span>
  <span class="kd">static</span> <span class="n">Future</span><span class="p">&lt;</span><span class="n">Stream</span><span class="p">&lt;</span><span class="n">FileChunk</span><span class="p">&gt;&gt;</span> <span class="n">_readXFile</span><span class="p">(</span><span class="n">XFile</span> <span class="n">xFile</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="flutter-widget-file_transfer_progress_bar_widgetdart">Flutter Widget: file_transfer_progress_bar_widget.dart</h2>

<h2 id="flutter-widget-file_transfer_widgetdart">Flutter Widget: file_transfer_widget.dart</h2>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">FileTransferProgressBarWidget</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">ValueNotifier</span><span class="p">&lt;</span><span class="n">FileTransferChangeNotifier</span><span class="o">?</span><span class="p">&gt;</span> <span class="n">fileTransferChangeNotifierNotifier</span><span class="p">;</span>

  <span class="c1">/// Returns % progress, display text: line1, line 2</span>
  <span class="c1">/// action = 'upload' or 'download'</span>
  <span class="kd">static</span> <span class="p">(</span><span class="kt">double</span><span class="p">,</span> <span class="kt">String</span><span class="p">,</span> <span class="kt">String</span><span class="p">)</span> <span class="n">calculateTextAndProgress</span><span class="p">(</span>
    <span class="n">FileTransferProgress</span> <span class="n">fileTransferProgress</span><span class="p">,</span> 
    <span class="kt">String</span> <span class="n">action</span><span class="p">,</span>
  <span class="p">)</span>
  
  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">/// Displays a Column([ProgressBar, Text(line1), Text(line2)]);</span>
    <span class="k">return</span> <span class="n">ValueListenableBuilder</span><span class="p">();</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>Steven Skelton</name></author><category term="Dart" /><category term="Bloc" /><category term="gRPC" /><summary type="html"><![CDATA[Modern mobile apps are benefiting from using gRPC with Protobuf to reduce boilerplate code for their client-server networking implementation. While directly implemented by gRPC, the library can easily implement all necessary features for efficient file transfers.]]></summary></entry><entry><title type="html">File Transfers using gRPC and ZIO</title><link href="https://www.stevenskelton.ca/grpc-file-transfer-zio/" rel="alternate" type="text/html" title="File Transfers using gRPC and ZIO" /><published>2024-10-01T00:00:00-04:00</published><updated>2024-10-01T00:00:00-04:00</updated><id>https://www.stevenskelton.ca/grpc-file-transfer-zio</id><content type="html" xml:base="https://www.stevenskelton.ca/grpc-file-transfer-zio/"><![CDATA[<p>gRPC with Protobuf is a framework to efficiently simplify the client-server networking requirements of modern
applications. One use-case where the low-level simplicity of pure HTTP maintains an advantage over gRPC is handling file
transfers: the uploading and downloading of contiguous binary block data. But gRPC can efficiently replicate all HTTP
functionality within its Protobuf message framework making it unnecessary to host separate gRPC and HTTP servers for
applications.<!--more--></p>

<div class="html-bg">
  <strong>Other Posts in this Series</strong>
  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/flutter-grpc-file-transfer/" rel="permalink">Flutter gRPC File Transfers
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-10-03T00:00:00-04:00">October 3, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          4 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  
</div>

<style>
    .multiplex_responsive_1 { width: 360px; height: 100px; margin-top: 1em; margin-bottom: 1em; }
    @media(min-width: 720px) { .multiplex_responsive_1 { width: 360px; margin: 0; height: 700px; } }
</style>

<div class="toc-container">
    <aside class="sidebar__right">
<nav class="toc">
      <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> Table of Contents</h4></header>
<ul class="toc__menu" id="markdown-toc">
  <li><a href="#comparison-of-grpc-to-http2" id="markdown-toc-comparison-of-grpc-to-http2">Comparison of gRPC to HTTP/2</a>    <ul>
      <li><a href="#http2-backpressure" id="markdown-toc-http2-backpressure">HTTP/2 Backpressure</a></li>
    </ul>
  </li>
  <li><a href="#protobuf-definition" id="markdown-toc-protobuf-definition">Protobuf Definition</a>    <ul>
      <li><a href="#feature-requirements" id="markdown-toc-feature-requirements">Feature Requirements</a></li>
      <li><a href="#server-definition" id="markdown-toc-server-definition">Server Definition</a></li>
    </ul>
  </li>
  <li><a href="#server-implementation" id="markdown-toc-server-implementation">Server Implementation</a>    <ul>
      <li><a href="#implementation-assumptions" id="markdown-toc-implementation-assumptions">Implementation Assumptions</a></li>
      <li><a href="#client-getfile-download" id="markdown-toc-client-getfile-download">Client GetFile (Download)</a></li>
      <li><a href="#client-setfile-upload" id="markdown-toc-client-setfile-upload">Client SetFile (Upload)</a>        <ul>
          <li><a href="#alternative-grpc-metadata-headers-approach" id="markdown-toc-alternative-grpc-metadata-headers-approach">Alternative GRPC Metadata Headers Approach</a></li>
          <li><a href="#zio-approach" id="markdown-toc-zio-approach">ZIO Approach</a>            <ul>
              <li><a href="#zsink-output" id="markdown-toc-zsink-output">ZSink Output</a></li>
              <li><a href="#zsink-processing" id="markdown-toc-zsink-processing">ZSink Processing</a>                <ul>
                  <li><a href="#case-1-invalid-chunk-size" id="markdown-toc-case-1-invalid-chunk-size">Case 1: Invalid Chunk Size</a></li>
                  <li><a href="#case-2-file-channel-not-open" id="markdown-toc-case-2-file-channel-not-open">Case 2: File Channel Not Open</a></li>
                  <li><a href="#case-3-file-channel-open-but-invalid-offset" id="markdown-toc-case-3-file-channel-open-but-invalid-offset">Case 3: File Channel Open But Invalid Offset</a></li>
                  <li><a href="#case-4-file-channel-open-but-chunk-greater-than-remaining-bytes" id="markdown-toc-case-4-file-channel-open-but-chunk-greater-than-remaining-bytes">Case 4: File Channel Open, But Chunk Greater Than Remaining Bytes</a></li>
                  <li><a href="#case-5-file-channel-open-write-to-channel" id="markdown-toc-case-5-file-channel-open-write-to-channel">Case 5: File Channel Open, Write to Channel</a></li>
                </ul>
              </li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

    </nav>
</aside>
    <div class="toc-ad"><ins class="adsbygoogle multiplex_responsive_1" style="display:block" data-ad-client="ca-pub-5000348308466945" data-ad-slot="5418078712"></ins>
        <script>
            (adsbygoogle = window.adsbygoogle || []).push({});
        </script></div>
</div>

<h1 id="comparison-of-grpc-to-http2">Comparison of gRPC to HTTP/2</h1>

<p>Because gRPC is built directly on top of HTTP/2 it is understandable that for simple file transfers gRPC can be viewed
as HTTP/2 with unnecessary overhead. For certain simple tasks, gRPC can never reach the resource efficiency of a pure
HTTP implementation.</p>

<p>For this reason, use-cases with significant volume or large file transfers will see noticeably reduced server resource
demands using HTTP instead of gRPC. However, this comes with the burden of maintaining another server cluster for HTTP,
or sideloading an HTTP server onto the gRPC server. The HTTP performance gain becomes a trade-off against system 
complexity.</p>

<p>Most gRPC overhead comes from the intentional copying of in-memory data models. The Java gRPC implementation will 
recopy userspace data to yet another array simply as a precaution: ensuring no code references and data immutability. 
This to enable assertions about internal state in order to optimize serialization code paths.</p>

<p>On the other hand, HTTP servers can be optimized for their simpler code paths without extra work; reading and writing 
data directly from storage to network with zero or minimal memory buffering and CPU processing.</p>

<h2 id="http2-backpressure">HTTP/2 Backpressure</h2>

<p>The gRPC server implementations leverage the flow control in HTTP/2 streams to apply backpressure. The particular 
settings are implementation dependent, but can usually be modified when initializing the server implementation. 
Performance tuning the gRPC implementation is beyond the scope of this article, with the takeaway being that how much 
data the server will receive from a client before blocking the client stream will also impact transfer performance, 
memory usage, and code settings such as <code class="language-plaintext highlighter-rouge">chunk_size</code>.</p>

<h1 id="protobuf-definition">Protobuf Definition</h1>

<h2 id="feature-requirements">Feature Requirements</h2>

<p>The primary requirements of a file transfer mechanism are:</p>

<ul>
  <li>No additional encoding overhead</li>
  <li>Ability to determine progress</li>
</ul>

<p>Other feature concerns related to client and server implementation will be discussed later, but compatibility should
still be verified when architecting the proto models. These features would include:</p>

<ul>
  <li>the ability to handle partial-file resumption,</li>
  <li>multiplexed / concurrent segment transfers, and</li>
  <li>pre transfer actions, such as authentication, permissions, collision detection and quotas</li>
  <li>post actions, such as file renaming, name sanitation, or moving completed from temp to final directories.</li>
</ul>

<p>The <a href="https://grpc.io/docs/what-is-grpc/core-concepts/#server-streaming-rpc">gRPC stream</a> has the least amount of network
overhead for an indeterminate amount of data. Each gRPC message is an identical type, in our case we will define a
stream of <code class="language-plaintext highlighter-rouge">FileChunk</code> messages.</p>

<p>gRPC supports call <a href="https://grpc.io/docs/guides/metadata/">metadata</a>, which are directly like an HTTP header it is
possible to send additional data which would not be part of
each <code class="language-plaintext highlighter-rouge">FileChunk</code> in the stream. Perhaps, fields which do not vary for each message such as <code class="language-plaintext highlighter-rouge">filename</code> and <code class="language-plaintext highlighter-rouge">file_size</code>
would more efficiently be sent as call metadata, but this is typically</p>

<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">message</span> <span class="nc">FileChunk</span> <span class="p">{</span>
  <span class="c1">//Name of file</span>
  <span class="kt">string</span> <span class="na">filename</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="c1">//Total size of file</span>
  <span class="kt">uint64</span> <span class="na">file_size</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
  <span class="c1">//Starting offset of current chunk</span>
  <span class="kt">uint64</span> <span class="na">offset</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
  <span class="c1">//Binary data of chunk</span>
  <span class="kt">bytes</span> <span class="na">body</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="server-definition">Server Definition</h2>

<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">service</span> <span class="n">FileService</span> <span class="p">{</span>
  <span class="k">rpc</span> <span class="n">GetFile</span> <span class="p">(</span><span class="n">GetFileRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">stream</span> <span class="n">FileChunk</span><span class="p">);</span>
  <span class="k">rpc</span> <span class="n">SetFile</span> <span class="p">(</span><span class="n">stream</span> <span class="n">FileChunk</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">SetFileResponse</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="server-implementation">Server Implementation</h1>

<h2 id="implementation-assumptions">Implementation Assumptions</h2>

<p>Before getting into the server code, a set of assumptions have been made for simplicity.</p>

<p>First, the grpc connection is assumed to have been authenticated, and clients have full access to read/write to the
server’s <code class="language-plaintext highlighter-rouge">filesDirectory</code> directory.  The value of <code class="language-plaintext highlighter-rouge">filesDirectory</code> might depend on the authenticated user of the 
client, with each separate user having access to only a home directory.</p>

<p>The sample <code class="language-plaintext highlighter-rouge">javaFile</code> function maps a <code class="language-plaintext highlighter-rouge">filename</code> request parameter to a server <code class="language-plaintext highlighter-rouge">java.io.File</code>, and implements no
sanitation on the value. Obviously allowing clients to enter <code class="language-plaintext highlighter-rouge">..</code> and <code class="language-plaintext highlighter-rouge">/</code> characters within the <code class="language-plaintext highlighter-rouge">filename</code> will
result in security vulnerabilities.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">def</span> <span class="nf">javaFile</span><span class="o">(</span><span class="n">unsafeFilename</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span><span class="k">:</span> <span class="kt">File</span> <span class="o">=</span> <span class="o">{</span>
  <span class="nc">File</span><span class="o">(</span><span class="n">s</span><span class="s">"$filesDirectory/$unsafeFilename"</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Put requests where <code class="language-plaintext highlighter-rouge">filename</code> already exists will overwrite the existing file. Concurrent requests to read/write the
same file will result in corruption. How to solve this depends on requirements, but a common first recommended change 
would be to write to a temporary server directory and move completed files to a readable directory only after the upload
has completed successfully.</p>

<h2 id="client-getfile-download">Client GetFile (Download)</h2>

<p>A <code class="language-plaintext highlighter-rouge">GetFileRequest</code> client request will result in the server streaming <code class="language-plaintext highlighter-rouge">filename</code> back to the client in a chunk size 
set by the server. The request could easily be expanded with an <em>offset</em> field to allow partial file resumption, or
both <em>offset</em> and <em>end_offset</em> fields to allow concurrent download streams.</p>

<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">message</span> <span class="nc">GetFileRequest</span> <span class="p">{</span>
  <span class="kt">string</span> <span class="na">filename</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The ZIO server implementation for the generated Protobuf is:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">getFile</span><span class="o">(</span><span class="n">request</span><span class="k">:</span> <span class="kt">GetFileRequest</span><span class="o">)</span><span class="k">:</span> <span class="kt">Stream</span><span class="o">[</span><span class="kt">StatusException</span>, <span class="kt">FileChunk</span><span class="o">]</span>
</code></pre></div></div>

<p>The body is fairly simple after creating 2 private helper functions around the <a href="https://zio.github.io/zio-nio/">ZIO NIO</a>
file library.</p>

<p>One function to return the <code class="language-plaintext highlighter-rouge">file_size</code> of a file using ZIO <em>Files.size</em>.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">def</span> <span class="nf">readFileSize</span><span class="o">(</span><span class="n">file</span><span class="k">:</span> <span class="kt">File</span><span class="o">)</span><span class="k">:</span> <span class="kt">IO</span><span class="o">[</span><span class="kt">IOException</span>, <span class="kt">Long</span><span class="o">]</span> <span class="k">=</span> <span class="o">{</span>
  <span class="k">val</span> <span class="nv">path</span> <span class="k">=</span> <span class="nv">Path</span><span class="o">.</span><span class="py">fromJava</span><span class="o">(</span><span class="nv">file</span><span class="o">.</span><span class="py">toPath</span><span class="o">)</span>
  <span class="nv">Files</span><span class="o">.</span><span class="py">exists</span><span class="o">(</span><span class="n">path</span><span class="o">)</span>
    <span class="o">.</span><span class="py">filterOrFail</span><span class="o">(</span><span class="k">_</span> <span class="o">==</span> <span class="kc">true</span><span class="o">)(</span><span class="nc">FileNotFoundException</span><span class="o">(</span><span class="nv">file</span><span class="o">.</span><span class="py">getName</span><span class="o">))</span>
    <span class="o">.</span><span class="py">flatMap</span><span class="o">(</span><span class="k">_</span> <span class="k">=&gt;</span> <span class="nv">Files</span><span class="o">.</span><span class="py">size</span><span class="o">(</span><span class="n">path</span><span class="o">))</span>
<span class="o">}</span>
</code></pre></div></div>

<p>And another to create a read stream of the file using <code class="language-plaintext highlighter-rouge">ZStream.fromPath</code>, and luckily ZIO will chunk the stream to a 
specified <code class="language-plaintext highlighter-rouge">chunkSize</code>.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">def</span> <span class="nf">readFile</span><span class="o">(</span><span class="n">file</span><span class="k">:</span> <span class="kt">File</span><span class="o">)</span><span class="k">:</span> <span class="kt">UStream</span><span class="o">[</span><span class="kt">ByteString</span><span class="o">]</span> <span class="k">=</span> <span class="o">{</span>
  <span class="nv">ZStream</span><span class="o">.</span><span class="py">fromPath</span><span class="o">(</span><span class="nv">file</span><span class="o">.</span><span class="py">toPath</span><span class="o">,</span> <span class="n">chunkSize</span> <span class="k">=</span> <span class="n">chunkSize</span><span class="o">)</span>
    <span class="o">.</span><span class="py">chunks</span><span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="n">chunk</span> <span class="k">=&gt;</span> <span class="nv">ByteString</span><span class="o">.</span><span class="py">copyFrom</span><span class="o">(</span><span class="nv">chunk</span><span class="o">.</span><span class="py">toArray</span><span class="o">))</span>
    <span class="o">.</span><span class="py">catchAll</span> <span class="o">{</span> 
      <span class="n">ex</span> <span class="k">=&gt;</span> 
        <span class="nv">ZStream</span><span class="o">.</span><span class="py">fromZIO</span> <span class="o">{</span>
          <span class="nv">ZIO</span><span class="o">.</span><span class="py">logErrorCause</span><span class="o">(</span><span class="n">s</span><span class="s">"Error reading file ${file.getName}"</span><span class="o">,</span> <span class="nv">Cause</span><span class="o">.</span><span class="py">fail</span><span class="o">(</span><span class="n">ex</span><span class="o">))</span>
        <span class="o">}.</span><span class="py">drain</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The only logic left will be to convert the ZIO NIO file stream to a <code class="language-plaintext highlighter-rouge">FileChunk</code> stream. The only complexity here is
that each chunk will depend on the previous chunk.  The <code class="language-plaintext highlighter-rouge">offset</code> will simply be a running total of the body size of all
previous <code class="language-plaintext highlighter-rouge">FileChunk</code>.  ZIO <code class="language-plaintext highlighter-rouge">mapAccum</code> implements a stateful stream mapping, the state being the count of <code class="language-plaintext highlighter-rouge">sentBytes</code>.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">def</span> <span class="nf">getFile</span><span class="o">(</span><span class="n">request</span><span class="k">:</span> <span class="kt">GetFileRequest</span><span class="o">)</span><span class="k">:</span> <span class="kt">Stream</span><span class="o">[</span><span class="kt">StatusException</span>, <span class="kt">FileChunk</span><span class="o">]</span> <span class="k">=</span> <span class="o">{</span>
  <span class="k">val</span> <span class="nv">file</span> <span class="k">=</span> <span class="nf">javaFile</span><span class="o">(</span><span class="nv">request</span><span class="o">.</span><span class="py">filename</span><span class="o">)</span>
  <span class="nv">ZStream</span><span class="o">.</span><span class="py">fromZIO</span><span class="o">(</span><span class="nf">readFileSize</span><span class="o">(</span><span class="n">file</span><span class="o">))</span>
    <span class="o">.</span><span class="py">flatMap</span> <span class="o">{</span> <span class="n">fileSize</span> <span class="k">=&gt;</span>
      <span class="nf">readFile</span><span class="o">(</span><span class="n">file</span><span class="o">).</span><span class="py">mapAccum</span><span class="o">(</span><span class="mi">0L</span><span class="o">)((</span><span class="n">sentBytes</span><span class="o">,</span> <span class="n">byteString</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">val</span> <span class="nv">fileChunk</span> <span class="k">=</span> <span class="nv">FileChunk</span><span class="o">.</span><span class="py">of</span><span class="o">(</span>
          <span class="n">filename</span> <span class="k">=</span> <span class="nv">file</span><span class="o">.</span><span class="py">getName</span><span class="o">,</span>
          <span class="n">fileSize</span> <span class="k">=</span> <span class="n">fileSize</span><span class="o">,</span>
          <span class="n">offset</span> <span class="k">=</span> <span class="n">sentBytes</span><span class="o">,</span>
          <span class="n">body</span> <span class="k">=</span> <span class="n">byteString</span><span class="o">,</span>
        <span class="o">)</span>
        <span class="o">(</span><span class="n">sentBytes</span> <span class="o">+</span> <span class="nv">byteString</span><span class="o">.</span><span class="py">size</span><span class="o">,</span> <span class="n">fileChunk</span><span class="o">)</span>
      <span class="o">})</span>
    <span class="o">}</span>
    <span class="o">.</span><span class="py">catchAll</span> <span class="o">{</span> <span class="n">ex</span> <span class="k">=&gt;</span>
      <span class="nv">ZStream</span><span class="o">.</span><span class="py">fromZIO</span><span class="o">(</span><span class="nv">ZIO</span><span class="o">.</span><span class="py">fail</span><span class="o">(</span><span class="nc">StatusException</span><span class="o">(</span><span class="nv">io</span><span class="o">.</span><span class="py">grpc</span><span class="o">.</span><span class="py">Status</span><span class="o">.</span><span class="py">fromThrowable</span><span class="o">(</span><span class="n">ex</span><span class="o">))))</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>There are many types of IO related errors which can happen accessing local files, our implementation will opt to return 
the default GRPC error status on all failures.</p>

<h2 id="client-setfile-upload">Client SetFile (Upload)</h2>

<p>As mentioned at the head of the article, GRPC allows metadata content to be part of call headers. Storing upload 
parameters as headers would simplify a streaming approach by removing the requirement to inspect the head element of 
the stream for parameters such as the <code class="language-plaintext highlighter-rouge">filename</code> and <code class="language-plaintext highlighter-rouge">file_size</code>.</p>

<h3 id="alternative-grpc-metadata-headers-approach">Alternative GRPC Metadata Headers Approach</h3>

<p>Standard GRPC practices are to use the call metadata to store call agnostic data, such as authentication, tracing, and 
other information which will apply to all calls. The <code class="language-plaintext highlighter-rouge">.proto</code> file specification doesn’t include the ability to define
call message headers, so associating headers with calls will make <code class="language-plaintext highlighter-rouge">.proto</code> files an incomplete documentation of the
call.</p>

<p>Moreover, GRPC services are generated code creating an inflexibility to specifying individual call signatures. Each 
signature within a service will contain the same additional parameter. So a modified <code class="language-plaintext highlighter-rouge">setFile</code> will need to choose 
between having a generically typed header field or residing in a separate service class.</p>

<h3 id="zio-approach">ZIO Approach</h3>

<p>The <code class="language-plaintext highlighter-rouge">setFile</code> request has the Scala signature:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">setFile</span><span class="o">(</span><span class="n">request</span><span class="k">:</span> <span class="kt">Stream</span><span class="o">[</span><span class="kt">StatusException</span>, <span class="kt">FileChunk</span><span class="o">])</span><span class="k">:</span> <span class="kt">IO</span><span class="o">[</span><span class="kt">StatusException</span>, <span class="kt">SetFileResponse</span><span class="o">]</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">SetFileResponse</code> response returns a <code class="language-plaintext highlighter-rouge">filename</code>, useful in the case where the input <code class="language-plaintext highlighter-rouge">filename</code> has been modified, 
such as stripping out illegal characters, adding a version identifier or translating to a UUID or URI. Our 
implementation will mirror the input.</p>

<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">message</span> <span class="nc">SetFileResponse</span> <span class="p">{</span>
  <span class="kt">string</span> <span class="na">filename</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The implementation will use a helper class, as the stream is stateful.  The head <code class="language-plaintext highlighter-rouge">FileChunk</code> of the stream will create
a new state by opening an <code class="language-plaintext highlighter-rouge">AsynchronousFileChannel</code> on the server that subsequent stream elements will append to. The 
<code class="language-plaintext highlighter-rouge">SaveFileAccum</code> state will also continue to update its <code class="language-plaintext highlighter-rouge">offset</code> field, which while unnecessary to function it will 
continue to be verified against the <code class="language-plaintext highlighter-rouge">offset</code> of the incoming stream elements.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="k">class</span> <span class="nc">SaveFileAccum</span><span class="o">(</span>
  <span class="n">asynchronousFileChannel</span><span class="k">:</span> <span class="kt">AsynchronousFileChannel</span><span class="o">,</span>
  <span class="n">file</span><span class="k">:</span> <span class="kt">File</span><span class="o">,</span>
  <span class="n">totalSize</span><span class="k">:</span> <span class="kt">Long</span><span class="o">,</span>
  <span class="n">offset</span><span class="k">:</span> <span class="kt">Long</span><span class="o">,</span>
<span class="o">)</span>
</code></pre></div></div>

<p>The code for the <code class="language-plaintext highlighter-rouge">setFile</code> function will be center around creating a <a href="https://zio.dev/reference/stream/zsink/">ZSink</a>, 
a ZIO Stream class that processes a stream and returns a final output value.  Our sink will have the signature:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ZSink</span><span class="o">[</span><span class="kt">Scope</span>, <span class="kt">StatusException</span> <span class="kt">|</span> <span class="kt">IOException</span>, <span class="kt">FileChunk</span>, <span class="kt">Nothing</span>, <span class="kt">Option</span><span class="o">[</span><span class="kt">SaveFileAccum</span><span class="o">]]</span>
</code></pre></div></div>

<p>The type parameters can be a bit intimidating at first, but are straight forward. Our <code class="language-plaintext highlighter-rouge">ZSink</code> needs a <code class="language-plaintext highlighter-rouge">Scope</code> to run 
in because it contains an open <code class="language-plaintext highlighter-rouge">AsynchronousFileChannel</code> which will need to be closed. It will throw both 
<code class="language-plaintext highlighter-rouge">StatusException</code> and <code class="language-plaintext highlighter-rouge">IOException</code>, but we could reduce this to just GRPC <code class="language-plaintext highlighter-rouge">StatusException</code> if made our sink a little
more complicated by handling all IO errors internally. It processes a stream of <code class="language-plaintext highlighter-rouge">FileChunk</code> items and will process all 
of them so it will have <code class="language-plaintext highlighter-rouge">Nothing</code> remaining elements, and its output will be an <code class="language-plaintext highlighter-rouge">Option[SaveFileAccum]</code>.</p>

<h4 id="zsink-output">ZSink Output</h4>

<p>The choice to return an <code class="language-plaintext highlighter-rouge">Option[SaveFileAccum]</code> allows us to externalize more code from the sink than if it returned
a <code class="language-plaintext highlighter-rouge">SetFileResponse</code> directly.</p>

<p>After the sink runs, the value of <code class="language-plaintext highlighter-rouge">Option[SaveFileAccum]</code> is either:</p>
<ul>
  <li>If <code class="language-plaintext highlighter-rouge">Some</code> then a file was created. Either:
    <ul>
      <li>The client’s <code class="language-plaintext highlighter-rouge">filesize</code> matches our file size means the upload was successful, or</li>
      <li>The file size doesn’t match, meaning the upload was incomplete.</li>
    </ul>
  </li>
  <li>If <code class="language-plaintext highlighter-rouge">None</code>, then no file was created.</li>
</ul>

<p>The ZIO code for this is:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">ZIO</span><span class="o">.</span><span class="py">scoped</span> <span class="o">{</span>
  <span class="nv">request</span><span class="o">.</span><span class="py">run</span><span class="o">(</span><span class="n">sink</span><span class="o">).</span><span class="py">flatMap</span> <span class="o">{</span>
    <span class="k">case</span> <span class="nc">Some</span><span class="o">(</span><span class="nc">SaveFileAccum</span><span class="o">(</span><span class="k">_</span><span class="o">,</span> <span class="n">file</span><span class="o">,</span> <span class="n">expectedSize</span><span class="o">,</span> <span class="n">actualSize</span><span class="o">))</span> <span class="k">if</span> <span class="n">expectedSize</span> <span class="o">==</span> <span class="n">actualSize</span> <span class="k">=&gt;</span>
      <span class="k">val</span> <span class="nv">response</span> <span class="k">=</span> <span class="nv">SetFileResponse</span><span class="o">.</span><span class="py">of</span><span class="o">(</span><span class="nv">file</span><span class="o">.</span><span class="py">getName</span><span class="o">)</span>
      <span class="nv">ZIO</span><span class="o">.</span><span class="py">succeed</span><span class="o">(</span><span class="n">response</span><span class="o">)</span>

    <span class="k">case</span> <span class="nc">Some</span><span class="o">(</span><span class="nc">SaveFileAccum</span><span class="o">(</span><span class="k">_</span><span class="o">,</span> <span class="n">file</span><span class="o">,</span> <span class="n">expectedSize</span><span class="o">,</span> <span class="n">actualSize</span><span class="o">))</span> <span class="k">=&gt;</span>
      <span class="nv">ZIO</span><span class="o">.</span><span class="py">logError</span><span class="o">(</span><span class="n">s</span><span class="s">"Upload ended at $actualSize of $expectedSize"</span><span class="o">)</span> <span class="o">*&gt;</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">fail</span><span class="o">(</span><span class="nc">StatusException</span><span class="o">(</span><span class="nc">CANCELLED</span><span class="o">))</span>

    <span class="k">case</span> <span class="k">_</span> <span class="k">=&gt;</span>
      <span class="nv">ZIO</span><span class="o">.</span><span class="py">logError</span><span class="o">(</span><span class="s">"Could not create file"</span><span class="o">)</span> <span class="o">*&gt;</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">fail</span><span class="o">(</span><span class="nc">StatusException</span><span class="o">(</span><span class="nc">UNKNOWN</span><span class="o">))</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="zsink-processing">ZSink Processing</h4>

<p>The processing done by the sink to the stream of <code class="language-plaintext highlighter-rouge">FileChunk</code> has a few responsibilities. First, it will need to inspect
the first element of the stream to look for the upload parameters. The upload parameters will include the <code class="language-plaintext highlighter-rouge">filename</code> 
which will be used to create the <code class="language-plaintext highlighter-rouge">AsynchronousFileChannel</code>, and an expected <code class="language-plaintext highlighter-rouge">filesize</code>, which will be used to verify a
successfully completed stream. (Additionally we could include an MD5 or other hash to verify content as well as 
file size.)</p>

<p>Our code will include a verification that the expected upload file size is below a maximum, as we don’t have unlimited
storage resources. Moreover, the processing of each <code class="language-plaintext highlighter-rouge">FileChunk</code> will continue to verify that the running total of bytes 
uploaded is below our server maximum and equal to the expected file size.</p>

<p>The sink will process the stream using a <code class="language-plaintext highlighter-rouge">foldLeftZIO</code> since we will need to maintain a state (<code class="language-plaintext highlighter-rouge">Option[SaveFileAccum]</code>)
updating it during each chunk processing.</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">ZSink</span><span class="o">.</span><span class="py">foldLeftZIO</span><span class="o">(</span><span class="nc">None</span><span class="o">)((</span><span class="n">saveFileAccum</span><span class="o">,</span> <span class="n">fileChunk</span><span class="o">)</span> <span class="o">{</span>
  <span class="c1">//case 1: invalid chunk size, throw INVALID_ARGUMENT</span>
  <span class="c1">//case 2: file channel not open =&gt; open file channel and write first chunk</span>
  <span class="c1">//case 3: file channel open, but chunk offset != expected throw INVALID_ARGUMENT</span>
  <span class="c1">//case 4: file channel open, but chunk greater than remaining bytes</span>
  <span class="c1">//case 5: channel open, chunk valid =&gt; append to file channel</span>
<span class="o">})</span>
</code></pre></div></div>

<h5 id="case-1-invalid-chunk-size">Case 1: Invalid Chunk Size</h5>

<p>Separate from <code class="language-plaintext highlighter-rouge">maxFileSize</code> it is also important to limit how large individual chunks can be. Noted earlier, gRPC will 
perform multiple copies of the body data so allowing too large of chunks will result in more memory churn and GC 
pressure. For example, allowing 100MB chunk sizes may require &gt; 300MB of memory to properly process the request.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="k">_</span>  <span class="k">if</span> <span class="nv">fileChunk</span><span class="o">.</span><span class="py">body</span><span class="o">.</span><span class="py">size</span><span class="o">()</span> <span class="o">&gt;</span> <span class="n">maxChunkSize</span> <span class="k">=&gt;</span>
  <span class="nv">ZIO</span><span class="o">.</span><span class="py">logError</span><span class="o">(</span><span class="n">s</span><span class="s">"Chunk size ${fileChunk.body.size()} exceeds maximum $maxChunkSize"</span><span class="o">)</span> 
    <span class="o">*&gt;</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">fail</span><span class="o">(</span><span class="nc">StatusException</span><span class="o">(</span><span class="nc">INVALID_ARGUMENT</span><span class="o">))</span>
</code></pre></div></div>

<h5 id="case-2-file-channel-not-open">Case 2: File Channel Not Open</h5>

<p>This should only run on the first chunk. It will either create an open <code class="language-plaintext highlighter-rouge">AsynchronousFileChannel</code> or determine that the
file upload is invalid and throw an exception.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">file</span> <span class="k">=</span> <span class="nf">javaFile</span><span class="o">(</span><span class="nv">fileChunk</span><span class="o">.</span><span class="py">filename</span><span class="o">)</span>
<span class="k">val</span> <span class="nv">path</span> <span class="k">=</span> <span class="nv">Path</span><span class="o">.</span><span class="py">fromJava</span><span class="o">(</span><span class="nv">file</span><span class="o">.</span><span class="py">toPath</span><span class="o">)</span>
<span class="k">val</span> <span class="nv">chunk</span> <span class="k">=</span> <span class="nv">Chunk</span><span class="o">.</span><span class="py">from</span><span class="o">(</span><span class="nv">fileChunk</span><span class="o">.</span><span class="py">body</span><span class="o">.</span><span class="py">asScala</span><span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="nv">Byte</span><span class="o">.</span><span class="py">unbox</span><span class="o">))</span>
<span class="nf">if</span> <span class="o">(</span><span class="nv">fileChunk</span><span class="o">.</span><span class="py">fileSize</span> <span class="o">&gt;</span> <span class="n">maxFileSize</span><span class="o">)</span> <span class="o">{</span>
  <span class="nv">ZIO</span><span class="o">.</span><span class="py">logError</span><span class="o">(</span><span class="n">s</span><span class="s">"File too large, attempted ${fileChunk.fileSize} bytes"</span><span class="o">)</span>
    <span class="o">*&gt;</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">fail</span><span class="o">(</span><span class="nc">StatusException</span><span class="o">(</span><span class="nc">OUT_OF_RANGE</span><span class="o">))</span>
<span class="o">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="nv">fileChunk</span><span class="o">.</span><span class="py">fileSize</span> <span class="o">&lt;</span> <span class="nv">chunk</span><span class="o">.</span><span class="py">length</span><span class="o">)</span> <span class="o">{</span>
  <span class="nv">ZIO</span><span class="o">.</span><span class="py">logError</span><span class="o">(</span><span class="n">s</span><span class="s">"Invalid chunk ${chunk.length} exceeds total size ${fileChunk.fileSize}"</span><span class="o">)</span>
    <span class="o">*&gt;</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">fail</span><span class="o">(</span><span class="nc">StatusException</span><span class="o">(</span><span class="nc">INVALID_ARGUMENT</span><span class="o">))</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
  <span class="k">for</span> <span class="o">{</span>
    <span class="k">_</span> <span class="k">&lt;-</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">log</span><span class="o">(</span><span class="n">s</span><span class="s">"Uploading file ${path.toString}, size ${fileChunk.fileSize}"</span><span class="o">)</span>
    <span class="n">channel</span> <span class="k">&lt;-</span> <span class="nv">AsynchronousFileChannel</span><span class="o">.</span><span class="py">open</span><span class="o">(</span>
        <span class="n">path</span><span class="o">,</span>
        <span class="nv">StandardOpenOption</span><span class="o">.</span><span class="py">WRITE</span><span class="o">,</span>
        <span class="nv">StandardOpenOption</span><span class="o">.</span><span class="py">TRUNCATE_EXISTING</span><span class="o">,</span>
        <span class="nv">StandardOpenOption</span><span class="o">.</span><span class="py">CREATE</span>
      <span class="o">)</span> 
    <span class="k">_</span> <span class="k">&lt;-</span> <span class="nv">channel</span><span class="o">.</span><span class="py">writeChunk</span><span class="o">(</span><span class="n">chunk</span><span class="o">,</span> <span class="mi">0</span><span class="o">)</span>
  <span class="o">}</span> <span class="k">yield</span> <span class="o">{</span>
    <span class="nc">Some</span><span class="o">(</span><span class="nc">SaveFileAccum</span><span class="o">(</span><span class="n">channel</span><span class="o">,</span> <span class="n">file</span><span class="o">,</span> <span class="nv">fileChunk</span><span class="o">.</span><span class="py">fileSize</span><span class="o">,</span> <span class="nv">chunk</span><span class="o">.</span><span class="py">length</span><span class="o">))</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h5 id="case-3-file-channel-open-but-invalid-offset">Case 3: File Channel Open But Invalid Offset</h5>

<p>Verifies that this <code class="language-plaintext highlighter-rouge">FileChunk</code> is at the expected offset.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="nc">Some</span><span class="o">(</span><span class="nc">SaveFileAccum</span><span class="o">(</span><span class="k">_</span><span class="o">,</span> <span class="k">_</span><span class="o">,</span> <span class="k">_</span><span class="o">,</span> <span class="n">offset</span><span class="o">))</span> <span class="k">if</span> <span class="nv">fileChunk</span><span class="o">.</span><span class="py">offset</span> <span class="o">!=</span> <span class="n">offset</span> <span class="k">=&gt;</span>
  <span class="nv">ZIO</span><span class="o">.</span><span class="py">logError</span><span class="o">(</span><span class="n">s</span><span class="s">"Invalid chunk offset ${fileChunk.offset}, expected $offset"</span><span class="o">)</span>
    <span class="o">*&gt;</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">fail</span><span class="o">(</span><span class="nc">StatusException</span><span class="o">(</span><span class="nc">INVALID_ARGUMENT</span><span class="o">))</span>
</code></pre></div></div>

<h5 id="case-4-file-channel-open-but-chunk-greater-than-remaining-bytes">Case 4: File Channel Open, But Chunk Greater Than Remaining Bytes</h5>

<p>Verifies that this <code class="language-plaintext highlighter-rouge">FileChunk</code> body doesn’t exceed the remaining number of bytes left in the stored file.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="nc">Some</span><span class="o">(</span><span class="nc">SaveFileAccum</span><span class="o">(</span><span class="k">_</span><span class="o">,</span> <span class="k">_</span><span class="o">,</span> <span class="n">totalSize</span><span class="o">,</span> <span class="n">offset</span><span class="o">))</span> <span class="k">if</span> <span class="nv">fileChunk</span><span class="o">.</span><span class="py">body</span><span class="o">.</span><span class="py">size</span><span class="o">()</span> <span class="o">&gt;</span> <span class="n">totalSize</span> <span class="o">-</span> <span class="n">offset</span> <span class="k">=&gt;</span>
  <span class="nv">ZIO</span><span class="o">.</span><span class="py">logError</span><span class="o">(</span><span class="n">s</span><span class="s">"Invalid chunk ${fileChunk.offset} exceeds total size $totalSize"</span><span class="o">)</span>
    <span class="o">*&gt;</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">fail</span><span class="o">(</span><span class="nc">StatusException</span><span class="o">(</span><span class="nc">OUT_OF_RANGE</span><span class="o">))</span>
</code></pre></div></div>

<h5 id="case-5-file-channel-open-write-to-channel">Case 5: File Channel Open, Write to Channel</h5>

<p>All invalid <code class="language-plaintext highlighter-rouge">FileChunk</code> cases have been checked, the <code class="language-plaintext highlighter-rouge">body</code> should be appended to the open file channel and the sink
state updated with the new expected offset for the next element of the stream.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">chunk</span> <span class="k">=</span> <span class="nv">Chunk</span><span class="o">.</span><span class="py">from</span><span class="o">(</span><span class="nv">fileChunk</span><span class="o">.</span><span class="py">body</span><span class="o">.</span><span class="py">asScala</span><span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="nv">Byte</span><span class="o">.</span><span class="py">unbox</span><span class="o">))</span>
<span class="k">val</span> <span class="nv">saveFileAccum</span> <span class="k">=</span> <span class="nc">SaveFileAccum</span><span class="o">(</span>
  <span class="n">asynchronousFileChannel</span> <span class="k">=</span> <span class="n">asyncFileChannel</span><span class="o">,</span>
  <span class="n">file</span> <span class="k">=</span> <span class="n">file</span><span class="o">,</span>
  <span class="n">totalSize</span> <span class="k">=</span> <span class="n">totalSize</span><span class="o">,</span>
  <span class="n">offset</span> <span class="k">=</span> <span class="n">offset</span> <span class="o">+</span> <span class="nv">chunk</span><span class="o">.</span><span class="py">length</span><span class="o">,</span>
<span class="o">)</span>
<span class="nv">asyncFileChannel</span><span class="o">.</span><span class="py">writeChunk</span><span class="o">(</span><span class="n">chunk</span><span class="o">,</span> <span class="n">offset</span><span class="o">).</span><span class="py">as</span><span class="o">(</span><span class="nc">Some</span><span class="o">(</span><span class="n">saveFileAccum</span><span class="o">))</span>
</code></pre></div></div>

<h1 id="conclusion">Conclusion</h1>

<p>The next post in this series will create a Flutter client for this server component.</p>]]></content><author><name>Steven Skelton</name></author><category term="Scala" /><category term="ZIO" /><category term="gRPC" /><summary type="html"><![CDATA[gRPC with Protobuf is a framework to efficiently simplify the client-server networking requirements of modern applications. One use-case where the low-level simplicity of pure HTTP maintains an advantage over gRPC is handling file transfers: the uploading and downloading of contiguous binary block data. But gRPC can efficiently replicate all HTTP functionality within its Protobuf message framework making it unnecessary to host separate gRPC and HTTP servers for applications.]]></summary></entry><entry><title type="html">Realtime Client Database: Flutter client using Bloc and gRPC</title><link href="https://www.stevenskelton.ca/flutter-realtime-client-database-bloc/" rel="alternate" type="text/html" title="Realtime Client Database: Flutter client using Bloc and gRPC" /><published>2024-06-05T00:00:00-04:00</published><updated>2024-06-05T00:00:00-04:00</updated><id>https://www.stevenskelton.ca/flutter-realtime-client-database-bloc</id><content type="html" xml:base="https://www.stevenskelton.ca/flutter-realtime-client-database-bloc/"><![CDATA[<p>//TODO
<!--more--></p>

<div class="html-bg">
  <strong>Other Posts in this Series</strong>
  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/realtime-client-database-grpc-streams-zio/" rel="permalink">Realtime Client Database: gRPC Bi-Directional Streams and ZIO Hub
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-04-15T00:00:00-04:00">April 15, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          6 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/realtime-client-database-external-datasource-zlayer/" rel="permalink">Realtime Client Database: External Datasource using ZIO ZLayer
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-05-01T00:00:00-04:00">May 1, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          2 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/realtime-client-database-performance-testing/" rel="permalink">Realtime Client Database: Performance Testing in the Cloud
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-05-06T00:00:00-04:00">May 6, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          1 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  
</div>

<h1 id="pubspecyaml-dependencies">pubspec.yaml Dependencies</h1>

<p>This sample client requires 2 Flutter libraries:</p>
<ul>
  <li><a href="https://pub.dev/packages/grpc">grpc</a> core implementation for networking.</li>
  <li><a href="https://pub.dev/packages/flutter_bloc">flutter_bloc</a> …</li>
  <li><a href="https://pub.dev/packages/built_collection">built_collection</a> …</li>
  <li><a href="https://pub.dev/packages/path_provider">path_provider</a> …</li>
</ul>]]></content><author><name>Steven Skelton</name></author><category term="Dart" /><category term="Bloc" /><category term="gRPC" /><summary type="html"><![CDATA[//TODO]]></summary></entry><entry><title type="html">Realtime Client Database: Performance Testing in the Cloud</title><link href="https://www.stevenskelton.ca/realtime-client-database-performance-testing/" rel="alternate" type="text/html" title="Realtime Client Database: Performance Testing in the Cloud" /><published>2024-05-06T00:00:00-04:00</published><updated>2024-05-06T00:00:00-04:00</updated><id>https://www.stevenskelton.ca/realtime-client-database-performance-testing</id><content type="html" xml:base="https://www.stevenskelton.ca/realtime-client-database-performance-testing/"><![CDATA[<p>//TODO
<!--more--></p>

<div class="html-bg">
  <strong>Other Posts in this Series</strong>
  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/realtime-client-database-grpc-streams-zio/" rel="permalink">Realtime Client Database: gRPC Bi-Directional Streams and ZIO Hub
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-04-15T00:00:00-04:00">April 15, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          6 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/realtime-client-database-external-datasource-zlayer/" rel="permalink">Realtime Client Database: External Datasource using ZIO ZLayer
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-05-01T00:00:00-04:00">May 1, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          2 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/flutter-realtime-client-database-bloc/" rel="permalink">Realtime Client Database: Flutter client using Bloc and gRPC
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-06-05T00:00:00-04:00">June 5, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          less than 1 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  
</div>

<p>The testing client for a performance test can be the same as one created for local unit tests.</p>

<h2 id="performance-testing-implementation">Performance Testing Implementation</h2>

<p>A sample implementation for performance testing would timestamp on <code class="language-plaintext highlighter-rouge">Data</code> element updates allowing clients to compare
time lag between server updates and their notification of it.</p>

<p>Performance testing implementation and results are covered in <a href="/realtime-client-database-performance-testing/">Realtime Client Database Performance Testing</a>.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ClockExternalDataLayer</span> <span class="nf">private</span><span class="o">(</span><span class="n">clock</span><span class="k">:</span> <span class="kt">Clock</span><span class="o">,</span> <span class="n">refreshSchedule</span><span class="k">:</span> <span class="kt">Schedule</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Any</span>, <span class="kt">Any</span><span class="o">])</span>
  <span class="k">extends</span> <span class="nc">ExternalDataLayer</span><span class="o">(</span><span class="n">refreshSchedule</span><span class="o">)</span> <span class="o">{</span>

  <span class="k">override</span> <span class="k">protected</span> <span class="k">def</span> <span class="nf">externalData</span><span class="o">(</span>
                                       <span class="n">chunk</span><span class="k">:</span> <span class="kt">NonEmptyChunk</span><span class="o">[</span><span class="kt">Either</span><span class="o">[</span><span class="kt">DataId</span>, <span class="kt">DataRecord</span><span class="o">]]</span>
                                     <span class="o">)</span><span class="k">:</span> <span class="kt">UIO</span><span class="o">[</span><span class="kt">Chunk</span><span class="o">[</span><span class="kt">Data</span><span class="o">]]</span> <span class="k">=</span> <span class="o">{</span>
    <span class="nv">clock</span><span class="o">.</span><span class="py">instant</span><span class="o">.</span><span class="py">map</span> <span class="o">{</span>
      <span class="n">now</span> <span class="k">=&gt;</span>
        <span class="k">val</span> <span class="nv">dataId</span> <span class="k">=</span> <span class="nv">either</span><span class="o">.</span><span class="py">fold</span><span class="o">(</span><span class="n">identity</span><span class="o">,</span> <span class="nv">_</span><span class="o">.</span><span class="py">data</span><span class="o">.</span><span class="py">id</span><span class="o">)</span>
        <span class="nv">chunk</span><span class="o">.</span><span class="py">map</span><span class="o">(</span><span class="n">either</span> <span class="k">=&gt;</span> <span class="nv">Data</span><span class="o">.</span><span class="py">of</span><span class="o">(</span><span class="n">dataId</span><span class="o">,</span> <span class="nv">now</span><span class="o">.</span><span class="py">toString</span><span class="o">))</span>
    <span class="o">}</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="k">class</span> <span class="nc">GrpcClient</span><span class="o">(</span>
                       <span class="n">userId</span><span class="k">:</span> <span class="kt">UserId</span><span class="o">,</span>
                       <span class="n">requests</span><span class="k">:</span> <span class="kt">Queue</span><span class="o">[</span><span class="kt">SyncRequest</span><span class="o">],</span>
                       <span class="n">responses</span><span class="k">:</span> <span class="kt">Stream</span><span class="o">[</span><span class="kt">Throwable</span>, <span class="o">(</span><span class="kt">UserId</span>, <span class="kt">SyncResponse</span><span class="o">)],</span>
                       <span class="n">grpcLayer</span><span class="k">:</span> <span class="kt">ZLayer</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Throwable</span>, <span class="kt">SyncServiceClient</span><span class="o">],</span>
                     <span class="o">)</span> <span class="o">{</span>

  <span class="k">def</span> <span class="nf">update</span><span class="o">(</span><span class="n">request</span><span class="k">:</span> <span class="kt">UpdateRequest</span><span class="o">)</span><span class="k">:</span> <span class="kt">IO</span><span class="o">[</span><span class="kt">Throwable</span>, <span class="kt">UpdateResponse</span><span class="o">]</span> <span class="k">=</span> <span class="o">{</span>
    <span class="nv">SyncServiceClient</span><span class="o">.</span><span class="py">update</span><span class="o">(</span><span class="n">request</span><span class="o">).</span><span class="py">provideLayer</span><span class="o">(</span><span class="n">grpcLayer</span><span class="o">)</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">object</span> <span class="nc">GrpcClient</span> <span class="o">{</span>
  <span class="k">def</span> <span class="nf">launch</span><span class="o">(</span>
              <span class="n">userId</span><span class="k">:</span> <span class="kt">UserId</span><span class="o">,</span>
              <span class="n">serverAddress</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span>
              <span class="n">serverPort</span><span class="k">:</span> <span class="kt">Int</span><span class="o">,</span>
            <span class="o">)</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Scope</span>, <span class="kt">Nothing</span>, <span class="kt">GrpcClient</span><span class="o">]</span> <span class="k">=</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">scoped</span><span class="o">(</span><span class="nv">Queue</span><span class="o">.</span><span class="py">unbounded</span><span class="o">[</span><span class="kt">SyncRequest</span><span class="o">]).</span><span class="py">map</span> <span class="o">{</span>

    <span class="n">requests</span> <span class="k">=&gt;</span>

      <span class="k">val</span> <span class="nv">grpcLayer</span><span class="k">:</span> <span class="kt">ZLayer</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Throwable</span>, <span class="kt">SyncServiceClient</span><span class="o">]</span> <span class="k">=</span> <span class="nv">SyncServiceClient</span><span class="o">.</span><span class="py">live</span><span class="o">(</span>
        <span class="nc">ZManagedChannel</span><span class="o">(</span><span class="nv">ManagedChannelBuilder</span><span class="o">.</span><span class="py">forAddress</span><span class="o">(</span><span class="n">serverAddress</span><span class="o">,</span> <span class="n">serverPort</span><span class="o">).</span><span class="py">usePlaintext</span><span class="o">()),</span>
        <span class="n">options</span> <span class="k">=</span> <span class="nv">CallOptions</span><span class="o">.</span><span class="py">DEFAULT</span><span class="o">,</span>
        <span class="n">metadata</span> <span class="k">=</span> <span class="nv">SafeMetadata</span><span class="o">.</span><span class="py">make</span><span class="o">((</span><span class="nv">AuthenticatedUser</span><span class="o">.</span><span class="py">MetadataUserIdKey</span><span class="o">,</span> <span class="nv">userId</span><span class="o">.</span><span class="py">toString</span><span class="o">)),</span>
      <span class="o">)</span>

      <span class="k">val</span> <span class="nv">responses</span> <span class="k">=</span> <span class="nc">SyncServiceClient</span>
        <span class="o">.</span><span class="py">bidirectionalStream</span><span class="o">(</span><span class="nv">ZStream</span><span class="o">.</span><span class="py">fromQueue</span><span class="o">(</span><span class="n">requests</span><span class="o">))</span>
        <span class="o">.</span><span class="py">provideLayer</span><span class="o">(</span><span class="n">grpcLayer</span><span class="o">)</span>
        <span class="o">.</span><span class="py">map</span><span class="o">((</span><span class="n">userId</span><span class="o">,</span> <span class="k">_</span><span class="o">))</span>

      <span class="nc">GrpcClient</span><span class="o">(</span><span class="n">userId</span><span class="o">,</span> <span class="n">requests</span><span class="o">,</span> <span class="n">responses</span><span class="o">,</span> <span class="n">grpcLayer</span><span class="o">)</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Data ids: 
1 - 600 are second markers.
601 - 1000 are random</p>

<p>Clients:
Subscribe 1 - 600, subscribe i mod clientid</p>]]></content><author><name>Steven Skelton</name></author><category term="Scala" /><category term="ZIO" /><category term="Non-Blocking/Concurrency" /><category term="gRPC" /><summary type="html"><![CDATA[//TODO]]></summary></entry><entry><title type="html">Realtime Client Database: External Datasource using ZIO ZLayer</title><link href="https://www.stevenskelton.ca/realtime-client-database-external-datasource-zlayer/" rel="alternate" type="text/html" title="Realtime Client Database: External Datasource using ZIO ZLayer" /><published>2024-05-01T00:00:00-04:00</published><updated>2024-05-01T00:00:00-04:00</updated><id>https://www.stevenskelton.ca/realtime-client-database-external-datasource-zlayer</id><content type="html" xml:base="https://www.stevenskelton.ca/realtime-client-database-external-datasource-zlayer/"><![CDATA[<p>Expanding on the realtime Firebase implementation in the previous article, this expands the functionality allowing the
server to fetch data on-demand from an external datasource. Additionally, functionality to periodical refresh active
data which is subscribed to by connected clients transforms this database into an efficient cache to evolving external
data which can only be obtained by polling.<!--more--></p>

<div class="html-bg">
  <strong>Other Posts in this Series</strong>
  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/realtime-client-database-grpc-streams-zio/" rel="permalink">Realtime Client Database: gRPC Bi-Directional Streams and ZIO Hub
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-04-15T00:00:00-04:00">April 15, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          6 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/realtime-client-database-performance-testing/" rel="permalink">Realtime Client Database: Performance Testing in the Cloud
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-05-06T00:00:00-04:00">May 6, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          1 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/flutter-realtime-client-database-bloc/" rel="permalink">Realtime Client Database: Flutter client using Bloc and gRPC
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-06-05T00:00:00-04:00">June 5, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          less than 1 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  
</div>

<style>
    .multiplex_responsive_1 { width: 360px; height: 100px; margin-top: 1em; margin-bottom: 1em; }
    @media(min-width: 720px) { .multiplex_responsive_1 { width: 360px; margin: 0; height: 100px; } }
</style>

<div class="toc-container">
    <aside class="sidebar__right">
<nav class="toc">
      <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> Table of Contents</h4></header>
<ul class="toc__menu" id="markdown-toc">
  <li><a href="#external-data-using-a-zlayer" id="markdown-toc-external-data-using-a-zlayer">External Data using a ZLayer</a>    <ul>
      <li><a href="#external-data-sample-implementations" id="markdown-toc-external-data-sample-implementations">External Data Sample Implementations</a>        <ul>
          <li><a href="#hardcoded-data" id="markdown-toc-hardcoded-data">Hardcoded Data</a></li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

    </nav>
</aside>
    <div class="toc-ad"><ins class="adsbygoogle multiplex_responsive_1" style="display:block" data-ad-client="ca-pub-5000348308466945" data-ad-slot="5418078712"></ins>
        <script>
            (adsbygoogle = window.adsbygoogle || []).push({});
        </script></div>
</div>

<h1 id="external-data-using-a-zlayer">External Data using a ZLayer</h1>

<p>The ZLayer mechanism in ZIO conveniently defines environment dependencies, and will be used to create an external data
service implementation. The implementation details will depend on how and where the external data is stored, but it
will be exposed with a simple interface mapping data ids to new data elements.</p>

<p>The ZLayer <em>trait</em> will contain the related methods used to transform inputs and outputs, as well as scheduling
updates. With <em>protected</em> visibility implementations can be overriden accomidating non-standard operation.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Data layer that will refresh all subscribed data an external datasource based on a schedule.
 *
 * @param refreshSchedule
 */</span>
<span class="k">abstract</span> <span class="k">class</span> <span class="nc">ExternalDataLayer</span><span class="o">(</span><span class="n">refreshSchedule</span><span class="k">:</span> <span class="kt">Schedule</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Any</span>, <span class="kt">Any</span><span class="o">])</span> <span class="o">{</span>

  <span class="cm">/**
   * Implementation details for resolving elements in queue to external `Data`.
   *
   * @param chunk Will contain the `DataRecord` for records existing in `databaseRecordsRef`
   * @return `Data` from external source when available.
   */</span>
  <span class="k">protected</span> <span class="k">def</span> <span class="nf">externalData</span><span class="o">(</span><span class="n">chunk</span><span class="k">:</span> <span class="kt">NonEmptyChunk</span><span class="o">[</span><span class="kt">Either</span><span class="o">[</span><span class="kt">DataId</span>, <span class="kt">DataRecord</span><span class="o">]])</span><span class="k">:</span> <span class="kt">UIO</span><span class="o">[</span><span class="kt">Chunk</span><span class="o">[</span><span class="kt">Data</span><span class="o">]]</span>

  <span class="cm">/**
   * All DataId from this queue are fetched from external datasource.
   * Updates are commited to `databaseRecordsRef` and emitted to `journal`.
   * Creation of queue will start the automatic data refresh using `refreshSchedule`.
   * Closing queue will stop the scheduled refresh.
   * Calling multiple times will execute schedules in parallel, 
   * useful for manual refresh using `Schedule.once`.
   */</span>
  <span class="k">def</span> <span class="nf">createFetchQueue</span><span class="o">(</span>
                        <span class="n">journal</span><span class="k">:</span> <span class="kt">Hub</span><span class="o">[</span><span class="kt">DataRecord</span><span class="o">],</span>
                        <span class="n">databaseRecordsRef</span><span class="k">:</span> <span class="kt">Ref</span><span class="o">[</span><span class="kt">Map</span><span class="o">[</span><span class="kt">DataId</span>, <span class="kt">DataRecord</span><span class="o">]],</span>
                        <span class="n">globalSubscribersRef</span><span class="k">:</span> <span class="kt">Ref</span><span class="o">[</span><span class="kt">Set</span><span class="o">[</span><span class="kt">Ref</span><span class="o">[</span><span class="kt">HashSet</span><span class="o">[</span><span class="kt">DataId</span><span class="o">]]]],</span>
                      <span class="o">)</span><span class="k">:</span> <span class="kt">UIO</span><span class="o">[</span><span class="kt">Enqueue</span><span class="o">[</span><span class="kt">DataId</span><span class="o">]]</span>

  <span class="cm">/**
   * Whenever DataId are queued for refresh, call `externalData` to get data,
   * Update `databaseRecordsRef` when new data available without conflicts,
   * Emit data updates to `journal`.
   */</span>
  <span class="k">protected</span> <span class="k">def</span> <span class="nf">attachFetchQueueListener</span><span class="o">(</span>
                                          <span class="n">queue</span><span class="k">:</span> <span class="kt">Dequeue</span><span class="o">[</span><span class="kt">DataId</span><span class="o">],</span>
                                          <span class="n">journal</span><span class="k">:</span> <span class="kt">Hub</span><span class="o">[</span><span class="kt">DataRecord</span><span class="o">],</span>
                                          <span class="n">databaseRecordsRef</span><span class="k">:</span> <span class="kt">Ref</span><span class="o">[</span><span class="kt">Map</span><span class="o">[</span><span class="kt">DataId</span>, <span class="kt">DataRecord</span><span class="o">]]</span>
                                        <span class="o">)</span><span class="k">:</span> <span class="kt">UIO</span><span class="o">[</span><span class="kt">Unit</span><span class="o">]</span>

  <span class="cm">/**
   * On schedule, attempt to refresh all subscribed data.
   */</span>
  <span class="k">protected</span> <span class="k">def</span> <span class="nf">attachRefreshScheduler</span><span class="o">(</span>
                                        <span class="n">queue</span><span class="k">:</span> <span class="kt">Enqueue</span><span class="o">[</span><span class="kt">DataId</span><span class="o">],</span>
                                        <span class="n">globalSubscribersRef</span><span class="k">:</span> <span class="kt">Ref</span><span class="o">[</span><span class="kt">Set</span><span class="o">[</span><span class="kt">Ref</span><span class="o">[</span><span class="kt">HashSet</span><span class="o">[</span><span class="kt">DataId</span><span class="o">]]]]</span>
                                      <span class="o">)</span><span class="k">:</span> <span class="kt">UIO</span><span class="o">[</span><span class="kt">Unit</span><span class="o">]</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="external-data-sample-implementations">External Data Sample Implementations</h2>

<h3 id="hardcoded-data">Hardcoded Data</h3>

<p>A sample implementation useful for testing will allow data to be specified as a construction parameter. The data will
be part of the ZLayer instance with it consumed by calls from the server. This will allow subsequent calls to return
different data values allowing for state-based unit testing. However this requires strict ordering, so calls
to <code class="language-plaintext highlighter-rouge">externalData</code> requires single concurrency. Java threading uses <code class="language-plaintext highlighter-rouge">syncronize</code> for serialize execution, in ZIO fiber
synchronization is through <code class="language-plaintext highlighter-rouge">Ref.Synchronized</code>.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">HardcodedExternalDataLayer</span> <span class="nf">private</span><span class="o">(</span>
                                          <span class="n">hardcodedData</span><span class="k">:</span> <span class="kt">Ref.Synchronized</span><span class="o">[</span><span class="kt">Seq</span><span class="o">[</span><span class="kt">Data</span><span class="o">]],</span>
                                          <span class="n">refreshSchedule</span><span class="k">:</span> <span class="kt">Schedule</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Any</span>, <span class="kt">Any</span><span class="o">]</span>
                                        <span class="o">)</span>
  <span class="k">extends</span> <span class="nc">ExternalDataLayer</span><span class="o">(</span><span class="n">refreshSchedule</span><span class="o">)</span> <span class="o">{</span>

  <span class="k">override</span> <span class="k">protected</span> <span class="k">def</span> <span class="nf">externalData</span><span class="o">(</span>
                                       <span class="n">chunk</span><span class="k">:</span> <span class="kt">NonEmptyChunk</span><span class="o">[</span><span class="kt">Either</span><span class="o">[</span><span class="kt">DataId</span>, <span class="kt">DataRecord</span><span class="o">]]</span>
                                     <span class="o">)</span><span class="k">:</span> <span class="kt">UIO</span><span class="o">[</span><span class="kt">Chunk</span><span class="o">[</span><span class="kt">Data</span><span class="o">]]</span> <span class="k">=</span> <span class="o">{</span>
    <span class="nv">hardcodedData</span><span class="o">.</span><span class="py">modify</span> <span class="o">{</span>
      <span class="n">fetchData</span> <span class="k">=&gt;</span>
        <span class="nv">chunk</span><span class="o">.</span><span class="py">foldLeft</span><span class="o">((</span><span class="nv">Chunk</span><span class="o">.</span><span class="py">empty</span><span class="o">[</span><span class="kt">Data</span><span class="o">],</span> <span class="n">fetchData</span><span class="o">))({</span>
          <span class="nf">case</span> <span class="o">((</span><span class="n">foldData</span><span class="o">,</span> <span class="n">foldFetchData</span><span class="o">),</span> <span class="n">either</span><span class="o">)</span> <span class="k">=&gt;</span>
            <span class="k">val</span> <span class="nv">dataId</span> <span class="k">=</span> <span class="nv">either</span><span class="o">.</span><span class="py">fold</span><span class="o">(</span><span class="n">identity</span><span class="o">,</span> <span class="nv">_</span><span class="o">.</span><span class="py">data</span><span class="o">.</span><span class="py">id</span><span class="o">)</span>
            <span class="nv">foldFetchData</span><span class="o">.</span><span class="py">find</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">id</span> <span class="o">==</span> <span class="n">dataId</span><span class="o">).</span><span class="py">map</span> <span class="o">{</span>
              <span class="n">data</span> <span class="k">=&gt;</span> <span class="o">(</span><span class="n">foldData</span> <span class="o">:+</span> <span class="n">data</span><span class="o">,</span> <span class="nv">foldFetchData</span><span class="o">.</span><span class="py">filterNot</span><span class="o">(</span><span class="k">_</span> <span class="n">eq</span> <span class="n">data</span><span class="o">))</span>
            <span class="o">}.</span><span class="py">getOrElse</span> <span class="o">{</span>
              <span class="o">(</span><span class="n">foldData</span><span class="o">,</span> <span class="n">foldFetchData</span><span class="o">)</span>
            <span class="o">}</span>
        <span class="o">})</span>
    <span class="o">}</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>]]></content><author><name>Steven Skelton</name></author><category term="Scala" /><category term="ZIO" /><category term="Non-Blocking/Concurrency" /><category term="gRPC" /><summary type="html"><![CDATA[Expanding on the realtime Firebase implementation in the previous article, this expands the functionality allowing the server to fetch data on-demand from an external datasource. Additionally, functionality to periodical refresh active data which is subscribed to by connected clients transforms this database into an efficient cache to evolving external data which can only be obtained by polling.]]></summary></entry><entry><title type="html">Job Queue Execution Management using ZIO Scopes</title><link href="https://www.stevenskelton.ca/job-queue-management-zio-scope/" rel="alternate" type="text/html" title="Job Queue Execution Management using ZIO Scopes" /><published>2024-04-16T00:00:00-04:00</published><updated>2024-04-16T00:00:00-04:00</updated><id>https://www.stevenskelton.ca/job-queue-management-zio-scope</id><content type="html" xml:base="https://www.stevenskelton.ca/job-queue-management-zio-scope/"><![CDATA[<p>Job Queues are critical parts of Enterprise workloads. Complex queues use distributed nodes, state machines, and
complex scheduling to trigger and track running jobs. But when simplicity allows the best approach is to create small
idempotent jobs. The smaller the unit of work the easier progress can be tracked, jobs can be restarted or rerun with
minimal waste, composability and reuse are increased, and logic is easier to reason about. These are the same arguments
for Functional Programming and their Effect Systems, such as ZIO. Effect systems are congruent to the enterprise job
queue, with ZIO fibers performing work and ZIO <a href="https://zio.dev/reference/resource/">Resource Management</a>
forming the scheduling and supervision backbone. An efficient job queue can be written using ZIO constructs using
surprisingly minimal amount of code.<!--more--></p>

<style>
    .multiplex_responsive_1 { width: 360px; height: 100px; margin-top: 1em; margin-bottom: 1em; }
    @media(min-width: 720px) { .multiplex_responsive_1 { width: 360px; margin: 0; height: 900px; } }
</style>

<div class="toc-container">
    <aside class="sidebar__right">
<nav class="toc">
      <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> Table of Contents</h4></header>
<ul class="toc__menu" id="markdown-toc">
  <li><a href="#zio-resources-and-scope" id="markdown-toc-zio-resources-and-scope">ZIO Resources and Scope</a>    <ul>
      <li><a href="#queue-features" id="markdown-toc-queue-features">Queue Features</a>        <ul>
          <li><a href="#class-interface" id="markdown-toc-class-interface">Class Interface</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#zio-concurrency-uses-fibers-not-threads" id="markdown-toc-zio-concurrency-uses-fibers-not-threads">ZIO Concurrency uses Fibers, Not Threads</a>    <ul>
      <li><a href="#using-semaphore-for-concurrency" id="markdown-toc-using-semaphore-for-concurrency">Using Semaphore for Concurrency</a></li>
      <li><a href="#ref-and-refsynchronized" id="markdown-toc-ref-and-refsynchronized">Ref and Ref.Synchronized</a>        <ul>
          <li><a href="#ref-and-hash-codes" id="markdown-toc-ref-and-hash-codes">Ref and Hash Codes</a>            <ul>
              <li><a href="#javautillinkedhashset" id="markdown-toc-javautillinkedhashset">java.util.LinkedHashSet</a></li>
              <li><a href="#scalacollectionmutablelinkedhashset" id="markdown-toc-scalacollectionmutablelinkedhashset">scala.collection.mutable.LinkedHashSet</a></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#blocking-in-zio" id="markdown-toc-blocking-in-zio">Blocking in ZIO</a>    <ul>
      <li><a href="#queue-write-operations" id="markdown-toc-queue-write-operations">Queue Write Operations</a></li>
    </ul>
  </li>
  <li><a href="#creating-scope" id="markdown-toc-creating-scope">Creating Scope</a>    <ul>
      <li><a href="#using-acquirereleaseexit" id="markdown-toc-using-acquirereleaseexit">Using acquireReleaseExit</a></li>
      <li><a href="#scope-interruption" id="markdown-toc-scope-interruption">Scope Interruption</a>        <ul>
          <li><a href="#taking-n-from-queue" id="markdown-toc-taking-n-from-queue">Taking N from Queue</a></li>
          <li><a href="#handling-zero-queue-elements" id="markdown-toc-handling-zero-queue-elements">Handling Zero Queue Elements</a></li>
          <li><a href="#creating-scope-with-finalizer" id="markdown-toc-creating-scope-with-finalizer">Creating Scope with Finalizer</a></li>
        </ul>
      </li>
      <li><a href="#defining-scope-acquire-and-release-finalizer" id="markdown-toc-defining-scope-acquire-and-release-finalizer">Defining Scope acquire and release Finalizer</a></li>
      <li><a href="#closing-scope" id="markdown-toc-closing-scope">Closing Scope</a>        <ul>
          <li><a href="#streaming-consumers" id="markdown-toc-streaming-consumers">Streaming Consumers</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#element-uniqueness-using-equals--hashcode" id="markdown-toc-element-uniqueness-using-equals--hashcode">Element Uniqueness using equals / hashCode</a>    <ul>
      <li><a href="#feature-extensions-using-jobstatus-fields" id="markdown-toc-feature-extensions-using-jobstatus-fields">Feature Extensions using JobStatus Fields</a></li>
    </ul>
  </li>
  <li><a href="#queue-class-and-private-members" id="markdown-toc-queue-class-and-private-members">Queue Class and Private Members</a></li>
  <li><a href="#conclusion-putting-it-all-together" id="markdown-toc-conclusion-putting-it-all-together">Conclusion: Putting It All Together</a></li>
</ul>

    </nav>
</aside>
    <div class="toc-ad"><ins class="adsbygoogle multiplex_responsive_1" style="display:block" data-ad-client="ca-pub-5000348308466945" data-ad-slot="5418078712"></ins>
        <script>
            (adsbygoogle = window.adsbygoogle || []).push({});
        </script></div>
</div>

<h1 id="zio-resources-and-scope">ZIO Resources and Scope</h1>

<p>ZIO Resources form a contract preventing resource leaks and ensuring proper finalization of a closure. A job queue which
maintains observability of in-progress jobs has the same concern. Active jobs are open resources and need to properly
finalization after being consumed, in the same way an open file needs to be properly closed of after use.</p>

<p>The approach is release queue items with an attached ZIO <a href="https://zio.dev/reference/resource/scope/">Scope</a>. Work can
be performed within this scope, and the scope can be responsible for marking queue items either as consumed or to be
returned back into the queue due to processing failure.</p>

<figure class="">
  <img src="/assets/images/2024/04/unique_job_queue.svg" alt="Job queue using ListHashSet and ZIO Scopes to manage queue removal" class="fighascaption" style="padding: 10px; background-color: white; height: 320px;" /><figcaption>
      Job queue using ListHashSet and ZIO Scopes to manage queue removal

    </figcaption></figure>

<h2 id="queue-features">Queue Features</h2>

<ul>
  <li>Maintain a distinct list of queue entries.<br />
If objects are added multiple times the queue will only contain the first object, in its correct queue position.</li>
  <li>Automatically remove popped queue items after their work has been completed
This allows work in-progress to count towards the item uniqueness. Re-adding work that is already in-progress will be
rejected by the queue.</li>
  <li>Popping from the queue is a blocking operation
There is no need to poll the queue for new items, all consumers can stream items and fetch batches using thread-safe
operations.</li>
  <li>For simplicity, there is no <a href="https://en.wikipedia.org/wiki/Dead_letter_queue">dead-letter output</a>
Items which cannot be processed are returned to the queue by the Scope finalizer.</li>
</ul>

<h3 id="class-interface">Class Interface</h3>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DistinctZioJobQueue</span><span class="o">[</span><span class="kt">A</span><span class="o">]</span> <span class="o">{</span>

  <span class="c1">//Jobs queued for execution.</span>
  <span class="k">def</span> <span class="nf">queued</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Nothing</span>, <span class="kt">Seq</span><span class="o">[</span><span class="kt">A</span><span class="o">]]</span>

  <span class="c1">//Jobs currently executing.</span>
  <span class="k">def</span> <span class="nf">inProgress</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Nothing</span>, <span class="kt">Seq</span><span class="o">[</span><span class="kt">A</span><span class="o">]]</span>

  <span class="c1">//Add job to queue, will return `true` if successful. Jobs already in queue will return `false`.</span>
  <span class="k">def</span> <span class="nf">add</span><span class="o">(</span><span class="n">elem</span><span class="k">:</span> <span class="kt">A</span><span class="o">)</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Nothing</span>, <span class="kt">Boolean</span><span class="o">]</span>

  <span class="c1">//Add jobs to queue. Will return all jobs that failed to be added.</span>
  <span class="k">def</span> <span class="nf">addAll</span><span class="o">(</span><span class="n">elems</span><span class="k">:</span> <span class="kt">Seq</span><span class="o">[</span><span class="kt">A</span><span class="o">])</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Nothing</span>, <span class="kt">Seq</span><span class="o">[</span><span class="kt">A</span><span class="o">]]</span>

  <span class="c1">//Blocks until returning at least one, but no more than N, queued jobs.</span>
  <span class="k">def</span> <span class="nf">takeUpToNQueued</span><span class="o">(</span><span class="n">max</span><span class="k">:</span> <span class="kt">Int</span><span class="o">)</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Scope</span>, <span class="kt">Nothing</span>, <span class="kt">Seq</span><span class="o">[</span><span class="kt">A</span><span class="o">]]</span>

<span class="o">}</span>
</code></pre></div></div>

<h1 id="zio-concurrency-uses-fibers-not-threads">ZIO Concurrency uses Fibers, Not Threads</h1>

<h2 id="using-semaphore-for-concurrency">Using Semaphore for Concurrency</h2>

<p>The common approach to create concurrent collections in Java is using the JDK provided
wrapper <code class="language-plaintext highlighter-rouge">Collections.synchronizedSet()</code>. This is a great mechanism for handling thread-safety, however ZIO concurrency
operates with ZIO fibers making this approach untenable. It is incorrect to block threads at any time in ZIO outside
a <code class="language-plaintext highlighter-rouge">ZIO.blocking</code> scope because this will block all fibers using that thread. Fibers are the independent workers in ZIO,
not threads, and blocking the system thread will cause performance degradation and possible deadlocks.</p>

<p>The ZIO <a href="https://zio.dev/reference/concurrency/semaphore/">Semaphore</a> is the ZIO equivalent mechanism to provide
synchronization. It operates on the fiber level making it distinctly different from a JDK semaphore. The same
concurrency concerns are still valid while using fibers to access <code class="language-plaintext highlighter-rouge">LinkedHashSet</code> methods. All write operations
must be synchronized, and all read operations can only parallelize with other read operations. Any read operation
occurring during a write operation is vulnerable to a <code class="language-plaintext highlighter-rouge">ConcurrentModificationException</code> if it its <code class="language-plaintext highlighter-rouge">iterator</code> encounters
stale state.</p>

<h2 id="ref-and-refsynchronized">Ref and Ref.Synchronized</h2>

<p>In addition to semaphore, ZIO provides other concurrency mechanisms such as <code class="language-plaintext highlighter-rouge">Ref</code> and <code class="language-plaintext highlighter-rouge">STM</code>.
<a href="https://zio.dev/reference/stm/">Software Transactional Memory</a> is a powerful construct however requiring specialized
implementations of common classes, making it worthy of its own external discussion. The <code class="language-plaintext highlighter-rouge">Ref</code> construct is a very
accessible mechanism in ZIO comparable to the <code class="language-plaintext highlighter-rouge">AtomicReference</code> class in the JDK, but at a higher level. A noticeable
downside
is it is only suitable for immutable references. Our implementation uses a mutable <code class="language-plaintext highlighter-rouge">LinkedHashSet</code>.</p>

<h3 id="ref-and-hash-codes">Ref and Hash Codes</h3>

<p>There are fundamental differences between the Java and Scala library implementations of <code class="language-plaintext highlighter-rouge">LinkedHashSet</code>.</p>

<h4 id="javautillinkedhashset">java.util.LinkedHashSet</h4>

<p>The Java implementation of LinkedHashSet is a mutable implementation, and modifying it will not change its hashCode.
This means that wrapping it within either a <code class="language-plaintext highlighter-rouge">Ref</code> or <code class="language-plaintext highlighter-rouge">Synchronized</code> will cause all atomic guarantees to brake. The
atomicity is implemented using hashCode verification to detect write conflicts rather than thread synchronization to
the memory address. This is better for performance, but in this case it will effectivily behave as if there were no
write management at all.</p>

<h4 id="scalacollectionmutablelinkedhashset">scala.collection.mutable.LinkedHashSet</h4>

<p>The Scala implementation of LinkedHashSet is a mutable implementation, however it has a dynamically computed hashCode.
This will allow it to function correctly within a Ref under certain conditions. By incurring a performance overhead
during all writes it will allow the <code class="language-plaintext highlighter-rouge">AtomicReference.compareAndSet</code> method to work correctly.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">def</span> <span class="nf">hashCode</span><span class="k">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="o">{</span>
  <span class="k">val</span> <span class="nv">setIterator</span> <span class="k">=</span> <span class="k">this</span><span class="o">.</span><span class="py">iterator</span>
  <span class="k">val</span> <span class="nv">hashIterator</span><span class="k">:</span> <span class="kt">Iterator</span><span class="o">[</span><span class="kt">Any</span><span class="o">]</span> <span class="k">=</span>
    <span class="nf">if</span> <span class="o">(</span><span class="nv">setIterator</span><span class="o">.</span><span class="py">isEmpty</span><span class="o">)</span> <span class="n">setIterator</span>
    <span class="k">else</span> <span class="k">new</span> <span class="nc">HashSetIterator</span><span class="o">[</span><span class="kt">Any</span><span class="o">]</span> <span class="o">{</span>
      <span class="k">var</span> <span class="n">hash</span><span class="k">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">0</span>

      <span class="k">override</span> <span class="k">def</span> <span class="nf">hashCode</span><span class="k">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="n">hash</span>

      <span class="k">override</span> <span class="k">protected</span><span class="o">[</span><span class="kt">this</span><span class="o">]</span> <span class="k">def</span> <span class="nf">extract</span><span class="o">(</span><span class="n">nd</span><span class="k">:</span> <span class="kt">Node</span><span class="o">[</span><span class="kt">A</span><span class="o">])</span><span class="k">:</span> <span class="kt">Any</span> <span class="o">=</span> <span class="o">{</span>
        <span class="n">hash</span> <span class="k">=</span> <span class="nf">unimproveHash</span><span class="o">(</span><span class="nv">nd</span><span class="o">.</span><span class="py">hash</span><span class="o">)</span>
        <span class="k">this</span>
      <span class="o">}</span>
    <span class="o">}</span>
  <span class="nv">MurmurHash3</span><span class="o">.</span><span class="py">unorderedHash</span><span class="o">(</span><span class="n">hashIterator</span><span class="o">,</span> <span class="nv">MurmurHash3</span><span class="o">.</span><span class="py">setSeed</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div></div>

<p>There still remains the inability of LinkedHashSet to handle reads during writes. The primary benefit of immutability
is data never changes preventing all data instability during a read operations.</p>

<p>The motivation of the dynamic hashCode for the mutable LinkedHashSet in the Scala Collections Library is to allow
equality between immutable/mutable variants. Comparing a mutable Set to a immutable Set with the same internal elements
will be successful, creating a more powerful <code class="language-plaintext highlighter-rouge">Set</code> interface abstraction.</p>

<p>To allow the Scala LinkedHashSet to work within a <code class="language-plaintext highlighter-rouge">Ref</code> we will need to accept a hashCode calculation penalty on all
writes, as well as implement a gate around simultaneous read/write operations. This may be suitable in some situations,
but optimizing for fast reads is a more generally acceptable approach, and for these reasons our queue implementation
is better off using a semaphore directly.</p>

<h1 id="blocking-in-zio">Blocking in ZIO</h1>

<p>The primary mechanism to block ZIO fibers is by mapping from <code class="language-plaintext highlighter-rouge">await</code> on a <code class="language-plaintext highlighter-rouge">Promise</code>. When a second fiber completes
the promise using <code class="language-plaintext highlighter-rouge">succeed</code> the first fiber will unblock and resume execution. Within our queue, all consumers can await
the same activity promise. Whenever the queue state changes making queued elements available we will
call <code class="language-plaintext highlighter-rouge">notifyActivity</code> to complete the promise. The unblocked consumers are in a FIFO priority and will execute in order
until the queue is empty, initiating another blocked state.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">def</span> <span class="nf">notifyActivity</span><span class="k">:</span> <span class="kt">UIO</span><span class="o">[</span><span class="kt">Unit</span><span class="o">]</span> <span class="k">=</span>
  <span class="k">for</span> <span class="o">{</span>
    <span class="n">resetPromise</span> <span class="k">&lt;-</span> <span class="nv">Promise</span><span class="o">.</span><span class="py">make</span><span class="o">[</span><span class="kt">Nothing</span>, <span class="kt">Unit</span><span class="o">]</span>
    <span class="k">_</span> <span class="k">&lt;-</span> <span class="nv">promise</span><span class="o">.</span><span class="py">succeed</span><span class="o">(())</span>
  <span class="o">}</span> <span class="k">yield</span> <span class="n">promise</span> <span class="k">=</span> <span class="n">resetPromise</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">notifyActivity</code> method will complete the current promise, and replace it with a new uncompleted promise. All calls
to <code class="language-plaintext highlighter-rouge">notifyActivity</code> are within the <code class="language-plaintext highlighter-rouge">semaphore</code> write permit so access is guaranteed to be exclusive to the fiber,
allowing this to be a simple <code class="language-plaintext highlighter-rouge">var</code> instead of a <code class="language-plaintext highlighter-rouge">Ref</code>.</p>

<h2 id="queue-write-operations">Queue Write Operations</h2>

<p>The queue can be modified in 3 ways: elements added, elements removed, and elements undergoing status change. These map
to <code class="language-plaintext highlighter-rouge">add</code>/<code class="language-plaintext highlighter-rouge">addAll</code> calls and <code class="language-plaintext highlighter-rouge">Scope</code> creation / finalization. Exclusive access to the queue is enforced by reserving all
permits via <code class="language-plaintext highlighter-rouge">semaphore.withPermits(MaxReadFibers)</code>. This call will block until any outstanding permits reserved by
read-only operations have been returned, and will block any new read-only permits from being obtained.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">add</span><span class="o">(</span><span class="n">elem</span><span class="k">:</span> <span class="kt">A</span><span class="o">)</span><span class="k">:</span>
<span class="c1">//- obtain permits to LinkedHashSet</span>
<span class="c1">//- try to add to queue</span>
<span class="c1">//- if added successfully call notifyActivity</span>
<span class="c1">//- release permits</span>

<span class="k">def</span> <span class="nf">addAll</span><span class="o">(</span><span class="n">elems</span><span class="k">:</span> <span class="kt">Seq</span><span class="o">[</span><span class="kt">A</span><span class="o">])</span><span class="k">:</span>
<span class="c1">//- obtain permits to LinkedHashSet</span>
<span class="c1">//- try to add all to queue</span>
<span class="c1">//- if any added successfully call notifyActivity</span>
<span class="c1">//- release permits</span>

<span class="k">def</span> <span class="nf">takeUpToNQueued</span><span class="o">(</span><span class="n">max</span><span class="k">:</span> <span class="kt">Int</span><span class="o">)</span><span class="k">:</span>
<span class="c1">//- obtain permits to LinkedHashSet</span>
<span class="c1">//- return items with Scope</span>
<span class="c1">//- release permits</span>
<span class="c1">//- Scope finalizer:</span>
<span class="c1">//    - obtain permits to LinkedHashSet</span>
<span class="c1">//    - returns items to queue on exception</span>
<span class="c1">//    - calls notifyActivity</span>
<span class="c1">//    - release permits</span>
<span class="c1">//  or</span>
<span class="c1">//    - obtain permits to LinkedHashSet</span>
<span class="c1">//    - remove items from queue</span>
<span class="c1">//    - calls notifyActivity</span>
<span class="c1">//    - release permits</span>
</code></pre></div></div>

<p>Implementation details of <code class="language-plaintext highlighter-rouge">add</code> / <code class="language-plaintext highlighter-rouge">addAll</code> are straight-forward queue enqueue operations. The return values of these
may be immaterial for many use-cases. The example code emits enqueue outcomes to the server➤client stream, but for
network efficiency, these can be omitted.</p>

<p>The <code class="language-plaintext highlighter-rouge">takeUpToNQueue</code> implementation is best examined in 3 parts:</p>
<ul>
  <li>taking from the queue</li>
  <li>handling zero queue elements</li>
  <li>creating a scope with finalizer</li>
</ul>

<h1 id="creating-scope">Creating Scope</h1>

<h2 id="using-acquirereleaseexit">Using acquireReleaseExit</h2>

<p>The <code class="language-plaintext highlighter-rouge">Scope</code> is a <em>trait</em> and not typically defined as a named class. It should normally be anonymously constructed using
one of the <code class="language-plaintext highlighter-rouge">acquireRelease</code> methods. There are variations, the simplest being <code class="language-plaintext highlighter-rouge">acquireRelease</code> where the <em>acquire</em> is
an action to get an <code class="language-plaintext highlighter-rouge">A</code> and <em>release</em> is an action to perform on <code class="language-plaintext highlighter-rouge">A</code> to close it.</p>

<p>This queue will require the more advanced <code class="language-plaintext highlighter-rouge">acquireReleaseExit</code> method, which has the same <em>acquire</em> but
the <code class="language-plaintext highlighter-rouge">A</code> as well as <code class="language-plaintext highlighter-rouge">Exit</code> are available to the <em>release</em> as a tuple. ZIO <code class="language-plaintext highlighter-rouge">Exit</code> is the functional equivalent of the
Scala <code class="language-plaintext highlighter-rouge">Try</code>, resolving into either a <code class="language-plaintext highlighter-rouge">Success</code> or <code class="language-plaintext highlighter-rouge">Failure</code>. Failures can be the result of either exceptions or
interruptions.</p>

<h2 id="scope-interruption">Scope Interruption</h2>

<p>The other <code class="language-plaintext highlighter-rouge">Scope</code> creators <code class="language-plaintext highlighter-rouge">acquireReleaseInterruptible</code> and <code class="language-plaintext highlighter-rouge">acquireReleaseInterruptibleExit</code> need to be avoided here.
They lack the ability to determine the queue elements which were part of the scope being closed. This ability is 
critical in the correct operation of this queue (without very advanced logic being added). Because of this reason, the 
<code class="language-plaintext highlighter-rouge">takeUpToNQueue</code> has been broken into 3 stages:</p>

<h3 id="taking-n-from-queue">Taking N from Queue</h3>

<p>//TODO:</p>

<h3 id="handling-zero-queue-elements">Handling Zero Queue Elements</h3>

<p>This is separated</p>

<h3 id="creating-scope-with-finalizer">Creating Scope with Finalizer</h3>

<p>This is covered in the <a href="#creating-scope-using-acquirereleaseexit">Creating Scope using acquireReleaseExit</a> section
below.</p>

<h2 id="defining-scope-acquire-and-release-finalizer">Defining Scope acquire and release Finalizer</h2>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">acquire</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">?</span>, <span class="kt">?</span>, <span class="kt">Seq</span><span class="o">]</span>
<span class="c1">//- obtain permits to LinkedHashSet</span>
<span class="c1">//- iterate elements in queue collecting up to N unflagged</span>
<span class="c1">//- flag elements as being taken</span>
<span class="c1">//- if any elements unflagged call notifyActivity</span>
<span class="c1">//- release permits</span>
</code></pre></div></div>

<p>A line-by-line implementation of the Scope <em>acquire</em> would be:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">semaphore</span><span class="o">.</span><span class="py">withPermits</span><span class="o">(</span><span class="nc">MaxReadFibers</span><span class="o">)</span> <span class="o">{</span>
  <span class="k">val</span> <span class="nv">flagged</span> <span class="k">=</span> <span class="nv">linkedHashSet</span><span class="o">.</span><span class="py">iterator</span>
    <span class="o">.</span><span class="py">filter</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">status</span> <span class="o">==</span> <span class="nv">Status</span><span class="o">.</span><span class="py">Queued</span><span class="o">)</span>
    <span class="o">.</span><span class="py">take</span><span class="o">(</span><span class="n">max</span><span class="o">)</span>
    <span class="o">.</span><span class="py">map</span> <span class="o">{</span>
      <span class="n">jobStatus</span> <span class="k">=&gt;</span>
        <span class="nv">jobStatus</span><span class="o">.</span><span class="py">status</span> <span class="k">=</span> <span class="nv">Status</span><span class="o">.</span><span class="py">InProgress</span>
        <span class="nv">jobStatus</span><span class="o">.</span><span class="py">a</span>
    <span class="o">}</span>
  <span class="nf">if</span> <span class="o">(</span><span class="nv">flagged</span><span class="o">.</span><span class="py">nonEmpty</span><span class="o">)</span> <span class="nv">notifyActivity</span><span class="o">.</span><span class="py">as</span><span class="o">(</span><span class="n">flagged</span><span class="o">)</span> <span class="k">else</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">unit</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The finalizer will be outcome dependent, with a path for successful Scope closure and one for failure.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">release</span><span class="k">:</span> <span class="o">(</span><span class="kt">Seq</span><span class="o">,</span> <span class="kt">Exit</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Any</span><span class="o">])</span> <span class="k">=&gt;</span> <span class="nc">ZIO</span><span class="o">[</span><span class="kt">?</span>, <span class="kt">Nothing</span>, <span class="kt">Any</span><span class="o">]</span> <span class="k">=</span> <span class="o">{</span>
  <span class="c1">//if Seq has elements from the Queue:</span>
  <span class="c1">//- obtain permits to LinkedHashSet</span>
  <span class="c1">//- loop through all elements in Seq</span>
  <span class="c1">//- if Exit was success:</span>
  <span class="c1">//  - remove element from Queue</span>
  <span class="c1">//- if Exit was failure:</span>
  <span class="c1">//  - unflag element as taken in Queue</span>
  <span class="c1">//- if any elements unflagged call notifyActivity</span>
  <span class="c1">//- release permits</span>
<span class="o">}</span>
</code></pre></div></div>

<p>A line-by-line implementation of the Scope <em>release</em> finalizer would be:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span><span class="n">takeUpToNOption</span><span class="o">,</span> <span class="n">exit</span><span class="o">)</span> <span class="k">=&gt;</span>
  <span class="nv">takeUpToNOption</span><span class="o">.</span><span class="py">fold</span><span class="o">(</span><span class="kc">false</span><span class="o">)(</span><span class="n">seq</span> <span class="k">=&gt;</span>
    <span class="nv">semaphore</span><span class="o">.</span><span class="py">withPermits</span><span class="o">(</span><span class="nc">MaxReadFibers</span><span class="o">)</span> <span class="o">{</span>
      <span class="k">val</span> <span class="nv">activity</span> <span class="k">=</span> <span class="nv">seq</span><span class="o">.</span><span class="py">foldLeft</span><span class="o">(</span><span class="kc">false</span><span class="o">)((</span><span class="n">hasActivity</span><span class="o">,</span> <span class="n">a</span><span class="o">)</span> <span class="k">=&gt;</span>
        <span class="n">exit</span> <span class="k">match</span> <span class="o">{</span>
          <span class="k">case</span> <span class="nv">Exit</span><span class="o">.</span><span class="py">Success</span><span class="o">(</span><span class="k">_</span><span class="o">)</span> <span class="k">=&gt;</span>
            <span class="k">val</span> <span class="nv">_</span> <span class="k">=</span> <span class="nv">linkedHashSet</span><span class="o">.</span><span class="py">remove</span><span class="o">(</span><span class="nc">JobStatus</span><span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="nv">Status</span><span class="o">.</span><span class="py">InProgress</span><span class="o">))</span>
            <span class="n">hasActivity</span>
          <span class="k">case</span> <span class="nv">Exit</span><span class="o">.</span><span class="py">Failure</span><span class="o">(</span><span class="k">_</span><span class="o">)</span> <span class="k">=&gt;</span>
            <span class="k">val</span> <span class="nv">_</span> <span class="k">=</span> <span class="nv">linkedHashSet</span><span class="o">.</span><span class="py">find</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">a</span> <span class="o">==</span> <span class="n">a</span><span class="o">).</span><span class="py">foreach</span><span class="o">(</span><span class="nv">_</span><span class="o">.</span><span class="py">status</span> <span class="k">=</span> <span class="nv">Status</span><span class="o">.</span><span class="py">Queued</span><span class="o">)</span>
            <span class="kc">true</span>
        <span class="o">})</span>
      <span class="nf">if</span> <span class="o">(</span><span class="n">activity</span><span class="o">)</span> <span class="n">notifyActivity</span> <span class="k">else</span> <span class="nv">ZIO</span><span class="o">.</span><span class="py">unit</span>
    <span class="o">}</span>
  <span class="o">)</span>
</code></pre></div></div>

<h2 id="closing-scope">Closing Scope</h2>

<p>The mechanism to close the scope will depend on the actions performed by the consumer. The <code class="language-plaintext highlighter-rouge">Scope</code> is part of the ZIO
effect’s <em>environment</em> and appears in the type signature until it is removed. Explicitly defining a scope boundary can
be done using the <code class="language-plaintext highlighter-rouge">ZIO.scope</code> partial function. This is a closure around effect code, and drops it from the
<em>environment</em> type:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">ZIO</span><span class="o">.</span><span class="py">scope</span><span class="o">(</span><span class="n">zio</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Scope</span>, <span class="kt">?</span>, <span class="kt">?</span><span class="o">])</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">?</span>, <span class="kt">?</span>, <span class="kt">?</span><span class="o">]</span>
</code></pre></div></div>

<h3 id="streaming-consumers">Streaming Consumers</h3>

<p>Typical consumers would adopt a stream pattern. As the queue releases <code class="language-plaintext highlighter-rouge">NonEmptyChunk</code> elements within a <code class="language-plaintext highlighter-rouge">Scope</code>,
consumers should opt to complete the scope as soon as possible, however long-running scope have very minimal impact on
the queue as it is already optimized minimal memory consumption and internal iteration performance.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">val</span> <span class="nv">consumer</span><span class="k">:</span> <span class="kt">ZStream</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Throwable</span>, <span class="kt">T</span><span class="o">]</span> <span class="k">=</span> <span class="nv">ZStream</span><span class="o">.</span><span class="py">repeatZIO</span> <span class="o">{</span>
  <span class="nv">ZIO</span><span class="o">.</span><span class="py">scoped</span> <span class="o">{</span>
    <span class="nv">queue</span><span class="o">.</span><span class="py">takeUpToNQueued</span><span class="o">(?).</span><span class="py">map</span> <span class="o">{</span>
      <span class="n">nonEmptyChunk</span> <span class="k">=&gt;</span> <span class="o">???</span> <span class="c1">//function to create Ts</span>
    <span class="o">}</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>//TODO:</p>

<h1 id="element-uniqueness-using-equals--hashcode">Element Uniqueness using equals / hashCode</h1>

<p>The basic requirement for this queue will be able to flag entries as either queued, or having been released within a
scope by a consumer.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="n">enum</span> <span class="nc">Status</span> <span class="o">{</span>
  <span class="k">case</span> <span class="nc">Queued</span><span class="o">,</span> <span class="nc">InProgress</span>
<span class="o">}</span>
</code></pre></div></div>

<p>To attach a <code class="language-plaintext highlighter-rouge">status</code> field to each entry, we can wrap entries with a <code class="language-plaintext highlighter-rouge">JobStatus</code> class, but overwrite the
<code class="language-plaintext highlighter-rouge">equals</code> and <code class="language-plaintext highlighter-rouge">hashCode</code> fields such that they will only consider the queued item, and not the current status. This
ensures that trying to add a duplicate entry will be blocked regardless of the existing item’s status.</p>

<h2 id="feature-extensions-using-jobstatus-fields">Feature Extensions using JobStatus Fields</h2>

<p>The <code class="language-plaintext highlighter-rouge">JobStatus</code> class could be expanded to support additional queue functionality. Queue extensions typically include
performance metrics: queued time, in-progress time, and operational metrics: add-collision count and failure-count from
any failed consumer executions.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">class</span> <span class="nc">JobStatus</span><span class="o">[</span><span class="kt">A</span><span class="o">](</span><span class="k">val</span> <span class="nv">a</span><span class="k">:</span> <span class="kt">A</span><span class="o">,</span> <span class="k">var</span> <span class="n">status</span><span class="k">:</span> <span class="kt">Status</span><span class="o">)</span> <span class="o">{</span>
  <span class="k">override</span> <span class="k">def</span> <span class="nf">hashCode</span><span class="o">()</span><span class="k">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="nv">a</span><span class="o">.</span><span class="py">hashCode</span><span class="o">()</span>

  <span class="k">override</span> <span class="k">def</span> <span class="nf">equals</span><span class="o">(</span><span class="n">obj</span><span class="k">:</span> <span class="kt">Any</span><span class="o">)</span><span class="k">:</span> <span class="kt">Boolean</span> <span class="o">=</span> <span class="n">obj</span> <span class="k">match</span> <span class="o">{</span>
    <span class="k">case</span> <span class="n">o</span><span class="k">:</span> <span class="kt">JobStatus</span><span class="o">[</span><span class="kt">?</span><span class="o">]</span> <span class="k">=&gt;</span> <span class="nv">o</span><span class="o">.</span><span class="py">a</span><span class="o">.</span><span class="py">equals</span><span class="o">(</span><span class="n">a</span><span class="o">)</span>
    <span class="k">case</span> <span class="k">_</span> <span class="k">=&gt;</span> <span class="kc">false</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h1 id="queue-class-and-private-members">Queue Class and Private Members</h1>

<p>There are 3 private variables in the queue (these are constructor params to avoid <code class="language-plaintext highlighter-rouge">Unsafe</code> construction). They are all
intended to be internal references only.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">UniqueJobQueue</span><span class="o">[</span><span class="kt">A</span><span class="o">](</span>
                         <span class="k">private</span> <span class="k">val</span> <span class="nv">semaphore</span><span class="k">:</span> <span class="kt">Semaphore</span><span class="o">,</span>
                         <span class="k">private</span> <span class="k">var</span> <span class="n">promise</span><span class="k">:</span> <span class="kt">Promise</span><span class="o">[</span><span class="kt">Nothing</span>, <span class="kt">Unit</span><span class="o">],</span>
                       <span class="o">)</span> <span class="o">{</span>
  <span class="k">val</span> <span class="nv">linkedHashSet</span> <span class="k">=</span> <span class="k">new</span> <span class="nv">mutable</span><span class="o">.</span><span class="py">LinkedHashSet</span><span class="o">[</span><span class="kt">JobStatus</span><span class="o">[</span><span class="kt">A</span><span class="o">]]</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">semaphore</code> will be the read/write synchronization for the <code class="language-plaintext highlighter-rouge">linkedHashSet</code>.  The <code class="language-plaintext highlighter-rouge">promise</code> will be used to signal 
consumers to retry the queue because elements have been added. The <code class="language-plaintext highlighter-rouge">promise</code> is a mutable <em>var</em> but will only be 
modified behind the <code class="language-plaintext highlighter-rouge">semaphore</code> write guard, ensuring no write conflicts.</p>

<h1 id="conclusion-putting-it-all-together">Conclusion: Putting It All Together</h1>

<p>The example code utilizes type alias, such as <code class="language-plaintext highlighter-rouge">type URIO[-R, +A] = ZIO[R, Nothing, A]</code>, as well as using
<code class="language-plaintext highlighter-rouge">Chunk</code> and <code class="language-plaintext highlighter-rouge">NonEmptyChunk</code> instead of <code class="language-plaintext highlighter-rouge">Seq</code>.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">SynchronizedUniqueJobQueue</span><span class="o">[</span><span class="kt">A</span><span class="o">](</span>
                         <span class="k">private</span> <span class="k">val</span> <span class="nv">semaphore</span><span class="k">:</span> <span class="kt">Semaphore</span><span class="o">,</span>
                         <span class="k">private</span> <span class="k">var</span> <span class="n">promise</span><span class="k">:</span> <span class="kt">Promise</span><span class="o">[</span><span class="kt">Nothing</span>, <span class="kt">Unit</span><span class="o">],</span>
                       <span class="o">)</span> <span class="o">{</span>
  
  <span class="k">private</span> <span class="k">val</span> <span class="nv">linkedHashSet</span> <span class="k">=</span> <span class="k">new</span> <span class="nv">mutable</span><span class="o">.</span><span class="py">LinkedHashSet</span><span class="o">[</span><span class="kt">JobStatus</span><span class="o">[</span><span class="kt">A</span><span class="o">]]</span>

  <span class="k">def</span> <span class="nf">add</span><span class="o">(</span><span class="n">elem</span><span class="k">:</span> <span class="kt">A</span><span class="o">)</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Nothing</span>, <span class="kt">Boolean</span><span class="o">]</span>

  <span class="k">def</span> <span class="nf">addAll</span><span class="o">(</span><span class="n">elems</span><span class="k">:</span> <span class="kt">Seq</span><span class="o">[</span><span class="kt">A</span><span class="o">])</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Nothing</span>, <span class="kt">Seq</span><span class="o">[</span><span class="kt">A</span><span class="o">]]</span>

  <span class="k">def</span> <span class="nf">takeUpToNQueued</span><span class="o">(</span><span class="n">max</span><span class="k">:</span> <span class="kt">Int</span><span class="o">)</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Scope</span>, <span class="kt">Nothing</span>, <span class="kt">Seq</span><span class="o">[</span><span class="kt">A</span><span class="o">]]</span>
  
  <span class="k">private</span> <span class="k">def</span> <span class="nf">takeUpToQueuedAllowEmpty</span><span class="o">(</span><span class="n">max</span><span class="k">:</span> <span class="kt">Int</span><span class="o">)</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Scope</span>, <span class="kt">Nothing</span>, <span class="kt">Option</span><span class="o">[</span><span class="kt">Seq</span><span class="o">[</span><span class="kt">A</span><span class="o">]]]</span>

  <span class="k">private</span> <span class="k">def</span> <span class="nf">notifyActivity</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Any</span>, <span class="kt">Nothing</span>, <span class="kt">Unit</span><span class="o">]</span>
<span class="o">}</span>
</code></pre></div></div>]]></content><author><name>Steven Skelton</name></author><category term="Scala" /><category term="ZIO" /><category term="Non-Blocking/Concurrency" /><summary type="html"><![CDATA[Job Queues are critical parts of Enterprise workloads. Complex queues use distributed nodes, state machines, and complex scheduling to trigger and track running jobs. But when simplicity allows the best approach is to create small idempotent jobs. The smaller the unit of work the easier progress can be tracked, jobs can be restarted or rerun with minimal waste, composability and reuse are increased, and logic is easier to reason about. These are the same arguments for Functional Programming and their Effect Systems, such as ZIO. Effect systems are congruent to the enterprise job queue, with ZIO fibers performing work and ZIO Resource Management forming the scheduling and supervision backbone. An efficient job queue can be written using ZIO constructs using surprisingly minimal amount of code.]]></summary></entry><entry><title type="html">Realtime Client Database: gRPC Bi-Directional Streams and ZIO Hub</title><link href="https://www.stevenskelton.ca/realtime-client-database-grpc-streams-zio/" rel="alternate" type="text/html" title="Realtime Client Database: gRPC Bi-Directional Streams and ZIO Hub" /><published>2024-04-15T00:00:00-04:00</published><updated>2024-04-15T00:00:00-04:00</updated><id>https://www.stevenskelton.ca/realtime-client-database-grpc-streams-zio</id><content type="html" xml:base="https://www.stevenskelton.ca/realtime-client-database-grpc-streams-zio/"><![CDATA[<p>Realtime push-based databases such as <a href="https://firebase.google.com/docs/database">Google Firebase</a> conveniently ensure
clients are synchronized with the server. Data updates stream to clients immediately as they happen; and if a client
disconnects, updates are immediately processed after reconnecting.</p>

<p><a href="https://grpc.io/docs/what-is-grpc/core-concepts/#server-streaming-rpc">gRPC server streaming</a>
and <a href="https://zio.dev/reference/concurrency/hub/">ZIO Hub</a> can implement this functionality replicating an expensive paid
Firebase service while allowing greater extensibility.<!--more--></p>

<div class="html-bg">
  <strong>Other Posts in this Series</strong>
  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/realtime-client-database-external-datasource-zlayer/" rel="permalink">Realtime Client Database: External Datasource using ZIO ZLayer
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-05-01T00:00:00-04:00">May 1, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          2 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/realtime-client-database-performance-testing/" rel="permalink">Realtime Client Database: Performance Testing in the Cloud
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-05-06T00:00:00-04:00">May 6, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          1 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  

    

    <div class="list__item multi-part-post">
      <article class="archive__item" itemscope="" itemtype="https://schema.org/CreativeWork">
        <h3 class="archive__item-title no_toc" itemprop="headline">
          
          <a href="/flutter-realtime-client-database-bloc/" rel="permalink">Realtime Client Database: Flutter client using Bloc and gRPC
</a>
          
        </h3>
        

  <p class="page__meta">
    
      
      <span class="page__meta-date">
        <i class="far fa-calendar-alt" aria-hidden="true"></i>
        
        <time datetime="2024-06-05T00:00:00-04:00">June 5, 2024</time>
      </span>
    

    <span class="page__meta-sep"></span>

    
      
      

      <span class="page__meta-readtime">
        <i class="far fa-clock" aria-hidden="true"></i>
        
          less than 1 minute read
        
      </span>
    
  </p>


      </article>
    </div>

  
</div>

<style>
    .multiplex_responsive_1 { width: 360px; height: 100px; margin-top: 1em; margin-bottom: 1em; }
    @media(min-width: 720px) { .multiplex_responsive_1 { width: 360px; margin: 0; height: 100px; } }
</style>

<div class="toc-container">
    <aside class="sidebar__right">
<nav class="toc">
      <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> Table of Contents</h4></header>
<ul class="toc__menu" id="markdown-toc">
  <li><a href="#evolving-user-demands-for-data-streaming" id="markdown-toc-evolving-user-demands-for-data-streaming">Evolving User Demands for Data Streaming</a></li>
  <li><a href="#bi-directional-grpc-streaming-using-http2" id="markdown-toc-bi-directional-grpc-streaming-using-http2">Bi-Directional gRPC Streaming using HTTP/2</a>    <ul>
      <li><a href="#todays-complementary-web-standards" id="markdown-toc-todays-complementary-web-standards">Today’s Complementary Web Standards</a>        <ul>
          <li><a href="#websockets" id="markdown-toc-websockets">WebSockets</a></li>
          <li><a href="#http2-streaming" id="markdown-toc-http2-streaming">HTTP/2 Streaming</a></li>
        </ul>
      </li>
      <li><a href="#grpc-a-remote-procedure-call-rpc-framework" id="markdown-toc-grpc-a-remote-procedure-call-rpc-framework">gRPC: A Remote Procedure Call (RPC) framework</a>        <ul>
          <li><a href="#subscription-request-and-response-objects" id="markdown-toc-subscription-request-and-response-objects">Subscription Request and Response Objects</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#data" id="markdown-toc-data">Data</a></li>
  <li><a href="#adding-data-updates" id="markdown-toc-adding-data-updates">Adding Data Updates</a>    <ul>
      <li><a href="#etag-and-timestamp" id="markdown-toc-etag-and-timestamp">ETag and Timestamp</a></li>
    </ul>
  </li>
</ul>

    </nav>
</aside>
    <div class="toc-ad"><ins class="adsbygoogle multiplex_responsive_1" style="display:block" data-ad-client="ca-pub-5000348308466945" data-ad-slot="5418078712"></ins>
        <script>
            (adsbygoogle = window.adsbygoogle || []).push({});
        </script></div>
</div>

<h1 id="evolving-user-demands-for-data-streaming">Evolving User Demands for Data Streaming</h1>

<p>Typical web-based client-server communication is to have clients initiate all requests to the server. This minimizes
load on the server as very minimal processing needs to be performed between requests. As server technology and hardware
performance have increased, more robust paradigms have evolved to cater to rising user expectations. To reduce
notification latencies, it was imperative to allow server request initiation. Immediately reaction by the server to<br />
external changes can be set to clients without waiting for manual user initiations or for a polling delay to elapse.</p>

<h1 id="bi-directional-grpc-streaming-using-http2">Bi-Directional gRPC Streaming using HTTP/2</h1>

<p>Two technology standards for bi-directional web communications have become standards:
<a href="https://en.wikipedia.org/wiki/WebSocket">WebSockets</a>
and <a href="https://datatracker.ietf.org/doc/html/rfc9113#name-streams-and-multiplexing">HTTP/2 streams</a>.</p>

<h2 id="todays-complementary-web-standards">Today’s Complementary Web Standards</h2>

<h3 id="websockets">WebSockets</h3>

<p>WebSockets were created first of the two as an extension to the HTTP/1.1 standard. By allowing clients to issue
an <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Upgrade">Upgrade request</a> on an established HTTP/1.1
connection, capable servers can switch the connection to use the <em>websocket</em> network protocol, supporting bi-directional
streaming. Since this is an upgrade mechanism, backwards compatibility and fallback support for older clients has less
complication to implement. The WebSocket protocol has a common JavaScript API across all browsers, making it today’s
preferred bidirectional protocol within browser use-cases. Despite <em>websocket</em> being an unversioned specification it
has seen improvements over time by way of it being built on top of an HTTP protocol. HTTP/2 significantly improved
connection performance through connection multiplexing, allowing websockets to benefit when clients use multiple
websocket connections simultaneously as they are now being multiplexed over the single upgraded HTTP/2 connection.</p>

<h3 id="http2-streaming">HTTP/2 Streaming</h3>

<p>For non-browser communications such as by mobile apps or server-server communications, WebSockets is an unnecessary
layer. WebSockets is an upgraded HTTP connection, implemented by creating an additional layer in the networking
protocol. The changes in HTTP/2 directly addressed bidirectional communication streams, so when the WebSocket API isn’t
beneficial, it is optimal to use the HTTP protocol capabilities directly. HTTP/2 streaming is the preferred
bidirectional mechanism for all use-cases without a web browser client or JavaScript dependency.</p>

<h2 id="grpc-a-remote-procedure-call-rpc-framework">gRPC: A Remote Procedure Call (RPC) framework</h2>

<p>gRPC is a high-performance networking framework built on top of HTTP/2, supporting multiple programming languages for
both client and server implementation. It encodes network traffic using <a href="https://protobuf.dev/">Protocol Buffers</a> using
<em>proto</em> files and syntax, which also are used to define the server API exposed to clients.</p>

<p>A gRPC server which will be used for the realtime database will be called <em>SyncService</em>, and expose a bidirectional
stream on the <em>Bidirectional</em> endpoint, receiving <em>Request</em> objects and emitting <em>Response</em> objects to their respective
streams.</p>

<p>A <em>Update</em> endpoint has also been defined, this is the typical client-initiated handler, which will receive a
<code class="language-plaintext highlighter-rouge">UpdateRequest</code> object from the client and return a <code class="language-plaintext highlighter-rouge">UpdateResponse</code> object to the client.</p>

<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">service</span> <span class="n">SyncService</span> <span class="p">{</span>
  <span class="k">rpc</span> <span class="n">Bidirectional</span> <span class="p">(</span><span class="n">stream</span> <span class="n">Request</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">stream</span> <span class="n">Response</span><span class="p">);</span>
  <span class="k">rpc</span> <span class="n">Update</span><span class="p">(</span><span class="n">UpdateRequest</span><span class="p">)</span> <span class="k">returns</span> <span class="p">(</span><span class="n">UpdateResponse</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This maps to the Scala interface:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="cm">/**
 * Requests will subscribe/unsubscribe to `Data`.
 * Data updates of subscribed elements is streamed in realtime.
 */</span>
<span class="k">def</span> <span class="nf">bidirectionalStream</span><span class="o">(</span><span class="n">request</span><span class="k">:</span> <span class="kt">Stream</span><span class="o">[</span><span class="kt">StatusException</span>, <span class="kt">SyncRequest</span><span class="o">])</span><span class="k">:</span> <span class="kt">Stream</span><span class="o">[</span><span class="kt">StatusException</span>, <span class="kt">SyncResponse</span><span class="o">]</span>

<span class="cm">/**
 * Creation / Update of `Data`. Response will indicate success or failure due to write conflict.
 * Conflicts are detected based on the ETag in the request.
 */</span>
<span class="k">def</span> <span class="nf">update</span><span class="o">(</span><span class="n">request</span><span class="k">:</span> <span class="kt">UpdateRequest</span><span class="o">)</span><span class="k">:</span> <span class="kt">IO</span><span class="o">[</span><span class="kt">StatusException</span>, <span class="kt">UpdateResponse</span><span class="o">]</span>
</code></pre></div></div>

<h3 id="subscription-request-and-response-objects">Subscription Request and Response Objects</h3>

<p>A basic subscription mechanism will allow clients to subscribe and unsubscribe to object updates based on the object id.
The protobuf definition for requests is:</p>

<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">message</span> <span class="nc">SyncRequest</span> <span class="p">{</span>
  <span class="k">repeated</span> <span class="n">Subscribe</span> <span class="na">subscribes</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="k">repeated</span> <span class="n">Unsubscribe</span> <span class="na">unsubscribes</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The definition for responses is the <code class="language-plaintext highlighter-rouge">Data</code> object itself:</p>

<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">message</span> <span class="nc">SyncResponse</span> <span class="p">{</span>
  <span class="n">Data</span> <span class="na">data</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Within the server implementation, we will define a <em>Subscription Manager</em> which will remember subscriptions for
a specific client. ZIO has a primitive called <a href="https://zio.dev/reference/concurrency/hub/">ZIO Hub</a> which allows
objects such as these <em>Subscription Manager</em> to subscribe to a singular, central message queue. The queue will receive
notifications to all <code class="language-plaintext highlighter-rouge">Data</code> updates, and each client <em>Subscription Manager</em> will subscribe to the Hub and filter for
events of the ids its client has requested. Because this Hub will queue the stream of database changes, we will name
it <code class="language-plaintext highlighter-rouge">journal</code> since database journals have similar behaviour.</p>

<p>A helpful feature is to return the current <code class="language-plaintext highlighter-rouge">Data</code> object back in the client response when initiating a subscription. 
This operation has been indicated by a yellow dashed line in the function diagram.</p>

<figure class="">
  <img src="/assets/images/2024/04/realtime_database.svg" alt="Realtime database pushing updates to clients using bi-directional gRPC Streams" class="fighascaption" style="padding: 10px; background-color: white;" /><figcaption>
      Realtime database pushing updates to clients using bi-directional gRPC Streams

    </figcaption></figure>

<h1 id="data">Data</h1>

<p>The <code class="language-plaintext highlighter-rouge">Data</code> class will represent an arbitrary data record class, code will rely on the presence of an <code class="language-plaintext highlighter-rouge">id</code> field, here
represented as an <code class="language-plaintext highlighter-rouge">uint32</code>. While this type doesn’t exist in Java, it adds clarity to the API, but as the Protocol
Buffers Documentation <a href="https://protobuf.dev/programming-guides/api/">API Best Practices</a> indicates, limits to even the
<em>int64</em> addressable range may make
a <a href="https://protobuf.dev/programming-guides/api/#integer-field-for-id">string id preferable</a>. The <code class="language-plaintext highlighter-rouge">field1</code> field
represents an arbitrary field, it could be extrapolated to have <code class="language-plaintext highlighter-rouge">Data</code> contain additional fields (<code class="language-plaintext highlighter-rouge">field2</code>,<code class="language-plaintext highlighter-rouge">field3</code>
etc.).</p>

<div class="language-protobuf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">message</span> <span class="nc">Data</span> <span class="p">{</span>
  <span class="kt">uint32</span> <span class="na">id</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="kt">string</span> <span class="na">field1</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="adding-data-updates">Adding Data Updates</h1>

<h2 id="etag-and-timestamp">ETag and Timestamp</h2>

<p>ETags are part of the HTTP Specification and exist to reduce network transfer. The HTTP <code class="language-plaintext highlighter-rouge">If-None-Match</code> header, when
implemented signals that should the response have the same generated ETag that the server should respond with a <em>HTTP
304 Not Modified</em> instead of a 200 Success with a populated body.</p>

<p>The usefulness of an ETag depends on server support: APIs may implement ETag support similar to HTTP Specification and
use it to omit a response body, others may use it internally to return a previous response from its cache, while others
solely include it as a convenience for clients.</p>

<p>Our API will use an ETag to have our server only return a full Data object on subscription if the client either doesn’t
have a previous copy (ie: no ETag available) or has a stale version (ie: conflicting ETag).</p>

<figure class="">
  <img src="/assets/images/2024/04/etag_use.svg" alt="Conflicting ETag hashcode will result in an update response from the server" class="fighascaption" style="padding: 10px; background-color: white;" /><figcaption>
      Conflicting ETag hashcode will result in an update response from the server

    </figcaption></figure>

<p>To support this functionality, as well as many others which may depend on fetch/cache durations, we’ll associate an
ETag and last updated time to all <code class="language-plaintext highlighter-rouge">Data</code> elements by wrapping them in a new <code class="language-plaintext highlighter-rouge">DataRecord</code> class:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="k">class</span> <span class="nc">DataRecord</span><span class="o">(</span><span class="n">data</span><span class="k">:</span> <span class="kt">Data</span><span class="o">,</span> <span class="n">lastUpdate</span><span class="k">:</span> <span class="kt">Instant</span><span class="o">,</span> <span class="n">etag</span><span class="k">:</span> <span class="kt">ETag</span><span class="o">)</span>
</code></pre></div></div>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Update `databaseRecordsRef` with data in `request`, rejecting any conflicts.
 * Conflicts are based on the included ETag:
 * If the `previousEtag` doesn't match the ETag in the database it is a conflict. 
 * New item creation ignores the `previousEtag` field.
 * All database item creation / updates are emitting to `journal`.
 */</span>
<span class="k">def</span> <span class="nf">updateDatabaseRecords</span><span class="o">(</span>
                           <span class="n">request</span><span class="k">:</span> <span class="kt">UpdateRequest</span><span class="o">,</span>
                           <span class="n">journal</span><span class="k">:</span> <span class="kt">Hub</span><span class="o">[</span><span class="kt">Data</span><span class="o">],</span>
                           <span class="n">databaseRecordsRef</span><span class="k">:</span> <span class="kt">Ref</span><span class="o">[</span><span class="kt">Map</span><span class="o">[</span><span class="kt">Int</span>, <span class="kt">Data</span><span class="o">]]</span>
                         <span class="o">)</span><span class="k">:</span> <span class="kt">UIO</span><span class="o">[</span><span class="kt">UpdateResponse</span><span class="o">]</span>
</code></pre></div></div>

<p>We will need to call an effect to connect the <em>Subscription Manager</em> to the journal Hub:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Create Stream from database `journal`
 */</span>
<span class="k">def</span> <span class="nf">userSubscriptionStream</span><span class="o">(</span>
                            <span class="n">userSubscriptionsRef</span><span class="k">:</span> <span class="kt">Ref</span><span class="o">[</span><span class="kt">HashSet</span><span class="o">[</span><span class="kt">Int</span><span class="o">]],</span>
                            <span class="n">journal</span><span class="k">:</span> <span class="kt">Hub</span><span class="o">[</span><span class="kt">Data</span><span class="o">]</span>
                          <span class="o">)</span><span class="k">:</span> <span class="kt">ZIO</span><span class="o">[</span><span class="kt">Scope</span>, <span class="kt">Nothing</span>, <span class="kt">Stream</span><span class="o">[</span><span class="kt">StatusException</span>, <span class="kt">SyncResponse</span><span class="o">]]</span>
</code></pre></div></div>

<p>We will need to connect the</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * Update `userSubscriptionsRef` with subscription changes.
 */</span>
<span class="k">def</span> <span class="nf">modifyUserSubscriptions</span><span class="o">(</span>
                             <span class="n">syncRequest</span><span class="k">:</span> <span class="kt">SyncRequest</span><span class="o">,</span>
                             <span class="n">userSubscriptionsRef</span><span class="k">:</span> <span class="kt">Ref</span><span class="o">[</span><span class="kt">HashSet</span><span class="o">[</span><span class="kt">Int</span><span class="o">]],</span>
                             <span class="n">databaseRecords</span><span class="k">:</span> <span class="kt">Map</span><span class="o">[</span><span class="kt">Int</span>, <span class="kt">Data</span><span class="o">]</span>
                           <span class="o">)</span><span class="k">:</span> <span class="kt">UIO</span><span class="o">[</span><span class="kt">Seq</span><span class="o">[</span><span class="kt">SyncResponse</span><span class="o">]]</span>

</code></pre></div></div>

<p>//TODO:</p>

<p>The <code class="language-plaintext highlighter-rouge">context: AuthenticatedUser</code> parameter is for a feature exposed by ZIO to handle gRPC metadata. Every gRPC request
is able to provide headers, similar to how HTTP allows request headers. ZIO generates 2 interface</p>]]></content><author><name>Steven Skelton</name></author><category term="Scala" /><category term="ZIO" /><category term="Non-Blocking/Concurrency" /><category term="gRPC" /><summary type="html"><![CDATA[Realtime push-based databases such as Google Firebase conveniently ensure clients are synchronized with the server. Data updates stream to clients immediately as they happen; and if a client disconnects, updates are immediately processed after reconnecting. gRPC server streaming and ZIO Hub can implement this functionality replicating an expensive paid Firebase service while allowing greater extensibility.]]></summary></entry><entry><title type="html">Http4s Streams and Multipart Form-Data File Uploads</title><link href="https://www.stevenskelton.ca/http4s-streams-multipart-formdata-uploads/" rel="alternate" type="text/html" title="Http4s Streams and Multipart Form-Data File Uploads" /><published>2024-02-27T00:00:00-05:00</published><updated>2024-02-27T00:00:00-05:00</updated><id>https://www.stevenskelton.ca/http4s-streams-multipart-formdata-uploads</id><content type="html" xml:base="https://www.stevenskelton.ca/http4s-streams-multipart-formdata-uploads/"><![CDATA[<p>Streaming is the primary mechanism to reduce memory requirements for processing large datasets. The approach is to 
view only a small window of data at a time, allowing data to stream through in manageable amounts matching the 
data window size to the amount of RAM available. A practical example is a file-upload, where multi-GBs file streams 
can be handled by MBs of server RAM. However, enforcing streaming in software code is prone to errors, and misuse or
incompatible method implementations will lead to breaking stream semantics, and ultimately to OOM exceptions. This 
article focuses on streams within the context of file uploads, using the Http4s library for examples.<!--more--></p>

<style>
    .multiplex_responsive_1 { width: 360px; height: 100px; margin-top: 1em; margin-bottom: 1em; }
    @media(min-width: 720px) { .multiplex_responsive_1 { width: 360px; margin: 0; height: 600px; } }
</style>

<div class="toc-container">
    <aside class="sidebar__right">
<nav class="toc">
      <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> Table of Contents</h4></header>
<ul class="toc__menu" id="markdown-toc">
  <li><a href="#collections-versus-streams" id="markdown-toc-collections-versus-streams">Collections versus Streams</a></li>
  <li><a href="#http-file-transfers" id="markdown-toc-http-file-transfers">HTTP File Transfers</a>    <ul>
      <li><a href="#intermediate-nodes-and-proxies" id="markdown-toc-intermediate-nodes-and-proxies">Intermediate Nodes and Proxies</a>        <ul>
          <li><a href="#file-uploads-without-streams" id="markdown-toc-file-uploads-without-streams">File Uploads without Streams</a></li>
          <li><a href="#backpressure" id="markdown-toc-backpressure">Backpressure</a></li>
        </ul>
      </li>
      <li><a href="#strategies-for-http-file-transfers" id="markdown-toc-strategies-for-http-file-transfers">Strategies for HTTP File Transfers</a>        <ul>
          <li><a href="#large-file-transfers" id="markdown-toc-large-file-transfers">Large File Transfers</a>            <ul>
              <li><a href="#content-range-requests" id="markdown-toc-content-range-requests">Content-Range Requests</a></li>
              <li><a href="#alternative-sdk-approaches" id="markdown-toc-alternative-sdk-approaches">Alternative SDK Approaches</a></li>
            </ul>
          </li>
          <li><a href="#http-multipart-requests" id="markdown-toc-http-multipart-requests">HTTP Multipart Requests</a>            <ul>
              <li><a href="#real-world-examples-amazon-alexa-api" id="markdown-toc-real-world-examples-amazon-alexa-api">Real-World Examples: Amazon Alexa API</a></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#http4s-entitydecoder" id="markdown-toc-http4s-entitydecoder">Http4s EntityDecoder</a></li>
  <li><a href="#streaming-multipart-file-uploads-with-http4s" id="markdown-toc-streaming-multipart-file-uploads-with-http4s">Streaming Multipart File Uploads with Http4s</a>    <ul>
      <li><a href="#problems-converting-a-stream-to-stream-of-streams" id="markdown-toc-problems-converting-a-stream-to-stream-of-streams">Problems converting a Stream to Stream-of-Streams</a></li>
      <li><a href="#testing-streams-memory-use" id="markdown-toc-testing-streams-memory-use">Testing Streams: Memory Use</a></li>
      <li><a href="#implementation" id="markdown-toc-implementation">Implementation</a></li>
    </ul>
  </li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

    </nav>
</aside>
    <div class="toc-ad"><ins class="adsbygoogle multiplex_responsive_1" style="display:block" data-ad-client="ca-pub-5000348308466945" data-ad-slot="5418078712"></ins>
        <script>
            (adsbygoogle = window.adsbygoogle || []).push({});
        </script></div>
</div>

<h1 id="collections-versus-streams">Collections versus Streams</h1>

<p>Pragmatic programming moved complexity from projects to external libraries and frameworks. This allowed even small
improvements in libraries to have over-sized benefit scaling proportionally to their popularity. However, this has the
unintended consequence of developers never learning underlying concepts, both limiting their work within project
implementations directly and through misusing or under-using available libraries. From personal observation, streams
falls into this category, their performance benefits often passed-over until they are absolutely required by software
to function correctly.</p>

<h1 id="http-file-transfers">HTTP File Transfers</h1>

<p>It is inappropriate to write ETLs assuming the entire dataset will fit into RAM. The OS will attempt to manage if given
enough virtual memory, but it will be blind to program implementation details and unable to perform optimally. Beyond
this, by not streaming data directly from source to destination the additional steps can inefficiently be required to
copy data multiple times to different locations, noticeably decreasing throughput based on available I/O.</p>

<h2 id="intermediate-nodes-and-proxies">Intermediate Nodes and Proxies</h2>

<p>When data is transferred from source to destination, any proxies buffering substantial amounts of data can cause
performance degradation if using the wrong implementation. Proxies should use streams when possible, unless the
buffering is serving an explicit purpose, such as increasing data durability, smoothing irregular network flow or
providing batch/windowing.</p>

<h3 id="file-uploads-without-streams">File Uploads without Streams</h3>

<p>A common scenario where streaming is required, rather than optional, is handling file uploads. An typical example
encountered is to fully buffer a file upload within a proxy. In the following example, the user is allowed to upload
a file within the Salesforce UI, with the destination of the file to be stored in SharePoint.</p>

<figure class="">
  <img src="/assets/images/2024/02/salesforce-upload.svg" alt="File Upload from Salesforce to SharePoint" class="fighascaption" style="padding: 0 20px 10px 20px; background-color: white; height: 250px;" /><figcaption>
      File Upload from Salesforce to SharePoint

    </figcaption></figure>

<p>There are 3 different approaches to this:</p>

<ul>
  <li>File is uploaded to Salesforce, then copied to SharePoint</li>
  <li>File is buffered in Salesforce memory, then copied to SharePoint</li>
  <li>File streams to SharePoint, either directly or proxies through Salesforce servers</li>
</ul>

<p>The first 2 are without streaming and suffer from transfers <code class="language-plaintext highlighter-rouge">A</code> and <code class="language-plaintext highlighter-rouge">B</code> being sequential rather than concurrent. The
user must wait for <code class="language-plaintext highlighter-rouge">B</code> after <code class="language-plaintext highlighter-rouge">A</code> has completed. This also allows new modes of failure: what happens when <code class="language-plaintext highlighter-rouge">B</code> fails,
will <code class="language-plaintext highlighter-rouge">A</code> have to be repeated? Will the user know <code class="language-plaintext highlighter-rouge">B</code> will fail before starting <code class="language-plaintext highlighter-rouge">A</code>?</p>

<p>Not only is the streaming solution faster, it has fewer modes of failure.</p>

<h3 id="backpressure">Backpressure</h3>

<p>The idea of backpressure is that consumers can dictate throughput to the producer. The consumer essentially applies
pressure backwards against the path of flow towards the producer. Streams have different implementations and not all
streams allow for backpressure. Naive buffering can prevent backpressure signals from traveling back towards the source.
But when buffering is limited or non-existent, if the consumer is slower than the producer then there is nowhere for the
data to build up. For file transfers, the backpressure solution is obvious such that the producer isn’t allowed to
upload faster than the consumer can process.</p>

<p>This is ideal for file transfers because we want the outcome of the consumer to be relayed to the producer. If the file
upload to SharePoint <code class="language-plaintext highlighter-rouge">B</code> doesn’t complete, we don’t benefit from <code class="language-plaintext highlighter-rouge">A</code> completing successfully.</p>

<h2 id="strategies-for-http-file-transfers">Strategies for HTTP File Transfers</h2>

<h3 id="large-file-transfers">Large File Transfers</h3>

<p>Large file transfers have additional concerns over smaller files:</p>

<ul>
  <li>Ability to track progress,</li>
  <li>Ability to pause/resume over difference sessions,</li>
  <li>Ability to parallelize,</li>
  <li>Ability to support unknown or unlimited final file size</li>
</ul>

<h4 id="content-range-requests">Content-Range Requests</h4>

<p>The simplest strategy is to stream file contents within a single HTTP request body. A <em>chunked transfer
encoding</em> can be specifying using a <code class="language-plaintext highlighter-rouge">Transfer-Encoding: chunked</code> header allowing unknown or unlimited request size,
however the request has limited recoverability since there is no mechanism to pause, resume or parallelize for
performance. The <code class="language-plaintext highlighter-rouge">Content-Range</code> header is a common approach to expand into these features by replacing a single large
request with multiple smaller requests. Each request will specify a byte range to be transmitted, allowing the requests
to be parallelized, retried, and monitored.</p>

<h4 id="alternative-sdk-approaches">Alternative SDK Approaches</h4>

<p>While the <code class="language-plaintext highlighter-rouge">Content-Range</code> approach is a common standard built into all browsers, alternatives exist such as within the
<a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html">AWS SDK</a>. The AWS SDK builds out additional file
features:</p>

<ul>
  <li>Ability to list complete/incomplete segments</li>
  <li>Management of in-progress transfers</li>
  <li>Support a file lifecycle</li>
</ul>

<p>AWS has chosen not to use content-range headers, instead utilizing a custom part identification scheme. Each request
will include the part identifier within the URL query parameters instead of within a header.</p>

<p>Not covered within the content-range approach is how to manage in-progress transfers. Are each range tied to a specific
HTTP session? Can they be deleted? How can we list incomplete or range failures? By exposing additional URLs to list
transfers in progress, list transfer part statuses, and manage/delete transfers, AWS has both increased the durability
of the file transfer process and removed state management complexity from client agents.</p>

<p>The final feature of a file lifecycle exists to mark transfers as complete, such that they can be transferred from
temporary locations used during the upload to final destinations. This is important because of the granularity of
destinations, some being referred to as cold or offline storage, which would normally not be directly accessible to
clients.</p>

<h3 id="http-multipart-requests">HTTP Multipart Requests</h3>

<p>A variation to a singular HTTP request body is the multipart request. It is indicated by way of
a <code class="language-plaintext highlighter-rouge">Content-Type: multipart/form-data</code> header. As can be inferred from the form-data name, the primary use-case was the
expansion of the HTTP specification to directly support form data transfers without dependency on external encoding
schema such as JSON.</p>

<p>The multipart mechanism separates the request body into separate parts, each with its own headers and body. Each of the
parts is free to specify their <em>Content-Type</em> thereby allowing each part to be a binary file transfer.</p>

<figure class="">
  <img src="/assets/images/2024/02/multipart-request.svg" alt="Multipart HTTP Request with 2 parts" class="fighascaption" style="padding: 10px; background-color: white; height: 600px;" /><figcaption>
      Multipart HTTP Request with 2 parts

    </figcaption></figure>

<p>The intended use of a multipart upload is to handle multiple, small data fields, typically being user text input.
From a high level, the HTTP request is broken up into parts delimited by an arbitrary <code class="language-plaintext highlighter-rouge">boundary</code> sequence specified
within the <code class="language-plaintext highlighter-rouge">Content-Type</code> header.</p>

<p>There are multiple reasons this is unsuited to transferring large files:</p>

<ul>
  <li>Content needs to be inspected for <code class="language-plaintext highlighter-rouge">boundary</code> occurrences,</li>
  <li>Unable to know name, count, or content-type of parts without parsing previous parts,</li>
  <li>No additional features supporting large files beyond those of the single request format.</li>
</ul>

<p>The multipart form-data request should be considered a strictly worse version of putting the file content within the
request body. The cost to support multiple files within the request introduces the overhead of the data comparison
against the boundary. While <a href="https://en.wikipedia.org/wiki/String-searching_algorithm">algorithms</a> exist to minimize 
overhead, it should generally be considered to be <em>O(n)</em> where <em>n</em> is the file content length. Expansions of HTTP with
HTTP/2 and HTTP/3 have removed request overhead so there is reason to use form-data to transfer anything but trivially
small files.</p>

<h4 id="real-world-examples-amazon-alexa-api">Real-World Examples: Amazon Alexa API</h4>

<p>The <a href="https://developer.amazon.com/en-US/docs/alexa/alexa-voice-service/structure-http2-request.html">Amazon Alexa</a> 
device makes use of multipart form-data requests to communicate with the Alexa Voice Service (AVS) servers. Every 
request includes JSON metadata and binary audio data as separate parts. There is an inherent incompatibility between
JSON and binary data: there are no efficient ways to embed binary data into the text format, and embedding JSON into
binary data requires an additional encoding mechanism.</p>

<p>The binary encoding selected was multipart form-data, which would reduce complexity by being a well-supported part of 
HTTP and requiring no additional dependencies. While more efficient, binary encodings such as gRPC/protobuf, Thrift, 
and JSONB are often overlooked because of the need to add library dependencies for parsing, inspection, and debugging
of the over-the-wire data.</p>

<p>As mentioned earlier, the <code class="language-plaintext highlighter-rouge">boundary</code> mechanic creates a CPU bottleneck working against higher throughput. It should be 
noted that this inefficient processing can be omitted when the multipart format is of a particular type. When the only
binary part is at the end of the request, there would be no need to inspect the binary data for boundaries. With this
relaxation of the format, the multipart form-data encoding is just as performant as the binary encodings mentioned 
while retaining the human-readable over-the-write representation of the JSON part.</p>

<h1 id="http4s-entitydecoder">Http4s EntityDecoder</h1>

<p>The <code class="language-plaintext highlighter-rouge">org.http4s.EntityDecoder</code> trait includes the documentation:</p>

<blockquote>
  <p>A type that can be used to decode a Message EntityDecoder is used to attempt to decode a Message returning the entire
resulting A. If an error occurs it will result in a failed effect. The default decoders provided here are not
streaming, but one could implement a streaming decoder by having the value of A be some kind of streaming construct.</p>
</blockquote>

<p>It is also readily apparent from the <code class="language-plaintext highlighter-rouge">org.http4s.multipart.Multipart</code> implementation:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">final</span> <span class="k">case</span> <span class="k">class</span> <span class="nc">Multipart</span><span class="o">[</span><span class="kt">+F</span><span class="o">[</span><span class="k">_</span><span class="o">]](</span>
       <span class="n">parts</span><span class="k">:</span> <span class="kt">Vector</span><span class="o">[</span><span class="kt">Part</span><span class="o">[</span><span class="kt">F</span><span class="o">]],</span>
       <span class="n">boundary</span><span class="k">:</span> <span class="kt">Boundary</span><span class="o">,</span>
     <span class="o">)</span>
</code></pre></div></div>

<p>As mentioned earlier, multipart form-data requests are not a good mechanism to handle large file uploads. There has 
been no effort to optimize the streaming capabilities of the Http4s Multipart handler, opting to represent the parts
as an immutable <code class="language-plaintext highlighter-rouge">Vector</code>. To construct this instance, the entire request will need to be parsed, negating any request
streaming ability.</p>

<p>It is key to note that Out-Of-Memory issues are circumvented by way of temporary files, the implementation of this
decoder has chosen to create and manage filesystem objects as a way to offload large requests from system RAM. Http4s 
implicits can be explicitly created to modify default values for this mechanism, by creating a decoder using the 
<code class="language-plaintext highlighter-rouge">MultipartDecoder</code> static method <code class="language-plaintext highlighter-rouge">mixedMultipartResource</code>:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">mixedMultipartResource</span><span class="o">[</span><span class="kt">F</span><span class="o">[</span><span class="k">_</span><span class="o">]</span> <span class="kt">:</span> <span class="kt">Concurrent</span> <span class="kt">:</span> <span class="kt">Files</span><span class="o">](</span>
       <span class="n">headerLimit</span><span class="k">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">1024</span><span class="o">,</span>
       <span class="n">maxSizeBeforeWrite</span><span class="k">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">52428800</span><span class="o">,</span>
       <span class="n">maxParts</span><span class="k">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">50</span><span class="o">,</span>
       <span class="n">failOnLimit</span><span class="k">:</span> <span class="kt">Boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="o">,</span>
       <span class="n">chunkSize</span><span class="k">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">8192</span><span class="o">,</span>
     <span class="o">)</span><span class="k">:</span> <span class="kt">Resource</span><span class="o">[</span><span class="kt">F</span>, <span class="kt">EntityDecoder</span><span class="o">[</span><span class="kt">F</span>, <span class="kt">Multipart</span><span class="o">[</span><span class="kt">F</span><span class="o">]]]</span>
</code></pre></div></div>

<h1 id="streaming-multipart-file-uploads-with-http4s">Streaming Multipart File Uploads with Http4s</h1>

<p>There is no streaming issues when directly using the <code class="language-plaintext highlighter-rouge">request.body</code> exposed by Http4s. It is a <code class="language-plaintext highlighter-rouge">Stream[F, Byte]</code>, the 
issues mentioned in this post are concerning the included body decoders breaking streaming semantics. This shouldn’t
be seen as criticism or oversight, as the direct parsing of the body is a cleaner, more direct and the recommended 
approach to handle streaming request data. Instead of the inefficient use of multipart form-data, the same effect is
better achieved by moving all text-based form-data to HTTP headers. But for the stubborn and niche use-cases such as
with Alexa above, can the Http4s multipart decoder be implemented to support streaming?</p>

<h2 id="problems-converting-a-stream-to-stream-of-streams">Problems converting a Stream to Stream-of-Streams</h2>

<p>An HTTP request body should be viewed as a single stream.</p>

<p>//TODO: finish</p>

<p>a single stream, how can a multipart body be represented using a stream? Conceptually it maps
to a <code class="language-plaintext highlighter-rouge">Stream[Part]</code> since parts will need to be accessed sequentially. But pragmatically, each part could represent
a large file which would also need to be a stream. A single <code class="language-plaintext highlighter-rouge">Stream</code> cannot map to a <code class="language-plaintext highlighter-rouge">Stream[Stream[_]]</code> since this
wouldn’t allow each part, which can represent a large file, ecause we need to , but then what is a <code class="language-plaintext highlighter-rouge">Part</code>? It cannot be a s
to be as efficient as possible?  multipart request streaming be
properly implemented? With HTTP/2 multiplexing, is there even a plausible use-case to send multiple fields in a single
request?</p>

<p>//TODO: implementation details for streaming Multipart decoder</p>

<h2 id="testing-streams-memory-use">Testing Streams: Memory Use</h2>

<p>Streams are programming abstractions which are difficult to test directly, as their output is identical to their 
collection counterparts. It is insufficient to test the final output is being streamed, since any intermediate 
transformation could have easily buffered results only to stream them again. The absolute test would be to measure 
memory use of the system, as any buffering will have a measurable effect on heap use. For the purpose of our testing, 
restricting the JVM heap size to be smaller than the stream data would indicate no intermediate buffering.
(This cannot detect buffering to the filesystem, which would require additional code inspection).</p>

<p>Running the JVM with a 128Mb memory allocation can use the <code class="language-plaintext highlighter-rouge">Xmx</code> parameter:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java <span class="nt">-Xmx128m</span> <span class="nt">-jar</span> build-action-file-receiver-assembly-1.0.25.jar 
</code></pre></div></div>

<h2 id="implementation">Implementation</h2>

<p>//TODO: Scala implementation</p>

<h1 id="conclusion">Conclusion</h1>

<p>//TODO: conclusion</p>]]></content><author><name>Steven Skelton</name></author><category term="Scala" /><category term="Typelevel/Cats" /><category term="Non-Blocking/Concurrency" /><summary type="html"><![CDATA[Streaming is the primary mechanism to reduce memory requirements for processing large datasets. The approach is to view only a small window of data at a time, allowing data to stream through in manageable amounts matching the data window size to the amount of RAM available. A practical example is a file-upload, where multi-GBs file streams can be handled by MBs of server RAM. However, enforcing streaming in software code is prone to errors, and misuse or incompatible method implementations will lead to breaking stream semantics, and ultimately to OOM exceptions. This article focuses on streams within the context of file uploads, using the Http4s library for examples.]]></summary></entry><entry><title type="html">Compiling Scala Native in a GitHub Action; Alternatives to GraalVM</title><link href="https://www.stevenskelton.ca/compiling-scala-native-github-actions-alternative-to-graalvm/" rel="alternate" type="text/html" title="Compiling Scala Native in a GitHub Action; Alternatives to GraalVM" /><published>2024-02-06T00:00:00-05:00</published><updated>2024-02-06T00:00:00-05:00</updated><id>https://www.stevenskelton.ca/compiling-scala-native-github-actions-alternative-to-graalvm</id><content type="html" xml:base="https://www.stevenskelton.ca/compiling-scala-native-github-actions-alternative-to-graalvm/"><![CDATA[<p>Scala Native is a compiler and JDK written in Scala with the goal of removing Scala’s dependency on the JVM. This isn’t
meant to achieve a higher performance such as with JDKs, and it is targeting a specialized use-case not considered to be
today’s typical Scala development. Its competitors are Rust and Go, not GraalVM, Java or Kotlin. This article goes
through common steps and challenges encountered when compiling Scala Native for linux with a GitHub Action.<!--more--></p>

<p>After 7 years <a href="https://scala-native.org/">Scala Native</a> is at pre-production
maturity <a href="https://scala-native.org/en/stable/changelog/0.4.17.html">version 0.4.17</a>.</p>

<style>
    .multiplex_responsive_1 { width: 360px; height: 100px; margin-top: 1em; margin-bottom: 1em; }
    @media(min-width: 720px) { .multiplex_responsive_1 { width: 360px; margin: 0; height: 900px; } }
</style>

<div class="toc-container">
    <aside class="sidebar__right">
<nav class="toc">
      <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> Table of Contents</h4></header>
<ul class="toc__menu" id="markdown-toc">
  <li><a href="#different-versions-of-openjdk" id="markdown-toc-different-versions-of-openjdk">Different versions of OpenJDK</a>    <ul>
      <li><a href="#eclipse-openj9" id="markdown-toc-eclipse-openj9">Eclipse OpenJ9</a></li>
      <li><a href="#graalvm" id="markdown-toc-graalvm">GraalVM</a>        <ul>
          <li><a href="#quarkus-and-optimized-libraries-written-without-jit-requirements" id="markdown-toc-quarkus-and-optimized-libraries-written-without-jit-requirements">Quarkus and optimized libraries written without JIT requirements</a></li>
          <li><a href="#reflection-metadata-collection-and-code-hints" id="markdown-toc-reflection-metadata-collection-and-code-hints">Reflection Metadata Collection and Code Hints</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#motivations-for-scala-native" id="markdown-toc-motivations-for-scala-native">Motivations for Scala Native</a>    <ul>
      <li><a href="#scalajs-the-scala-to-javascript-transcompiler" id="markdown-toc-scalajs-the-scala-to-javascript-transcompiler">ScalaJS, the Scala to JavaScript Transcompiler</a></li>
      <li><a href="#executables-and-jars" id="markdown-toc-executables-and-jars">Executables and Jars</a></li>
      <li><a href="#cli-tools-and-jni-heavy-applications" id="markdown-toc-cli-tools-and-jni-heavy-applications">CLI Tools and JNI-heavy applications</a></li>
    </ul>
  </li>
  <li><a href="#compiling-scala-native-in-a-github-action" id="markdown-toc-compiling-scala-native-in-a-github-action">Compiling Scala Native in a GitHub Action</a>    <ul>
      <li><a href="#github-action-runners" id="markdown-toc-github-action-runners">GitHub Action Runners</a></li>
      <li><a href="#compiling-additional-c-libraries-with-cmake" id="markdown-toc-compiling-additional-c-libraries-with-cmake">Compiling additional C libraries with CMake</a></li>
      <li><a href="#linking-c-library-paths-in-sbt" id="markdown-toc-linking-c-library-paths-in-sbt">Linking C library paths in SBT</a></li>
      <li><a href="#scala-native-library-dependencies-in-sbt" id="markdown-toc-scala-native-library-dependencies-in-sbt">Scala Native library dependencies in SBT</a></li>
      <li><a href="#code-changes-required-to-support-scala-native" id="markdown-toc-code-changes-required-to-support-scala-native">Code Changes Required to Support Scala Native</a>        <ul>
          <li><a href="#cross-compiling" id="markdown-toc-cross-compiling">Cross Compiling</a></li>
          <li><a href="#partial-jvm-implementations" id="markdown-toc-partial-jvm-implementations">Partial JVM Implementations</a>            <ul>
              <li><a href="#multithreading-and-runtime-environment" id="markdown-toc-multithreading-and-runtime-environment">Multithreading and Runtime Environment</a></li>
              <li><a href="#cryptography-and-javasecurity-package" id="markdown-toc-cryptography-and-javasecurity-package">Cryptography and <code class="language-plaintext highlighter-rouge">java.security</code> package</a></li>
            </ul>
          </li>
          <li><a href="#scala-xml" id="markdown-toc-scala-xml">Scala-XML</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#runtime-observations" id="markdown-toc-runtime-observations">Runtime Observations</a>    <ul>
      <li><a href="#performance-and-memory" id="markdown-toc-performance-and-memory">Performance and Memory</a></li>
      <li><a href="#unit-tests" id="markdown-toc-unit-tests">Unit Tests</a></li>
      <li><a href="#debugging" id="markdown-toc-debugging">Debugging</a></li>
    </ul>
  </li>
  <li><a href="#conclusions" id="markdown-toc-conclusions">Conclusions</a>    <ul>
      <li><a href="#pros" id="markdown-toc-pros">Pros</a></li>
      <li><a href="#cons" id="markdown-toc-cons">Cons</a></li>
    </ul>
  </li>
</ul>

    </nav>
</aside>
    <div class="toc-ad"><ins class="adsbygoogle multiplex_responsive_1" style="display:block" data-ad-client="ca-pub-5000348308466945" data-ad-slot="5418078712"></ins>
        <script>
            (adsbygoogle = window.adsbygoogle || []).push({});
        </script></div>
</div>

<h1 id="different-versions-of-openjdk">Different versions of OpenJDK</h1>

<p>To avoid confusion between Scala Native, and Scala compiled natively it’s important to be clear on the goals of
alternative OpenJDK implementations. Initially Oracle released Java and Java’s JDK/JRE but licencing has changed
causing open source, free to use, alternatives to emerge that are compatible with the Java SE Specification.</p>

<p>From a high level, <a href="https://whichjdk.com/">whichjdk.com</a> highlights the JDK products supported by various cloud
computing entities; AWS, Azure, IBM, SAP, and RedHat all have JDKs. There are minor differences such as in their
Garbage Collector tuning, but for the most part they are unremarkable in their similarity.</p>

<h2 id="eclipse-openj9">Eclipse OpenJ9</h2>

<p><a href="https://eclipse.dev/openj9/">Eclipse OpenJ9</a> is a cloud optimized JDK which rethinks OpenJDK implementation while
offering full compatibility. With focus on JVM startup times and memory management, the notable new features include:</p>

<ul>
  <li><a href="https://blog.openj9.org/2022/10/14/openj9-criu-support-a-look-under-the-hood/">Checkpoint/Restore in Userspace (CRUI)</a>,</li>
  <li><a href="https://eclipse.dev/openj9/docs/shrc/">Class Data Sharing</a>,</li>
  <li>partial Ahead-of-Time (AOT) compilation, and</li>
  <li>aggressive GC tuning.</li>
</ul>

<h2 id="graalvm">GraalVM</h2>

<p>The goal of <a href="https://www.graalvm.org/">GraalVM</a> is of Java using strictly Ahead-of-Time (AOT) compilation, rather than
using Java’s <a href="https://en.wikipedia.org/wiki/Just-in-time_compilation">Just-in-Time Compilation</a>. This is a breaking
change but allows compilation to a native executable rather than Java’s class bytecode.</p>

<p>Scala has 2 SBT plugins with GraalVM support:</p>

<ul>
  <li><a href="https://github.com/scalameta/sbt-native-image">SBT Native Image</a></li>
  <li><a href="https://github.com/sbt/sbt-native-packager">SBT Native Packager</a></li>
</ul>

<p>The benefits of GraalVM native executable is removal of the JVM initialization time and the removal of any unreachable
code, resulting in smaller packages which boot quickly. Unfortunately the largest downside to the removal of JIT is
removal of any runtime performance optimization based on use. The AOT is able to optimize but without any visibility
into common paths and hotspots it is from a strictly static perspective. In addition, the optimizations come from
Java bytecode which has already removed some key ingredients for full static analysis. In real-world benchmarks,
GraalVM performance typically lags behind JIT competitors for long-running tasks.</p>

<p>Another breaking change in GraalVM use is the inability to use Java reflection and runtime code generation. This is
more common in Java than in languages such as Scala, but any program can be affected. There are 2 approaches to work
around this limitation:</p>

<h3 id="quarkus-and-optimized-libraries-written-without-jit-requirements">Quarkus and optimized libraries written without JIT requirements</h3>

<p>The popular Kubernetes focused framework <a href="https://quarkus.io/">Quarkus</a> avoids JIT with specialized versions of standard
libraries. The changes necessary can be minor, allowing for a well-supported ecosystem of popular libraries available
as extensions to Quarkus, without notable lag to official library releases. However, support for any library not
available as an extension will rely on the second JIT alternative.</p>

<h3 id="reflection-metadata-collection-and-code-hints">Reflection Metadata Collection and Code Hints</h3>

<p>The GraalVM compiler can perform static analysis of code to learn about certain types of JIT and reflection, but runtime
observation with the GraalVM Tracing Agent is required. Code needs to be run under all execution paths, and the Tracing
Agent will record all usages of the Java Native Interface (JNI), Java Reflection, Dynamic Proxy objects, or class path
resources. These will then be supplied as a JSON document to the AOT on the next compilation. The obvious problem is
triggering all executable code paths during the debugging cycle, making this (theoretically) a trial and error process.</p>

<h1 id="motivations-for-scala-native">Motivations for Scala Native</h1>

<h2 id="scalajs-the-scala-to-javascript-transcompiler">ScalaJS, the Scala to JavaScript Transcompiler</h2>

<p>The focus of Scala Native isn’t explicitly native execution, it is in the removal of the restrictive JDK dependency.
Scala Native has 2 supported outputs:</p>

<ul>
  <li><a href="https://www.scala-js.org/">ScalaJS</a> JavaScript, and</li>
  <li><a href="https://llvm.org">LLVM</a> executables.</li>
</ul>

<p>ScalaJS can realistically only transcompile to JavaScript if it can remove any unused overhead imposed by the JDK.
Typical JavaScript use cases are web oriented, where code sizes dictate latency and observed performance.</p>

<p>The JDK and Java bytecode are opinionated in their interactions with other native code. The
<a href="https://en.wikipedia.org/wiki/Java_Native_Interface">Java Native Interface (JNI)</a> is an unnecessary and in often a
performance restricting abstraction that is only necessary to bridge between the JRE and external native code. The
LLVM linking for native-native interactions offers low level primitives such as pointers and unsafe memory access that
can be necessary to achieve top performance. Scala Native includes additional Scala support for the low-level C/C++
primitives directly which cannot be represented in Java.</p>

<h2 id="executables-and-jars">Executables and Jars</h2>

<p>Java can be run directly in the JRE using <code class="language-plaintext highlighter-rouge">java -jar</code>. It is essentially just as easy to run a Jar as a native
executable (assuming a JRE is installed). As noted previously, AOT executables may initialize quicker but often bested
by JIT on long-running performance. This leaves short-lived container applications such as: AWS Lambda, serverless
functions, and Kubernetes the primary target for GraalVM, with standard JDK or OpenJ9 remaining the best choice for
server instance deployments or longer running serverless. So where exactly does Scala Native fit in?</p>

<h2 id="cli-tools-and-jni-heavy-applications">CLI Tools and JNI-heavy applications</h2>

<p>The benefit of a Scala Native LLVM executable is it is just another C/C++ program. In the same way
<a href="https://www.rust-lang.org/">Rust</a>, <a href="https://ziglang.org/">Zig</a> and <a href="https://go.dev/">Go</a> have emerged as replacements
to C/C++, Scala Native offers the same compatibility and performance with a familiar and powerful Scala language. It is
the native compatibility and low-level features brought to Scala via Scala Native that make Scala Native a compelling
choice with applications which require low-level management or direct hardware interactions.</p>

<h1 id="compiling-scala-native-in-a-github-action">Compiling Scala Native in a GitHub Action</h1>

<p>As outlined previously, the use-case of Scala Native is typically a low-level CLI application. This remainder of this
article deviates from this by working through a Scala application which will not benefit from Scala Native.</p>

<p><strong>This is not a recommendation by the author to apply Scala Native to a similar project, merely a discover exercise</strong></p>

<h2 id="github-action-runners">GitHub Action Runners</h2>

<p>The linux server configurations outlining OS and installed software is
<a href="https://github.com/actions/runner-images">available</a>, and includes suitable CLang and LLVM versions. For local
development the Scala Native
<a href="https://scala-native.org/en/stable/user/setup.html#installing-clang-and-runtime-dependencies">install guide covers</a> for
macOS (using <a href="https://brew.sh/">brew</a>) and linux using apt.</p>

<h2 id="compiling-additional-c-libraries-with-cmake">Compiling additional C libraries with CMake</h2>

<p>It is very likely that a Scala Native will have a library dependency not installed on the GitHub Runner. In our
application the <a href="https://fs2.io/">FS2</a> functional library requires the Amazon
<a href="https://github.com/aws/s2n-tls">AWS TLS/SSL library S2N</a>. Security and cryptography libraries are likely to be native
dependencies both because they likely already exist as high-performance native implementations, and secondly because
Scala Native JDK may omit them due to <a href="#cryptography-and-javasecurity-package">security concerns around implementation</a>.</p>

<p>Compiling a C application with CMake is a straight-forward addition to the GitHub Action YML:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Compile and Install AWS S2N-TLS</span>
  <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s"># clone s2n-tls</span>
    <span class="s">git clone --depth 1 https://github.com/aws/s2n-tls.git</span>
    <span class="s">cd s2n-tls</span>
    <span class="s"># build s2n-tls</span>
    <span class="s">cmake . -Bbuild \</span>
        <span class="s">-DCMAKE_BUILD_TYPE=Release \</span>
        <span class="s">-DCMAKE_INSTALL_PREFIX=./s2n-tls-install</span>
    <span class="s">cmake --build build -j $(nproc)</span>
    <span class="s">CTEST_PARALLEL_LEVEL=$(nproc) ctest --test-dir build</span>
    <span class="s">cmake --install build</span>
</code></pre></div></div>

<h2 id="linking-c-library-paths-in-sbt">Linking C library paths in SBT</h2>

<p>When linking to a GitHub Action compiled C libraries such as S2N, the library needs to be in a path scanned by LD.
Because LD is being called by CMake and not GCC, typical environmental variables such as <code class="language-plaintext highlighter-rouge">LD_LIBRARY_PATH</code> and
<code class="language-plaintext highlighter-rouge">LIBRARY_PATH</code> do not work.</p>

<p>TODO: <code class="language-plaintext highlighter-rouge">CMAKE_EXE_LINKER_FLAGS</code> <code class="language-plaintext highlighter-rouge">LDFLAGS</code></p>

<p>The easiest option is to include the path in <code class="language-plaintext highlighter-rouge">build.sbt</code> though this path will differ between local development
and within the GitHub Action.</p>

<p>For example, within the <code class="language-plaintext highlighter-rouge">build-action-file-receiver</code> project on GitHub, the runner installed S2N in the following path:</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">nativeLinkingOptions</span> <span class="o">+=</span> <span class="n">s</span><span class="s">"-L/home/runner/work/build-action-file-receiver/build-action-file-receiver/s2n-tls/s2n-tls-install/lib"</span>
</code></pre></div></div>

<h2 id="scala-native-library-dependencies-in-sbt">Scala Native library dependencies in SBT</h2>

<p>The <code class="language-plaintext highlighter-rouge">build-action-file-receiver</code> is a typical Scala web server application, but with compatibility to run Scala Native making
it suitable for this article. Scala Native compatibility is dictated by the availability of Scala Native versions for
all library dependencies. These are exist as separate artifacts:</p>

<ul>
  <li>https://repo1.maven.org/maven2/co/fs2/fs2-core_3/3.9.4/fs2-core_3-3.9.4.jar (1.5MB)</li>
  <li>https://repo1.maven.org/maven2/co/fs2/fs2-core_native0.4_3/3.9.4/fs2-core_native0.4_3-3.9.4.jar (3.7MB)</li>
</ul>

<p>This is indicated in <code class="language-plaintext highlighter-rouge">build.sbt</code> by using the <code class="language-plaintext highlighter-rouge">%%%</code> operator instead of <code class="language-plaintext highlighter-rouge">%</code> / <code class="language-plaintext highlighter-rouge">%%</code>:</p>

<p><em>Typical Scala:</em></p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">libraryDependencies</span> <span class="o">++=</span> <span class="nc">Seq</span><span class="o">(</span>
  <span class="s">"org.http4s"</span> <span class="o">%%</span> <span class="s">"http4s-ember-client"</span> <span class="o">%</span> <span class="n">http4sVersion</span><span class="o">,</span>
  <span class="s">"org.http4s"</span> <span class="o">%%</span> <span class="s">"http4s-ember-server"</span> <span class="o">%</span> <span class="n">http4sVersion</span><span class="o">,</span>
  <span class="s">"org.http4s"</span> <span class="o">%%</span> <span class="s">"http4s-dsl"</span> <span class="o">%</span> <span class="n">http4sVersion</span><span class="o">,</span>
  <span class="s">"org.typelevel"</span> <span class="o">%%</span> <span class="s">"log4cats-core"</span> <span class="o">%</span> <span class="s">"2.6.0"</span><span class="o">,</span>
  <span class="s">"co.fs2"</span> <span class="o">%%</span> <span class="s">"fs2-io"</span> <span class="o">%</span> <span class="s">"3.9.4"</span><span class="o">,</span>
  <span class="s">"org.scala-lang.modules"</span> <span class="o">%%</span> <span class="s">"scala-xml"</span> <span class="o">%</span> <span class="s">"2.2.0"</span><span class="o">,</span>
  <span class="s">"org.scalatest"</span> <span class="o">%%</span> <span class="s">"scalatest"</span> <span class="o">%</span> <span class="s">"3.3.0-alpha.1"</span> <span class="o">%</span> <span class="nc">Test</span><span class="o">,</span>
  <span class="s">"org.typelevel"</span> <span class="o">%%</span> <span class="s">"cats-effect-testing-scalatest"</span> <span class="o">%</span> <span class="s">"1.5.0"</span> <span class="o">%</span> <span class="nc">Test</span><span class="o">,</span>
<span class="o">)</span>
</code></pre></div></div>

<p><em>Scala Native:</em></p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">libraryDependencies</span> <span class="o">++=</span> <span class="nc">Seq</span><span class="o">(</span>
  <span class="s">"com.armanbilge"</span> <span class="o">%%%</span> <span class="s">"epollcat"</span> <span class="o">%</span> <span class="s">"0.1.4"</span><span class="o">,</span>
  <span class="s">"org.http4s"</span> <span class="o">%%%</span> <span class="s">"http4s-ember-client"</span> <span class="o">%</span> <span class="n">http4sVersion</span><span class="o">,</span>
  <span class="s">"org.http4s"</span> <span class="o">%%%</span> <span class="s">"http4s-ember-server"</span> <span class="o">%</span> <span class="n">http4sVersion</span><span class="o">,</span>
  <span class="s">"org.http4s"</span> <span class="o">%%%</span> <span class="s">"http4s-dsl"</span> <span class="o">%</span> <span class="n">http4sVersion</span><span class="o">,</span>
  <span class="s">"org.typelevel"</span> <span class="o">%%%</span> <span class="s">"log4cats-core"</span> <span class="o">%</span> <span class="s">"2.6.0"</span><span class="o">,</span>
  <span class="s">"co.fs2"</span> <span class="o">%%%</span> <span class="s">"fs2-io"</span> <span class="o">%</span> <span class="s">"3.9.4"</span><span class="o">,</span>
  <span class="s">"org.scala-lang.modules"</span> <span class="o">%%%</span> <span class="s">"scala-xml"</span> <span class="o">%</span> <span class="s">"2.2.0"</span><span class="o">,</span>
  <span class="s">"org.scalatest"</span> <span class="o">%%%</span> <span class="s">"scalatest"</span> <span class="o">%</span> <span class="s">"3.3.0-alpha.1"</span> <span class="o">%</span> <span class="nc">Test</span>
<span class="o">)</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">%%%</code> operator and SBT <code class="language-plaintext highlighter-rouge">nativeLink</code> task to compile the native executables/libraries are added by the
<a href="https://scala-native.org/en/stable/user/sbt.html#sbt-settings-and-tasks">SBT Scala Native</a> plugin, and additional
configuration options are straight forward parameters covered in the setup documentation.</p>

<h2 id="code-changes-required-to-support-scala-native">Code Changes Required to Support Scala Native</h2>

<h4 id="cross-compiling">Cross Compiling</h4>

<p>There exists the <a href="https://github.com/portable-scala/sbt-crossproject">SBT-CrossProject</a> plugin that will hopefully
alleviate issues with compiling to Scala 3 Jar and Scala 3 Native.</p>

<p>TODO: Examples on this working.</p>

<h3 id="partial-jvm-implementations">Partial JVM Implementations</h3>

<h4 id="multithreading-and-runtime-environment">Multithreading and Runtime Environment</h4>

<p>The Scala Native runtime environment is a very basic implementation lacking a threading model and thread
synchronization primitives. The current recommendation is to import and use necessary C libraries. To achieve basic
functionality libraries such as FS2 require an additional library to operate in Scala Native, such as the I/O runtime
<a href="https://github.com/armanbilge/epollcat">epollcat</a>.</p>

<p>Scala libraries are typically built around event-loops and/or monads, so the use of Node.js’s I/O event library
<a href="https://libuv.org/">libuv</a> has been made available
as <a href="https://github.com/scala-native/scala-native-loop">Scala Native Loop</a>.</p>

<h4 id="cryptography-and-javasecurity-package">Cryptography and <code class="language-plaintext highlighter-rouge">java.security</code> package</h4>

<p>While the Scala Native lists many JDK packages as <a href="https://scala-native.org/en/stable/user/lang.html">implemented</a>,
some packages such as <code class="language-plaintext highlighter-rouge">java.security</code> have intentionally been left incomplete. This can have unexpected touch points,
so while a TLS/SSL implementation was a predictably omitted, the dummy implementation of all hash functions, including
MD5 and SHA1
in <a href="https://github.com/scala-native/scala-native/blob/main/javalib/src/main/scala/java/security/MessageDigest.scala">java.security.MessageDigest</a>
was not.</p>

<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">object</span> <span class="nc">MessageDigest</span> <span class="o">{</span>

  <span class="k">def</span> <span class="nf">isEqual</span><span class="o">(</span><span class="n">digestA</span><span class="k">:</span> <span class="kt">Array</span><span class="o">[</span><span class="kt">Byte</span><span class="o">],</span> <span class="n">digestB</span><span class="k">:</span> <span class="kt">Array</span><span class="o">[</span><span class="kt">Byte</span><span class="o">])</span><span class="k">:</span> <span class="kt">Boolean</span> <span class="o">=</span>
    <span class="kc">true</span>

  <span class="k">def</span> <span class="nf">getInstance</span><span class="o">(</span><span class="n">algorithm</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span><span class="k">:</span> <span class="kt">MessageDigest</span> <span class="o">=</span>
    <span class="k">new</span> <span class="nc">DummyMessageDigest</span><span class="o">(</span><span class="n">algorithm</span><span class="o">)</span>
<span class="o">}</span>

<span class="k">private</span> <span class="k">class</span> <span class="nc">DummyMessageDigest</span><span class="o">(</span><span class="n">algorithm</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span>
  <span class="k">extends</span> <span class="nc">MessageDigest</span><span class="o">(</span><span class="n">algorithm</span><span class="o">)</span> <span class="o">{</span>

  <span class="k">override</span> <span class="k">protected</span> <span class="k">def</span> <span class="nf">engineDigest</span><span class="o">()</span><span class="k">:</span> <span class="kt">Array</span><span class="o">[</span><span class="kt">Byte</span><span class="o">]</span> <span class="k">=</span> <span class="nv">Array</span><span class="o">.</span><span class="py">empty</span>

  <span class="k">override</span> <span class="k">protected</span> <span class="k">def</span> <span class="nf">engineReset</span><span class="o">()</span><span class="k">:</span> <span class="kt">Unit</span> <span class="o">=</span> <span class="o">()</span>

  <span class="k">override</span> <span class="k">protected</span> <span class="k">def</span> <span class="nf">engineUpdate</span><span class="o">(</span><span class="n">input</span><span class="k">:</span> <span class="kt">Byte</span><span class="o">)</span><span class="k">:</span> <span class="kt">Unit</span> <span class="o">=</span> <span class="o">()</span>

  <span class="k">override</span> <span class="k">protected</span> <span class="k">def</span> <span class="nf">engineUpdate</span><span class="o">(</span>
                                       <span class="n">input</span><span class="k">:</span> <span class="kt">Array</span><span class="o">[</span><span class="kt">Byte</span><span class="o">],</span>
                                       <span class="n">offset</span><span class="k">:</span> <span class="kt">Int</span><span class="o">,</span>
                                       <span class="n">len</span><span class="k">:</span> <span class="kt">Int</span>
                                     <span class="o">)</span><span class="k">:</span> <span class="kt">Unit</span> <span class="o">=</span> <span class="o">()</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="scala-xml">Scala-XML</h3>

<p>The incompleteness of the Scala-XML library was a little puzzling. What is the point of publishing an XML library when
it can’t pass the most simple, obvious use-case.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[error] Found 21 missing definitions while linking
[error] Not found Top(javax.xml.parsers.SAXParser)
[error] 	at file:/home/runner/work/scala-xml/scala-xml/shared/src/main/scala/scala/xml/factory/XMLLoader.scala:51
</code></pre></div></div>

<p>The problem with <code class="language-plaintext highlighter-rouge">"org.scala-lang.modules" %%% "scala-xml" % "2.2.0"</code> is that <code class="language-plaintext highlighter-rouge">javax</code> library hasn’t been implemented.</p>

<h1 id="runtime-observations">Runtime Observations</h1>

<h2 id="performance-and-memory">Performance and Memory</h2>

<p>For a cross-compiled project, the <code class="language-plaintext highlighter-rouge">http-maven-reciever</code> project compiled to an 27.9MB executable “Fat Jar”.
The debug-mode Scala Native produced a 41.4MB linux executable.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[info] done compiling
[info] Linking (2666 ms)
[info] Checking intermediate code (quick) (194 ms)
[info] Discovered 6982 classes and 47495 methods
[info] Optimizing (debug mode) (3964 ms)
[info] Generating intermediate code (5599 ms)
[info] Produced 10 files
[info] Compiling to native code (17076 ms)
[info] Total (29698 ms)
[success] Total time: 32 s, completed Feb. 7, 2024, 10:29:39 a.m.
</code></pre></div></div>

<p>35MB</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[error] ld: Assertion failed: (aliasSectionNum == sectionNum &amp;&amp; "alias and its target must be located in the same section"), function assignAliasAtomOffsetInSection, file Layout.cpp, line 3324.
[error] clang: error: linker command failed with exit code 1 (use -v to see invocation)
</code></pre></div></div>

<p>TODO: optimized release mode file size
TODO: startup with Scala 3 Jar
TODO: load benchmarks with Scala 3 Jar
TODO: memory footprint with Scala 3 Jar</p>

<h2 id="unit-tests">Unit Tests</h2>

<p>Scala Native officially supports the major testing libraries.
TODO: running tests</p>

<h2 id="debugging">Debugging</h2>

<p>The debugging of native executables and libraries is not as developer friendly as JIT code. There is less debugging
information available within the executables for IDEs to interpret. A typical debugging experience using
<a href="https://lldb.llvm.org/">LLDB</a> is dramatically different from JVM tooling; meaning cross compilation has practical
utility within the entire software development lifecycle.</p>
<figure class="figsvgpadding">
  <img src="/assets/images/2024/02/lldb-gui.jpg" alt="LLDB GUI" class="fighascaption" style="" /><figcaption>
      Debugging Scala Native LLVM code using LLDB Gui

    </figcaption></figure>

<h1 id="conclusions">Conclusions</h1>

<h2 id="pros">Pros</h2>

<ul>
  <li>Fast and direct calling of native dependencies</li>
</ul>

<h2 id="cons">Cons</h2>

<ul>
  <li>Painful debugging</li>
  <li>Long compile/linking times</li>
  <li>Needs Scala Native to compatible dependencies</li>
  <li>Subtle code changes</li>
  <li>Hard to use resource and configuration files</li>
  <li>Usually need to make JVM version work</li>
</ul>

<p>Scala Native is a welcome addition to the C/C++ alternatives for Scala developers. This ecosystem has room to grow as
performance and latency bottlenecks are no longer determined by hardware, but by the minuscule overheads determined
at compile time. Latency can’t be solved by more parallelism or faster CPUs, but by slimming the instruction and cache
footprint of code. Rust and Go have taken off in popularity, and Scala Native, if done right has all the same benefits.</p>

<p>But to make this a compelling choice, Scala Native shouldn’t be confused with Scala’s JVM presence. Longtime users of
ScalaJS are familiar with the restrictions to the <code class="language-plaintext highlighter-rouge">lihaoyi</code> libraries, and Scala Native have the same burden. Projects
using Scala Native should be selective, and primarily oriented to using C native libraries or staying within the small
pockets of JVM libraries such as <code class="language-plaintext highlighter-rouge">lihaoyi</code> or <code class="language-plaintext highlighter-rouge">typelevel</code> which have been early native adopters.</p>

<p>The advice to use Scala Native for only CLI tools is on point. While it will be a long time before this changes, it
doesn’t need to for Scala Native to be successful. The JDK dominated space doesn’t need native via Scala Native,
advancements in alternative JDKs such as GraalVM are already offering native support in a more suitable and easier to
apply route. As it’s always been, select the right tool for the right job.</p>]]></content><author><name>Steven Skelton</name></author><category term="Scala" /><category term="SBT" /><category term="GitHub" /><summary type="html"><![CDATA[Scala Native is a compiler and JDK written in Scala with the goal of removing Scala’s dependency on the JVM. This isn’t meant to achieve a higher performance such as with JDKs, and it is targeting a specialized use-case not considered to be today’s typical Scala development. Its competitors are Rust and Go, not GraalVM, Java or Kotlin. This article goes through common steps and challenges encountered when compiling Scala Native for linux with a GitHub Action.]]></summary></entry><entry><title type="html">Data Transfers and Egress within a GitHub Action</title><link href="https://www.stevenskelton.ca/data-transfers-egress-github-actions/" rel="alternate" type="text/html" title="Data Transfers and Egress within a GitHub Action" /><published>2024-01-24T00:00:00-05:00</published><updated>2024-01-24T00:00:00-05:00</updated><id>https://www.stevenskelton.ca/data-transfers-egress-github-actions</id><content type="html" xml:base="https://www.stevenskelton.ca/data-transfers-egress-github-actions/"><![CDATA[<p>The free tier of GitHub Packages has limited bandwidth to download private artifacts; which can make it unsuitable for
use in a CI/CD pipeline for projects on a budget. In an effort to increase GitHub Packages’ usability, this article
develops an alternative approach minimizing the dependency on GitHub Packages as hot storage, but preserving it as a
viable cold storage, durable storage solution.<!--more--> Building out a cost-effective CI/CD pipeline on the GitHub
platform means utilizing the unlimited egress bandwidth afforded to GitHub Actions to its fullest potential.</p>

<style>
    .multiplex_responsive_1 { width: 360px; height: 100px; margin-top: 1em; margin-bottom: 1em; }
    @media(min-width: 720px) { .multiplex_responsive_1 { width: 360px; margin: 0; height: 100px; } }
</style>

<div class="toc-container">
    <aside class="sidebar__right">
<nav class="toc">
      <header><h4 class="nav__title"><i class="fas fa-file-alt"></i> Table of Contents</h4></header>
<ul class="toc__menu" id="markdown-toc">
  <li><a href="#github-packages-as-a-maven-repository" id="markdown-toc-github-packages-as-a-maven-repository">GitHub Packages as a Maven repository</a></li>
  <li><a href="#github-actions-have-unlimited-egress-transfer-out" id="markdown-toc-github-actions-have-unlimited-egress-transfer-out">GitHub Actions have Unlimited Egress (Transfer-Out)</a>    <ul>
      <li><a href="#security-and-implementation-of-a-push-solution" id="markdown-toc-security-and-implementation-of-a-push-solution">Security and Implementation of a push solution</a>        <ul>
          <li><a href="#maven-repository-checksums" id="markdown-toc-maven-repository-checksums">Maven Repository Checksums</a></li>
          <li><a href="#authentication-flow" id="markdown-toc-authentication-flow">Authentication Flow</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#http-server-for-receiving-and-validating-github-action-upload-requests" id="markdown-toc-http-server-for-receiving-and-validating-github-action-upload-requests">HTTP Server for receiving and validating GitHub Action upload requests</a></li>
</ul>

    </nav>
</aside>
    <div class="toc-ad"><ins class="adsbygoogle multiplex_responsive_1" style="display:block" data-ad-client="ca-pub-5000348308466945" data-ad-slot="5418078712"></ins>
        <script>
            (adsbygoogle = window.adsbygoogle || []).push({});
        </script></div>
</div>

<h1 id="github-packages-as-a-maven-repository">GitHub Packages as a Maven repository</h1>

<p>In an earlier article, <a href="/downloading-from-github-packages-using-http-and-maven/">Downloading from GitHub Packages using HTTP and Maven</a> we investigated GitHub Packages as a Maven
repository for Java artifacts. Standard practices with any network service is evaluating the benefit of implementing a local cache. A
local cache can speed up downloads, allow customized permissions, and increased resiliency against network failures.
Even if a local Maven repository proxy cache such as Artifactory or Nexus is selected, the question remains of how to
get artifacts into the local cache if GitHub Packages transfer limits are being hit.</p>

<h1 id="github-actions-have-unlimited-egress-transfer-out">GitHub Actions have Unlimited Egress (Transfer-Out)</h1>

<p>In a GitHub CI/CD pipeline, where compilation occurs within a GitHub Action, a solution is to utilize the unlettered
egress bandwidth available during post-compilation actions. Compiled artifacts can be stored both in GitHub Packages and
transferred to a Nexus or Artifactory proxy cache when bandwidth is available for free, avoiding metered egress being
made from the proxy to GitHub Packages.</p>

<h2 id="security-and-implementation-of-a-push-solution">Security and Implementation of a push solution</h2>

<h3 id="maven-repository-checksums">Maven Repository Checksums</h3>

<p>The question is now of how to securely transfer files out of a GitHub Actions to remote endpoints. All receivers should
accept only authenticated requests from GitHub Actions. Does this require receivers to implement token authentication?
Practically speaking no, taking a step back this isn’t about user authentication, it is about file authentication. For a
file to be authentic it needs to exist within GitHub Packages. If the receiver is uploaded a file, that file is
authentic and secure if and only if it has a corresponding Maven checksum in our GitHub Packages repository.</p>

<h3 id="authentication-flow">Authentication Flow</h3>

<p>An authentication mechanism beyond GitHub is unwanted and unnecessary. HTTP requests to any external receiver can be
authenticated by verifying the request includes a valid GitHub auth token. The files the requests are uploading can be
validated by accessing GitHub Packages using the GitHub auth token and comparing checksums.</p>

<figure class="figsvgpadding">
  <img src="https://raw.githubusercontent.com/stevenrskelton/build-action-file-receiver/main/requests.drawio.svg" alt="GitHub Actions file push using POST" class="fighascaption" style="" /><figcaption>
      GitHub Action to POST artifacts using HTTP uploads to an external receiver

    </figcaption></figure>

<h1 id="http-server-for-receiving-and-validating-github-action-upload-requests">HTTP Server for receiving and validating GitHub Action upload requests</h1>

<p>This project is basically server-side deployment scripts written in Scala, with Akka HTTP receiving builds from GitHub,
so it can easily be integrated as a Route of existing Akka HTTP / Play deployments.
User Permissions</p>

<p>Upload permissions are limited to the ability to publish to GitHub Packages Maven.</p>

<p>Server-side permissions are completely internal to your server.
Two Deployment Parts</p>

<p>SBT build tasks</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>publishAssemblyToGitHubPackages: pushes compiled code to GitHub Packages (Maven)
uploadAssemblyByPut: pushes compiled code to your server (HTTP PUT)
</code></pre></div></div>

<p>HTTP Upload Server</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>built on Akka, handles HTTP PUT
validates upload is latest version in Maven, and has correct MD5 checksum
performs any custom server-side tasks, such as deployment and restarting
</code></pre></div></div>]]></content><author><name>Steven Skelton</name></author><category term="DevOps/Platform" /><category term="SBT" /><category term="GitHub" /><summary type="html"><![CDATA[The free tier of GitHub Packages has limited bandwidth to download private artifacts; which can make it unsuitable for use in a CI/CD pipeline for projects on a budget. In an effort to increase GitHub Packages’ usability, this article develops an alternative approach minimizing the dependency on GitHub Packages as hot storage, but preserving it as a viable cold storage, durable storage solution.]]></summary></entry></feed>