<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sergey Fedorov</title>
    <description>The latest articles on DEV Community by Sergey Fedorov (@strech).</description>
    <link>https://dev.to/strech</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F210797%2F666a5d6c-9d55-42d0-9610-b12701ca1cfa.png</url>
      <title>DEV Community: Sergey Fedorov</title>
      <link>https://dev.to/strech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/strech"/>
    <language>en</language>
    <item>
      <title>Where is my ETS table!?</title>
      <dc:creator>Sergey Fedorov</dc:creator>
      <pubDate>Tue, 28 Jan 2020 22:08:01 +0000</pubDate>
      <link>https://dev.to/strech/where-is-my-ets-table-3ff1</link>
      <guid>https://dev.to/strech/where-is-my-ets-table-3ff1</guid>
      <description>&lt;h1&gt;
  
  
  Foreword
&lt;/h1&gt;

&lt;p&gt;In a perfect world, everyone will read the documentation beforehand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While working on the Elixir project &lt;a href="https://github.com/Strech/avrora"&gt;Avrora&lt;/a&gt; (convenient encoding and decoding of &lt;a href="https://avro.apache.org/"&gt;Avro&lt;/a&gt; messages) I've &lt;a href="https://github.com/Strech/avrora/issues/21"&gt;received an issue&lt;/a&gt; with testing environment.&lt;/p&gt;

&lt;p&gt;Which was odd to me because it turns out that some ETS references become invalid during a simple test case.&lt;/p&gt;

&lt;p&gt;A very straightforward Phoenix controller&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;AvroraIssueWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PageController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;AvroraIssueWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Avrora&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="s2"&gt;"login"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"fxn"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;schema_name:&lt;/span&gt; &lt;span class="s2"&gt;"com.example.User"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;fail to pass dead simple test cases&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;AvroraIssueWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PageControllerTest&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;AvroraIssueWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ConnCase&lt;/span&gt;

  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;conn:&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;conn:&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If we look inside the method &lt;code&gt;Avrora.encode/2&lt;/code&gt;, we will find that it triggers &lt;a href="https://github.com/Strech/avrora/blob/v0.8.1/lib/avrora/schema.ex#L32-L53"&gt;JSON string parsing&lt;/a&gt;, which will create Elixir struct with a reference to the created ETS table. The result of the &lt;code&gt;Avrora.encode/2&lt;/code&gt; will be cached.&lt;/p&gt;

&lt;p&gt;But how my ETS reference in the cache becomes invalid? And why?&lt;/p&gt;

&lt;h1&gt;
  
  
  Let's take a look at what ETS is?
&lt;/h1&gt;

&lt;p&gt;It's an &lt;a href="http://erlang.org/doc/man/ets.html"&gt;Erlang Built-In Term Storage&lt;/a&gt; which provides the ability to store very large quantities of data in an Erlang runtime system and to have constant access time to the data (&lt;em&gt;depends on the table type&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://elixir-lang.org/getting-started/mix-otp/ets.html"&gt;Elixir you can access it&lt;/a&gt; by using the &lt;code&gt;:ets&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;If we continue reading official Erlang documentation, we will find one important sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice that there is no automatic garbage collection for tables. Even if there&lt;br&gt;
are no references to a table from any process, it is not automatically destroyed&lt;br&gt;
unless the owner process terminates&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  ETS must have an owner process
&lt;/h1&gt;

&lt;p&gt;To illustrate that ETS behavior let's take a look at this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# file: where_is_my_ets.ex&lt;/span&gt;

&lt;span class="c1"&gt;# This is a wrapper around the :ets module.&lt;/span&gt;
&lt;span class="c1"&gt;# We will use named tables to visualize it later.&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Table&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="ss"&gt;:ets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:named_table&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# This is a GenServer which can create named tables&lt;/span&gt;
&lt;span class="c1"&gt;# by calling Table.new/1&lt;/span&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Owner&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GenServer&lt;/span&gt;

  &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;_from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now let's start IEx session and try this code via &lt;code&gt;iex where_is_my_ets.ex&lt;/code&gt;. Notice that &lt;strong&gt;IEx session is also a process&lt;/strong&gt;, hence all created tables right in the REPL will belong to that process, let's give it a try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Let's remember the PID of a REPL session&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;
&lt;span class="c1"&gt;#PID&amp;lt;0.113.0&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;# Now let's check what kind of ETS tables already exist&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:ac_tab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#Reference&amp;lt;0.1096493602.1531314178.170593&amp;gt;, ...,&lt;/span&gt;
 &lt;span class="ss"&gt;:elixir_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:elixir_modules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;IEx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;IEx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Pry&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Creating new table with name :repl&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:repl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;:repl&lt;/span&gt;

&lt;span class="c1"&gt;# Who is the owner?&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:repl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="c1"&gt;#Reference&amp;lt;0.1096493602.1531314178.171287&amp;gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;read_concurrency:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;write_concurrency:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;compressed:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;memory:&lt;/span&gt; &lt;span class="mi"&gt;311&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;owner:&lt;/span&gt; &lt;span class="c1"&gt;#PID&amp;lt;0.113.0&amp;gt;, # &amp;lt;--- This is our REPL session!&lt;/span&gt;
  &lt;span class="ss"&gt;heir:&lt;/span&gt; &lt;span class="ss"&gt;:none&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="ss"&gt;:repl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;size:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;node:&lt;/span&gt; &lt;span class="ss"&gt;:nonode&lt;/span&gt;&lt;span class="nv"&gt;@nohost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;named_table:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;type:&lt;/span&gt; &lt;span class="ss"&gt;:set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;keypos:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;protection:&lt;/span&gt; &lt;span class="ss"&gt;:protected&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Now we are able to see :repl table in the list of all tables&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:ac_tab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#Reference&amp;lt;0.1096493602.1531314178.170593&amp;gt;, ...,&lt;/span&gt;
 &lt;span class="ss"&gt;:elixir_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:elixir_modules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;IEx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;IEx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Pry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:repl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Starting Owner process without linking to REPL process&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#PID&amp;lt;0.123.0&amp;gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# And create a new table in REPL, but by different process&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:hello&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="ss"&gt;:hello&lt;/span&gt;

&lt;span class="c1"&gt;# Who is the owner of that table?&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:hello&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="c1"&gt;#Reference&amp;lt;0.1096493602.1531314178.171427&amp;gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;read_concurrency:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;write_concurrency:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;compressed:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;memory:&lt;/span&gt; &lt;span class="mi"&gt;311&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;owner:&lt;/span&gt; &lt;span class="c1"&gt;#PID&amp;lt;0.123.0&amp;gt;, # &amp;lt;--- This is our Owner process&lt;/span&gt;
  &lt;span class="ss"&gt;heir:&lt;/span&gt; &lt;span class="ss"&gt;:none&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="ss"&gt;:hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;size:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;node:&lt;/span&gt; &lt;span class="ss"&gt;:nonode&lt;/span&gt;&lt;span class="nv"&gt;@nohost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;named_table:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;type:&lt;/span&gt; &lt;span class="ss"&gt;:set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;keypos:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;protection:&lt;/span&gt; &lt;span class="ss"&gt;:protected&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# We can see :hello table too&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:ac_tab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#Reference&amp;lt;0.1096493602.1531314178.170593&amp;gt;, ...,&lt;/span&gt;
 &lt;span class="ss"&gt;:elixir_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:elixir_modules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;IEx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;IEx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Pry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:repl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:hello&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# What will happen if process Owner will die?&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="no"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# Table :hello gone ...&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:hello&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;:undefined&lt;/span&gt;

&lt;span class="c1"&gt;# Also from the "all" list&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:ac_tab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#Reference&amp;lt;0.1096493602.1531314178.170593&amp;gt;, ...,&lt;/span&gt;
 &lt;span class="ss"&gt;:elixir_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:elixir_modules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;IEx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;IEx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Pry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:repl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  So what was the problem with tests?
&lt;/h1&gt;

&lt;p&gt;In the Phoenix framework, every action in the controller will spawn a process to handle the request. Inside that process, an ETS table was created and its reference stored in a shared cache.&lt;/p&gt;

&lt;p&gt;But once an action is done, the ETS table is wiped and the reference becomes invalid. To solve this issue I delegate ETS creation to a separate process that becomes their owner instead of short-living Phoenix controller.&lt;/p&gt;

&lt;h1&gt;
  
  
  Some useful links
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/Strech/avrora"&gt;Avrora library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fxn/avrora_issue"&gt;Repository with reported issue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://elixirforum.com/t/do-we-need-a-process-for-ets-tables/22705"&gt;Discussion around processes and ETS&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>todayilearned</category>
      <category>elixir</category>
      <category>erlang</category>
    </item>
    <item>
      <title>What I miss in Github Actions pipeline?</title>
      <dc:creator>Sergey Fedorov</dc:creator>
      <pubDate>Thu, 17 Oct 2019 11:55:49 +0000</pubDate>
      <link>https://dev.to/strech/what-i-miss-in-github-actions-pipeline-47nc</link>
      <guid>https://dev.to/strech/what-i-miss-in-github-actions-pipeline-47nc</guid>
      <description>&lt;p&gt;You might have heard that Github launched their awesome Github Actions now for everyone?&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--F26Oc0ef--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1677873294/image_normal.jpg" alt="Nat Friedman profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Nat Friedman
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @natfriedman
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P4t6ys1m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Good news: anyone who signs up for the GitHub Actions beta will get admitted instantly. Give it a try! &lt;a href="https://t.co/bHgAcXcNyH"&gt;github.com/features/actio…&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      03:10 AM - 18 Sep 2019
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1174158823877042177" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1174158823877042177" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      247
      &lt;a href="https://twitter.com/intent/like?tweet_id=1174158823877042177" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      674
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;Github Actions brings a huge potential for automation of almost any kind of routines.&lt;/p&gt;

&lt;p&gt;And the first thing I tried was a bot. I like the idea of interactive automation via commands, be it in Slack or Github. But if you can't run your server with a bot, Github Actions comes to the rescue!&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Let's say you want to have a great &lt;a href="https://giphy.com/"&gt;GIPHY&lt;/a&gt; bot which can handle commands from the issue or pull request comments and its syntax could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~giphy cat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The workflow of the Github Action can be described as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trigger when the issue gets a new comment&lt;/li&gt;
&lt;li&gt;Extract the command and its arguments from the comment's body&lt;/li&gt;
&lt;li&gt;Query api.giphy.com and fetch the first matching picture&lt;/li&gt;
&lt;li&gt;Post a comment back with the gif&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  The action
&lt;/h2&gt;

&lt;p&gt;As an entry point, the &lt;a href="https://help.github.com/en/categories/automating-your-workflow-with-github-actions"&gt;official documentation&lt;/a&gt; is a good place to start.&lt;/p&gt;

&lt;p&gt;Let's say we know how to define a skeleton of the action which should trigger on a new comment&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A GIPHY bot&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;issue_comment&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;post_image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Post an image from the search&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# ... our steps ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After our skeleton is defined we need to set the steps that will be executed. According to the workflow we describe, after a new issue comment appears, we need to parse the message body and then pass the result to the next step. Sounds familiar, right?&lt;/p&gt;

&lt;p&gt;It sounds like a Linux pipe operator and if I needed to do this in a console, I would do something like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"~giphy cat"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;--color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;never &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s2"&gt;"(?&amp;lt;=~giphy&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;)[&lt;/span&gt;&lt;span class="se"&gt;\w\d\s&lt;/span&gt;&lt;span class="s2"&gt;]+"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  xargs &lt;span class="nt"&gt;-I&lt;/span&gt;&lt;span class="o"&gt;{}&lt;/span&gt; curl &lt;span class="s2"&gt;"https://api.giphy.com/v1/gifs/search?api_key=&amp;lt;API Key&amp;gt;&amp;amp;limit=1&amp;amp;rating=g&amp;amp;q={}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ok, let's try it ...&lt;/p&gt;
&lt;h1&gt;
  
  
  The fail
&lt;/h1&gt;

&lt;p&gt;Execute a command, take its result and pass to the next step, sounds easy, right? According to &lt;a href="https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions#steps-context"&gt;the documentation of the spec context&lt;/a&gt;, you should identify your step and then you can use its output like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# ... omitted ...&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Step &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;step_1&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Hello World"&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Step &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;step_2&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "${{ steps.step_1.outputs.&amp;lt;output name&amp;gt; }}"&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;What to put in as the "output name" you ask? I asked myself the same question. At first glance, I would expect to have like the Linux &lt;a href="https://en.wikipedia.org/wiki/Standard_streams"&gt;standards streams&lt;/a&gt; here&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;File descriptor&lt;/th&gt;
&lt;th&gt;Abbreviation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard input&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;stdin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard output&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;stdout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard error&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;stderr&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;But if you try to use it, you will get an error:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# ... omitted ...&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Step &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;step_1&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Hello World"&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Step &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;step_2&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Simon says - ${{ steps.step_1.outputs.0 }}"&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### ERRORED 09:27:44Z

- Your workflow file was invalid: The pipeline is not valid.
  .github/workflows/giphy.yml (Line: 16, Col: 14): Unexpected symbol: '0'. 
  Located at position 29 within expression: steps.parse.outputs.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Seems it expects &lt;a href="https://help.github.com/en/articles/metadata-syntax-for-github-actions#outputs"&gt;letters&lt;/a&gt; for the output name, which I can understand because your command can set more than one value for its execution result.&lt;/p&gt;

&lt;p&gt;Surely we can write a custom action and use the action API to set the output, but I just want to have a dead-simple pipelining and less code to maintain.&lt;/p&gt;

&lt;p&gt;The help comes from Google. Looks like I was not the only one who fails to get step output. Same &lt;a href="https://github.community/t5/GitHub-Actions/steps-output/td-p/30947"&gt;question had been asked&lt;/a&gt; in the Github Community and some other blog posts&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://jasonet.co/posts/new-features-of-github-actions/"&gt;https://jasonet.co/posts/new-features-of-github-actions/&lt;/a&gt;&lt;br&gt;
These methods use functionality of the runtime that isn’t documented (I had to read through some code to figure out how it was working), so it may feel a little magical. Now, if you’re like me, you’re thinking “Can this be done without the toolkit or JavaScript?” Turns out, yes!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Turns out, that to set the result you should use the &lt;strong&gt;yet undocumented&lt;/strong&gt; &lt;a href="https://github.com/actions/toolkit/blob/99d3ad0a6473e7e7906681627a450150a772ee1d/packages/core/src/command.ts#L9-L18"&gt;functionality of the commands&lt;/a&gt; which looks like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
 * Commands
 *
 * Command Format:
 *   ##[name key=value;key=value]message
 *
 * Examples:
 *   ##[warning]This is the user warning message
 *   ##[set-secret name=mypassword]definitelyNotAPassword!
 */
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's give it a try:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# ... omitted ...&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Step &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;step_1&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "##[set-output name=hello;]Hello World"&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Step &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;step_2&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Simon says - ${{ steps.step_1.outputs.hello }}"&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;2019-10-16T17:27:03.2937669Z Current runner version: &lt;span class="s1"&gt;'2.159.0'&lt;/span&gt;
2019-10-16T17:27:03.2956102Z Prepare workflow directory
2019-10-16T17:27:03.4191005Z Prepare all required actions
2019-10-16T17:27:03.5977738Z &lt;span class="c"&gt;##[group]Run echo "##[set-output name=hello;]Hello World"&lt;/span&gt;
2019-10-16T17:27:03.5978178Z &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"##[set-output name=hello;]Hello World"&lt;/span&gt;
2019-10-16T17:27:04.3506195Z shell: /bin/bash &lt;span class="nt"&gt;--noprofile&lt;/span&gt; &lt;span class="nt"&gt;--norc&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; pipefail &lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt;
2019-10-16T17:27:04.3506360Z &lt;span class="c"&gt;##[endgroup]&lt;/span&gt;
2019-10-16T17:27:04.4259015Z &lt;span class="c"&gt;##[set-output name=hello;]Hello World&lt;/span&gt;
2019-10-16T17:27:04.4740495Z &lt;span class="c"&gt;##[group]Run echo "Simon says - Hello World"&lt;/span&gt;
2019-10-16T17:27:04.4740667Z &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Simon says - Hello World"&lt;/span&gt;
2019-10-16T17:27:04.4778909Z shell: /bin/bash &lt;span class="nt"&gt;--noprofile&lt;/span&gt; &lt;span class="nt"&gt;--norc&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; pipefail &lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt;
2019-10-16T17:27:04.4779052Z &lt;span class="c"&gt;##[endgroup]&lt;/span&gt;
2019-10-16T17:27:04.4858165Z Simon says - Hello World
2019-10-16T17:27:04.4936789Z Cleaning up orphan processes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ok, now we know how to set output! I will show the result of the workflow with the steps pipelining and a use case of extra actions, like &lt;a href="https://github.com/actions/github-script"&gt;actions/github-script&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A GIPHY reply bot&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;issue_comment&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;post_image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Post an image from the GIPHY search&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Parse command&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;parse&lt;/span&gt;
        &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "##[set-output name=query;]$(echo "${{ github.event.comment.body }}" | grep --color=never -oP "(?&amp;lt;=~giphy\s)[\w\d\s]+")"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Search picture&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;search&lt;/span&gt;
        &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;import json&lt;/span&gt;
          &lt;span class="s"&gt;from urllib import urlopen&lt;/span&gt;

          &lt;span class="s"&gt;url = 'https://api.giphy.com/v1/gifs/search?limit=1&amp;amp;rating=g&amp;amp;api_key=${{ secrets.API_KEY }}&amp;amp;q=${{ steps.parse.outputs.query }}'&lt;/span&gt;
          &lt;span class="s"&gt;body = json.loads(urlopen(url).read())&lt;/span&gt;

          &lt;span class="s"&gt;print('##[set-output name=url;]' + body['data'][0]['images']['fixed_width']['url'])&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Reply with a picture&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/github-script@0.2.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.token }}&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;return github.issues.createComment({&lt;/span&gt;
              &lt;span class="s"&gt;...context.issue, body: "![giphy](${{ steps.search.outputs.url }})"&lt;/span&gt;
            &lt;span class="s"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;em&gt;NOTE: Don't forget to set the repository secret &lt;code&gt;API_KEY&lt;/code&gt; with your giphy application key if you want to try this workflow&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Thoughts
&lt;/h1&gt;

&lt;p&gt;I like the new &lt;a href="https://help.github.com/en/articles/about-github-actions#migrating-github-actions-from-hcl-to-yaml-syntax"&gt;YAML syntax&lt;/a&gt;. It feels more logical and simple. I see the constant improvements over the GUI of the Actions page.&lt;/p&gt;

&lt;p&gt;I wish that Github makes &lt;code&gt;steps&lt;/code&gt; more like a Linux pipeline, which will allow you to easily transfer your knowledge of working in the console to the Github Actions.&lt;/p&gt;

&lt;p&gt;At the same time, I understand the need to create custom Actions to satisfy different needs, but listen, somehow GNU manages to have an awesome set of composable commands and so can we, too!&lt;/p&gt;



&lt;p&gt;Here is the repository with the code samples&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Strech"&gt;
        Strech
      &lt;/a&gt; / &lt;a href="https://github.com/Strech/giphy-bot-example"&gt;
        giphy-bot-example
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An example of pipelining in Github actions
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
A Github Actions GIPHY bot&lt;/h1&gt;
&lt;p&gt;If you would like to see how bot is reacting on your messages, go to &lt;a href="https://github.com/Strech/giphy-bot-example/issues/1"&gt;this issue&lt;/a&gt; and leave a comment like&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~giphy grumpy cat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/Strech/giphy-bot-example/master/./how-it-works.gif"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lt80B6fJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://raw.githubusercontent.com/Strech/giphy-bot-example/master/./how-it-works.gif" alt="How it works"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Strech/giphy-bot-example"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



</description>
      <category>github</category>
      <category>actions</category>
      <category>todayilearned</category>
    </item>
  </channel>
</rss>
