<p dir="auto">I can't postmessage a shared WebAssembly memory or module to a worker:<br>
<a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/19688162/104839764-8ea55900-58c3-11eb-9c57-2fd8d255eddb.png"><img src="https://user-images.githubusercontent.com/19688162/104839764-8ea55900-58c3-11eb-9c57-2fd8d255eddb.png" alt="image" style="max-width: 100%;"></a></p>
<p dir="auto">results in:<br>
<a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/19688162/104839772-a41a8300-58c3-11eb-915e-3317c45adeec.png"><img src="https://user-images.githubusercontent.com/19688162/104839772-a41a8300-58c3-11eb-915e-3317c45adeec.png" alt="image" style="max-width: 100%;"></a></p>
<p dir="auto">Same happens with wasm module, also I cant use any of them to move wasm execution into a worker<br>
Do I need a special flag for this?</p>
<p dir="auto">Command I used to run this:<br>
docker run -it --init -v ${PWD}:/app hayd/alpine-deno run --allow-read --unstable --v8-flags="--experimental-wasm-threads" /app/main.ts</p>
<p dir="auto"><a href="https://github.com/denoland/deno/files/5826010/test.zip">test.zip</a></p>
<h1 dir="auto">Deno op dispatch and native plugins</h1>
<h2 dir="auto">Overview</h2>
<p dir="auto">Deno dispatch system is based on</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="Deno.core.send(opId: number, control: null | ArrayBufferView, data?: ArrayBufferView)"><pre class="notranslate"><span class="pl-smi">Deno</span><span class="pl-kos">.</span><span class="pl-c1">core</span><span class="pl-kos">.</span><span class="pl-en">send</span><span class="pl-kos">(</span><span class="pl-s1">opId</span>: <span class="pl-s1">number</span><span class="pl-kos">,</span> <span class="pl-s1">control</span>: <span class="pl-c1">null</span> <span class="pl-c1">|</span> <span class="pl-smi">ArrayBufferView</span><span class="pl-kos">,</span> <span class="pl-s1">data</span>?: <span class="pl-smi">ArrayBufferView</span><span class="pl-kos">)</span></pre></div>
<ul dir="auto">
<li>a function that passes messages from JS to Rust and executes function described with <code class="notranslate">opId</code>.</li>
</ul>
<p dir="auto">Message passing is done using so-called "shared queue", which for now has served it's purpose well.</p>
<p dir="auto">The <code class="notranslate">opId</code> which identifies the op and its dispatcher is a number that is shared between TypeScript and Rust.</p>
<p dir="auto">As of Deno v0.18.0 all ops dispatching logic is hardcoded.</p>
<h2 dir="auto">Problem</h2>
<p dir="auto">Current solution is very simple and quite efficient, however has several shortcomings that need to be addressed<br>
to expose native plugins API to users;</p>
<ol dir="auto">
<li><code class="notranslate">OpId</code> has to be manually synchronized between TS and Rust. In current situation, where we have about 60 ops, it's not that bad, but surely we'll be cumbersome as number of ops grows.</li>
</ol>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="...
// Warning! These values are duplicated in the TypeScript code (js/dispatch.ts),
// update with care.
pub const OP_READ: OpId = 1;
pub const OP_WRITE: OpId = 2;
..."><pre class="notranslate">..<span class="pl-kos">.</span>
<span class="pl-c">// Warning! These values are duplicated in the TypeScript code (js/dispatch.ts),</span>
<span class="pl-c">// update with care.</span>
<span class="pl-c1">pub</span><span class="pl-kos"></span> <span class="pl-k">const</span> <span class="pl-v">OP_READ</span><span class="pl-kos">:</span> <span class="pl-smi">OpId</span> = <span class="pl-c1">1</span><span class="pl-kos">;</span>
<span class="pl-k">pub</span> <span class="pl-k">const</span> <span class="pl-v">OP_WRITE</span><span class="pl-kos">:</span> <span class="pl-smi">OpId</span> = <span class="pl-c1">2</span><span class="pl-kos">;</span>
..<span class="pl-kos">.</span></pre></div>
<ol start="2" dir="auto">
<li>There is no way to add new op programatically. Op has be to registered in code manually before compilation, which prevents users from adding new ops from their' code.</li>
</ol>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="let op = match op_id {
    OP_READ =&gt; {
      dispatch_minimal::dispatch(io::op_read, state, control, zero_copy)
    }
    OP_WRITE =&gt; {
      dispatch_minimal::dispatch(io::op_write, state, control, zero_copy)
    }
    ...
}"><pre class="notranslate"><span class="pl-k">let</span> op = <span class="pl-k">match</span> op_id <span class="pl-kos">{</span>
    <span class="pl-v">OP_READ</span> =&gt; <span class="pl-kos">{</span>
      dispatch_minimal<span class="pl-kos">::</span><span class="pl-en">dispatch</span><span class="pl-kos">(</span>io<span class="pl-kos">::</span>op_read<span class="pl-kos">,</span> state<span class="pl-kos">,</span> control<span class="pl-kos">,</span> zero_copy<span class="pl-kos">)</span>
    <span class="pl-kos">}</span>
    <span class="pl-v">OP_WRITE</span> =&gt; <span class="pl-kos">{</span>
      dispatch_minimal<span class="pl-kos">::</span><span class="pl-en">dispatch</span><span class="pl-kos">(</span>io<span class="pl-kos">::</span>op_write<span class="pl-kos">,</span> state<span class="pl-kos">,</span> control<span class="pl-kos">,</span> zero_copy<span class="pl-kos">)</span>
    <span class="pl-kos">}</span>
    ...
<span class="pl-kos">}</span><span class="pl-kos"></span></pre></div>
<ol start="3" dir="auto">
<li>Op and its dispatcher are very tightly coupled. There still be tight coupling between op and it's dispatcher (you can't easily write op that will work with both "minimal" and "json" dispatcher), but it should be done using a simple API instead of manually writing closures.</li>
</ol>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="// see example above"><pre class="notranslate"><span class="pl-c">// see example above</span></pre></div>
<ol start="4" dir="auto">
<li>Code for ops lives in 2 different places. TS code for ops currently lives in <code class="notranslate">//js/</code> while Rust code lives in <code class="notranslate">//cli/ops/</code>. Ideally we'd like to have <code class="notranslate">//ops</code> directory that will contain both TS and Rust code (eg. <code class="notranslate">//ops/fs/main.ts</code> and <code class="notranslate">//ops/fs/mod.ts</code> as entry points).</li>
</ol>
<h2 dir="auto">Nomenclature</h2>
<ul dir="auto">
<li><code class="notranslate">dispatch</code> - main method used to call into Rust code from JS. See <code class="notranslate">Isolate::set_dispatch</code> method.</li>
<li><code class="notranslate">dispatcher</code> - an abstract construct that is called from <code class="notranslate">dispatch</code>. It's aware of serialization method ("minimal", "json") and sets contract for <code class="notranslate">op handler</code>.</li>
<li><code class="notranslate">op</code>/<code class="notranslate">op handler</code> - a function that performs some actual work, eg. "read"/"write"/"dial". <code class="notranslate">Op</code> has to be used with a <code class="notranslate">dispatcher</code> that knows how to transform result of that <code class="notranslate">op</code> into <code class="notranslate">CoreOp</code>.</li>
<li><code class="notranslate">CoreOp</code> - fundamental type that describes how Rust code can return value to JS.</li>
</ul>
<h2 dir="auto">Requirements</h2>
<ol dir="auto">
<li>Minimal or no changes to <code class="notranslate">Deno.core</code> and shared queue infrastructure.</li>
<li>New system must be FAST (at least as fast as current solution, preferably faster).</li>
<li>Ops should have access to permission APIs (TODO: can permissions be forced upon plugins/ops?)</li>
<li>Op handler signature shouldn't depend on <code class="notranslate">ThreadSafeState</code> because it's a <code class="notranslate">deno_cli</code> concept.</li>
<li>Ability to register new <code class="notranslate">dispatcher</code> with custom serialization method (eg. "msgpack").</li>
<li>Ability to register new <code class="notranslate">op</code> statically (before build).</li>
<li>No hardcoded <code class="notranslate">opIds</code> - after registration of an op, during startup Rust sends an "op map" that is a dictonary mapping op names to op ids.</li>
<li>Ability to register new <code class="notranslate">op</code> dynamically (from JS code), loading some Rust code (as dynamic library?). This functionality can be exposed as another op - <code class="notranslate">op_register_new_op</code>.</li>
<li>Ability to register an <code class="notranslate">op</code> multiple times. Say I have some native plugin that requires "read" op, but I'm not sure that host will have "read" available. So I register the op in my library setup code (potentially with namespace of my plugin).</li>
<li>Both static and dynamic op registration should be as simple as possible.</li>
</ol>
<h2 dir="auto">Proposed dispatch implementation</h2>
<h4 dir="auto">Prototype by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/afinch7/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/afinch7">@afinch7</a></h4>
<p dir="auto">In <a href="https://github.com/denoland/deno/pull/2840" data-hovercard-type="pull_request" data-hovercard-url="/denoland/deno/pull/2840/hovercard">#2840</a> there's a complete solution.</p>
<ul dir="auto">
<li>"op listeners"</li>
</ul>
<p dir="auto"><del>After registration of a new op a bunch of listener methods is called to reset the ids of all ops.<br>
I believe this can be refactored to encapsulate this logic in <code class="notranslate">JsonOp</code> (presented in "pros" section) and ultimately<br>
should be handled by separate op that calls "callback" function on dispatch to register op ids that are sent from Rust.</del><br>
This is only done when using <code class="notranslate">set_dispatch_registry</code>, which can be combined with <code class="notranslate">set_dispatch</code>. It's functionality is equivalent<br>
to proposed <code class="notranslate">setOpMap</code> below.</p>
<ul dir="auto">
<li>dispatcher registry</li>
</ul>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pub trait OpDispatcher: Send + Sync {
  fn dispatch(&amp;self, args: &amp;[u8], buf: Option&lt;PinnedBuf&gt;) -&gt; CoreOp;
}

type OpDispatcherRegistry = Vec&lt;Option&lt;Arc&lt;Box&lt;dyn OpDispatcher&gt;&gt;&gt;&gt;;

pub struct OpDisReg {
  // Quick lookups by unique &quot;op id&quot;/&quot;resource id&quot;
  // The main goal of op_dis_registry is to perform lookups as fast
  // as possible at all times.
  op_dis_registry: RwLock&lt;OpDispatcherRegistry&gt;,
  next_op_dis_id: AtomicU32,
  // Serves as &quot;phone book&quot; for op_dis_registry
  // This should only be referenced for initial lookups. It isn't
  // possible to achieve the level of perfromance we want if we
  // have to query this for every dispatch, but it may be needed
  // to build caches for subseqent lookups.
  op_dis_id_registry: RwLock&lt;HashMap&lt;String, HashMap&lt;&amp;'static str, OpId&gt;&gt;&gt;,
  notifier_reg: RwLock&lt;NotifyerReg&gt;,
}"><pre class="notranslate"><span class="pl-k">pub</span> <span class="pl-k">trait</span> <span class="pl-smi">OpDispatcher</span><span class="pl-kos">:</span> <span class="pl-smi">Send</span> + <span class="pl-smi">Sync</span> <span class="pl-kos">{</span>
  <span class="pl-k">fn</span> <span class="pl-en">dispatch</span><span class="pl-kos">(</span><span class="pl-c1">&amp;</span><span class="pl-smi">self</span><span class="pl-kos">,</span> <span class="pl-s1">args</span><span class="pl-kos">:</span> <span class="pl-c1">&amp;</span><span class="pl-kos">[</span><span class="pl-smi">u8</span><span class="pl-kos">]</span><span class="pl-kos">,</span> <span class="pl-s1">buf</span><span class="pl-kos">:</span> <span class="pl-smi">Option</span><span class="pl-kos">&lt;</span><span class="pl-smi">PinnedBuf</span><span class="pl-kos">&gt;</span><span class="pl-kos">)</span> -&gt; <span class="pl-smi">CoreOp</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>

<span class="pl-k">type</span> <span class="pl-smi">OpDispatcherRegistry</span> = <span class="pl-smi">Vec</span><span class="pl-kos">&lt;</span><span class="pl-smi">Option</span><span class="pl-kos">&lt;</span><span class="pl-smi">Arc</span><span class="pl-kos">&lt;</span><span class="pl-smi">Box</span><span class="pl-kos">&lt;</span><span class="pl-k">dyn</span> <span class="pl-smi">OpDispatcher</span><span class="pl-kos">&gt;</span><span class="pl-kos">&gt;</span><span class="pl-kos">&gt;</span><span class="pl-kos">&gt;</span><span class="pl-kos">;</span>

<span class="pl-k">pub</span> <span class="pl-k">struct</span> <span class="pl-smi">OpDisReg</span> <span class="pl-kos">{</span>
  <span class="pl-c">// Quick lookups by unique "op id"/"resource id"</span>
  <span class="pl-c">// The main goal of op_dis_registry is to perform lookups as fast</span>
  <span class="pl-c">// as possible at all times.</span>
  <span class="pl-c1">op_dis_registry</span><span class="pl-kos">:</span> <span class="pl-smi">RwLock</span><span class="pl-kos">&lt;</span><span class="pl-smi">OpDispatcherRegistry</span><span class="pl-kos">&gt;</span><span class="pl-kos">,</span>
  <span class="pl-c1">next_op_dis_id</span><span class="pl-kos">:</span> <span class="pl-smi">AtomicU32</span><span class="pl-kos">,</span>
  <span class="pl-c">// Serves as "phone book" for op_dis_registry</span>
  <span class="pl-c">// This should only be referenced for initial lookups. It isn't</span>
  <span class="pl-c">// possible to achieve the level of perfromance we want if we</span>
  <span class="pl-c">// have to query this for every dispatch, but it may be needed</span>
  <span class="pl-c">// to build caches for subseqent lookups.</span>
  <span class="pl-c1">op_dis_id_registry</span><span class="pl-kos">:</span> <span class="pl-smi">RwLock</span><span class="pl-kos">&lt;</span><span class="pl-smi">HashMap</span><span class="pl-kos">&lt;</span><span class="pl-smi">String</span><span class="pl-kos">,</span> <span class="pl-smi">HashMap</span><span class="pl-kos">&lt;</span><span class="pl-c1">&amp;</span><span class="pl-c1">'</span><span class="pl-ent">static</span> <span class="pl-smi">str</span><span class="pl-kos">,</span> <span class="pl-smi">OpId</span><span class="pl-kos">&gt;</span><span class="pl-kos">&gt;</span><span class="pl-kos">&gt;</span><span class="pl-kos">,</span>
  <span class="pl-c1">notifier_reg</span><span class="pl-kos">:</span> <span class="pl-smi">RwLock</span><span class="pl-kos">&lt;</span><span class="pl-smi">NotifyerReg</span><span class="pl-kos">&gt;</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto"><code class="notranslate">notifier_reg</code> is used to trigger "op listeners" described above.</p>
<p dir="auto">Pros:</p>
<ul dir="auto">
<li>elegant API for registration of ops in TS</li>
</ul>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="const OP_CHDIR = new JsonOp(opNamespace, &quot;chdir&quot;);

export function chdir(directory: string): void {
  OP_CHDIR.sendSync({ directory });
}"><pre class="notranslate"><span class="pl-k">const</span> <span class="pl-smi">OP_CHDIR</span> <span class="pl-c1">=</span> <span class="pl-k">new</span> <span class="pl-smi">JsonOp</span><span class="pl-kos">(</span><span class="pl-s1">opNamespace</span><span class="pl-kos">,</span> <span class="pl-s">"chdir"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-k">export</span> <span class="pl-k">function</span> <span class="pl-en">chdir</span><span class="pl-kos">(</span><span class="pl-s1">directory</span>: <span class="pl-smi">string</span><span class="pl-kos">)</span>: <span class="pl-smi"><span class="pl-k">void</span></span> <span class="pl-kos">{</span>
  <span class="pl-smi">OP_CHDIR</span><span class="pl-kos">.</span><span class="pl-en">sendSync</span><span class="pl-kos">(</span><span class="pl-kos">{</span> directory <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">It might need a little tweaking as each op would create a closure, but that has to verified experimentally.</p>
<ul dir="auto">
<li>nice code organization</li>
</ul>
<p dir="auto">All code (both Rust and TS) related to <code class="notranslate">fs</code> lives inside <code class="notranslate">//ops/fs</code> crate.</p>
<p dir="auto">Cons:</p>
<ul dir="auto">
<li>each op is a trait</li>
</ul>
<p dir="auto">This leads for a lot of boilerplate code, where we essentialy have an empty structure that must implement a trait.</p>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="pub struct OpChmod;

#[derive(Deserialize)]
#[serde(rename_all = &quot;camelCase&quot;)]
struct ChmodArgs {
  promise_id: Option&lt;u64&gt;,
  path: String,
  mode: u32,
}

impl FsOpDispatcher for OpChmod {
  fn dispatch(
    &amp;self,
    state: &amp;TSFsOpsState,
    control: &amp;[u8],
    buf: Option&lt;PinnedBuf&gt;,
  ) -&gt; CoreOp {
    wrap_json_op(
      move |args, _zero_copy| {
        let args: ChmodArgs = serde_json::from_value(args)?;
        let (path, path_) = deno_fs::resolve_from_cwd(args.path.as_ref())?;

        state.check_write(&amp;path_)?;

        let is_sync = args.promise_id.is_none();
        blocking_json(is_sync, move || {
          debug!(&quot;op_chmod {}&quot;, &amp;path_);
          // Still check file/dir exists on windows
          let _metadata = fs::metadata(&amp;path).map_err(FsOpError::from)?;
          #[cfg(any(unix))]
          {
            let mut permissions = _metadata.permissions();
            permissions.set_mode(args.mode);
            fs::set_permissions(&amp;path, permissions).map_err(FsOpError::from)?;
          }
          Ok(json!({}))
        })
      },
      control,
      buf,
    )
  }

  const NAME: &amp;'static str = &quot;chmod&quot;;
}"><pre class="notranslate"><span class="pl-k">pub</span> <span class="pl-k">struct</span> <span class="pl-smi">OpChmod</span><span class="pl-kos">;</span>

<span class="pl-c1">#<span class="pl-kos">[</span>derive<span class="pl-kos">(</span><span class="pl-v">Deserialize</span><span class="pl-kos">)</span><span class="pl-kos">]</span></span>
<span class="pl-c1">#<span class="pl-kos">[</span>serde<span class="pl-kos">(</span>rename_all = <span class="pl-s">"camelCase"</span><span class="pl-kos">)</span><span class="pl-kos">]</span></span>
<span class="pl-k">struct</span> <span class="pl-smi">ChmodArgs</span> <span class="pl-kos">{</span>
  <span class="pl-c1">promise_id</span><span class="pl-kos">:</span> <span class="pl-smi">Option</span><span class="pl-kos">&lt;</span><span class="pl-smi">u64</span><span class="pl-kos">&gt;</span><span class="pl-kos">,</span>
  <span class="pl-c1">path</span><span class="pl-kos">:</span> <span class="pl-smi">String</span><span class="pl-kos">,</span>
  <span class="pl-c1">mode</span><span class="pl-kos">:</span> <span class="pl-smi">u32</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span>

<span class="pl-k">impl</span> <span class="pl-smi">FsOpDispatcher</span> <span class="pl-k">for</span> <span class="pl-smi">OpChmod</span> <span class="pl-kos">{</span>
  <span class="pl-k">fn</span> <span class="pl-en">dispatch</span><span class="pl-kos">(</span>
    <span class="pl-c1">&amp;</span><span class="pl-smi">self</span><span class="pl-kos">,</span>
    <span class="pl-s1">state</span><span class="pl-kos">:</span> <span class="pl-c1">&amp;</span><span class="pl-smi">TSFsOpsState</span><span class="pl-kos">,</span>
    <span class="pl-s1">control</span><span class="pl-kos">:</span> <span class="pl-c1">&amp;</span><span class="pl-kos">[</span><span class="pl-smi">u8</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
    <span class="pl-s1">buf</span><span class="pl-kos">:</span> <span class="pl-smi">Option</span><span class="pl-kos">&lt;</span><span class="pl-smi">PinnedBuf</span><span class="pl-kos">&gt;</span><span class="pl-kos">,</span>
  <span class="pl-kos">)</span> -&gt; <span class="pl-smi">CoreOp</span> <span class="pl-kos">{</span>
    <span class="pl-en">wrap_json_op</span><span class="pl-kos">(</span>
      <span class="pl-k">move</span> |args<span class="pl-kos">,</span> _zero_copy| <span class="pl-kos">{</span>
        <span class="pl-k">let</span> args<span class="pl-kos">:</span> <span class="pl-smi">ChmodArgs</span> = serde_json<span class="pl-kos">::</span><span class="pl-en">from_value</span><span class="pl-kos">(</span>args<span class="pl-kos">)</span>?<span class="pl-kos">;</span>
        <span class="pl-k">let</span> <span class="pl-kos">(</span>path<span class="pl-kos">,</span> path_<span class="pl-kos">)</span> = deno_fs<span class="pl-kos">::</span><span class="pl-en">resolve_from_cwd</span><span class="pl-kos">(</span>args<span class="pl-kos">.</span><span class="pl-c1">path</span><span class="pl-kos">.</span><span class="pl-en">as_ref</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span>?<span class="pl-kos">;</span>

        state<span class="pl-kos">.</span><span class="pl-en">check_write</span><span class="pl-kos">(</span><span class="pl-c1">&amp;</span>path_<span class="pl-kos">)</span>?<span class="pl-kos">;</span>

        <span class="pl-k">let</span> is_sync = args<span class="pl-kos">.</span><span class="pl-c1">promise_id</span><span class="pl-kos">.</span><span class="pl-en">is_none</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
        <span class="pl-en">blocking_json</span><span class="pl-kos">(</span>is_sync<span class="pl-kos">,</span> <span class="pl-k">move</span> || <span class="pl-kos">{</span>
          <span class="pl-en">debug</span><span class="pl-en">!</span><span class="pl-kos">(</span><span class="pl-s">"op_chmod {}"</span>, &amp;path_<span class="pl-kos">)</span><span class="pl-kos">;</span>
          <span class="pl-c">// Still check file/dir exists on windows</span>
          <span class="pl-k">let</span> _metadata = fs<span class="pl-kos">::</span><span class="pl-en">metadata</span><span class="pl-kos">(</span><span class="pl-c1">&amp;</span>path<span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">map_err</span><span class="pl-kos">(</span><span class="pl-smi">FsOpError</span><span class="pl-kos">::</span>from<span class="pl-kos">)</span>?<span class="pl-kos">;</span>
          <span class="pl-c1">#<span class="pl-kos">[</span>cfg<span class="pl-kos">(</span>any<span class="pl-kos">(</span>unix<span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">]</span></span>
          <span class="pl-kos">{</span>
            <span class="pl-k">let</span> <span class="pl-k">mut</span> permissions = _metadata<span class="pl-kos">.</span><span class="pl-en">permissions</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
            permissions<span class="pl-kos">.</span><span class="pl-en">set_mode</span><span class="pl-kos">(</span>args<span class="pl-kos">.</span><span class="pl-c1">mode</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
            fs<span class="pl-kos">::</span><span class="pl-en">set_permissions</span><span class="pl-kos">(</span><span class="pl-c1">&amp;</span>path<span class="pl-kos">,</span> permissions<span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">map_err</span><span class="pl-kos">(</span><span class="pl-smi">FsOpError</span><span class="pl-kos">::</span>from<span class="pl-kos">)</span>?<span class="pl-kos">;</span>
          <span class="pl-kos">}</span>
          <span class="pl-v">Ok</span><span class="pl-kos">(</span><span class="pl-en">json</span><span class="pl-en">!</span><span class="pl-kos">(</span><span class="pl-kos">{</span><span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
        <span class="pl-kos">}</span><span class="pl-kos">)</span>
      <span class="pl-kos">}</span><span class="pl-kos">,</span>
      control<span class="pl-kos">,</span>
      buf<span class="pl-kos">,</span>
    <span class="pl-kos">)</span>
  <span class="pl-kos">}</span>

  <span class="pl-k">const</span> <span class="pl-v">NAME</span><span class="pl-kos">:</span> <span class="pl-c1">&amp;</span><span class="pl-c1">'</span><span class="pl-ent">static</span> <span class="pl-smi">str</span> = <span class="pl-s">"chmod"</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<ul dir="auto">
<li>a bit magical loading of TS code</li>
</ul>
<p dir="auto">A concept of "import map" (not import maps we have implemented in Deno tho!) is used to map bare specifier to path of .ts file inside the crate. Eg. "deno_op_fs" is mapped to "bundle/main.ts" in "ops_fs" crate.</p>
<h4 dir="auto">Prototype by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/bartlomieju/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/bartlomieju">@bartlomieju</a></h4>
<p dir="auto">Taking a lot of inspiration from <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/afinch7/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/afinch7">@afinch7</a> I came up with a bit different solution that allows to avoid problem of initial double lookup.</p>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="// NOTE: this can be merged into `ThreadSafeState` or `Isolate`
pub struct DispatchManager {
  op_registry: RwLock&lt;Vec&lt;OpHandler&gt;&gt;
  // TODO: HashMap signature should be changed to support namespacing
  op_phone_book: RwLock&lt;HashMap&lt;String, (OpId, Dispatcher, Op)&gt;&gt;
}"><pre class="notranslate"><span class="pl-c">// NOTE: this can be merged into `ThreadSafeState` or `Isolate`</span>
<span class="pl-k">pub</span> <span class="pl-k">struct</span> <span class="pl-smi">DispatchManager</span> <span class="pl-kos">{</span>
  <span class="pl-c1">op_registry</span><span class="pl-kos">:</span> <span class="pl-smi">RwLock</span><span class="pl-kos">&lt;</span><span class="pl-smi">Vec</span><span class="pl-kos">&lt;</span><span class="pl-smi">OpHandler</span><span class="pl-kos">&gt;</span><span class="pl-kos">&gt;</span>
  <span class="pl-c">// TODO: HashMap signature should be changed to support namespacing</span>
  op_phone_book<span class="pl-kos">:</span> <span class="pl-smi">RwLock</span><span class="pl-kos">&lt;</span><span class="pl-smi">HashMap</span><span class="pl-kos">&lt;</span><span class="pl-smi">String</span><span class="pl-kos">,</span> <span class="pl-kos">(</span><span class="pl-smi">OpId</span><span class="pl-kos">,</span> <span class="pl-smi">Dispatcher</span><span class="pl-kos">,</span> <span class="pl-smi">Op</span><span class="pl-kos">)</span><span class="pl-kos">&gt;</span><span class="pl-kos">&gt;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">In this case each registered op is placed in the <code class="notranslate">op_phone_book</code> structure to identify it by it's namespace and name and <code class="notranslate">OpId</code> is an offset in the <code class="notranslate">op_registry</code> vector obtained from length of the vector.</p>
<p dir="auto">In the example above <code class="notranslate">OpHandler</code> type can be roughly described as:</p>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="// TODO: this has to be changed; we want to get rid of `state` and also add permission API
type OpHandler = Fn(state: &amp;ThreadSafeState, control: &amp;[u8], zero_copy: Option&lt;PinnedBuf&gt;) -&gt; CoreOp);"><pre class="notranslate"><span class="pl-c">// TODO: this has to be changed; we want to get rid of `state` and also add permission API</span>
<span class="pl-k">type</span> <span class="pl-smi">OpHandler</span> = <span class="pl-smi">Fn</span><span class="pl-kos">(</span><span class="pl-s1">state</span><span class="pl-kos">:</span> <span class="pl-c1">&amp;</span><span class="pl-smi">ThreadSafeState</span><span class="pl-kos">,</span> <span class="pl-s1">control</span><span class="pl-kos">:</span> <span class="pl-c1">&amp;</span><span class="pl-kos">[</span><span class="pl-smi">u8</span><span class="pl-kos">]</span><span class="pl-kos">,</span> <span class="pl-s1">zero_copy</span><span class="pl-kos">:</span> <span class="pl-smi">Option</span><span class="pl-kos">&lt;</span><span class="pl-smi">PinnedBuf</span><span class="pl-kos">&gt;</span><span class="pl-kos">)</span> -&gt; <span class="pl-smi">CoreOp</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">Which is equivalent of wrapping any <code class="notranslate">op</code> with its designated <code class="notranslate">dispatcher</code>.</p>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="let read_handler = dispatch_minimal(op_read);
let resources_handler = dispatch_json(op_read);

// And registration...
isolate.register_op(&quot;read&quot;, read_handler);
isolate.register_op(&quot;resources&quot;, resources_handler);
// or
isolate.register_minimal_op(&quot;read&quot;, op_read);
isolate.register_json_op(&quot;resources&quot;, op_resources);"><pre class="notranslate"><span class="pl-k">let</span> read_handler = <span class="pl-en">dispatch_minimal</span><span class="pl-kos">(</span>op_read<span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">let</span> resources_handler = <span class="pl-en">dispatch_json</span><span class="pl-kos">(</span>op_read<span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-c">// And registration...</span>
isolate<span class="pl-kos">.</span><span class="pl-en">register_op</span><span class="pl-kos">(</span><span class="pl-s">"read"</span><span class="pl-kos">,</span> read_handler<span class="pl-kos">)</span><span class="pl-kos">;</span>
isolate<span class="pl-kos">.</span><span class="pl-en">register_op</span><span class="pl-kos">(</span><span class="pl-s">"resources"</span><span class="pl-kos">,</span> resources_handler<span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-c">// or</span>
isolate<span class="pl-kos">.</span><span class="pl-en">register_minimal_op</span><span class="pl-kos">(</span><span class="pl-s">"read"</span><span class="pl-kos">,</span> op_read<span class="pl-kos">)</span><span class="pl-kos">;</span>
isolate<span class="pl-kos">.</span><span class="pl-en">register_json_op</span><span class="pl-kos">(</span><span class="pl-s">"resources"</span><span class="pl-kos">,</span> op_resources<span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">Then on startup a map of all registered ops can be sent to JS to synchronize JS counterparts functions:</p>
<div class="highlight highlight-source-rust notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="// Construct an op map that looks like this:
// {
//    &quot;minimal&quot;: {
//       &quot;read&quot;: 1,
//       &quot;write&quot;: 2,
//    },
//    &quot;json&quot;: {
//       &quot;resources&quot;: 3,
//       &quot;fs&quot;: {
//         &quot;chown&quot;: 4,
//         &quot;chmod&quot;: 5,
//         ...
//       }
//    }
// }
let op_map = dispatch_manager.get_map()

isolate.execute(format!(&quot;denoMain({})&quot;, json!(op_map)));"><pre class="notranslate"><span class="pl-c">// Construct an op map that looks like this:</span>
<span class="pl-c">// {</span>
<span class="pl-c">//    "minimal": {</span>
<span class="pl-c">//       "read": 1,</span>
<span class="pl-c">//       "write": 2,</span>
<span class="pl-c">//    },</span>
<span class="pl-c">//    "json": {</span>
<span class="pl-c">//       "resources": 3,</span>
<span class="pl-c">//       "fs": {</span>
<span class="pl-c">//         "chown": 4,</span>
<span class="pl-c">//         "chmod": 5,</span>
<span class="pl-c">//         ...</span>
<span class="pl-c">//       }</span>
<span class="pl-c">//    }</span>
<span class="pl-c">// }</span>
<span class="pl-k">let</span> op_map = dispatch_manager<span class="pl-kos">.</span><span class="pl-en">get_map</span><span class="pl-kos">(</span><span class="pl-kos">)</span>

isolate<span class="pl-kos">.</span><span class="pl-en">execute</span><span class="pl-kos">(</span><span class="pl-en">format</span><span class="pl-en">!</span><span class="pl-kos">(</span><span class="pl-s">"denoMain({})"</span>, json!<span class="pl-kos">(</span>op_map<span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="function denoMain(opMao: OpMap, ...args): void {
  dispatch.setOpMap(opMaps);
  os.start(true, &quot;TS&quot;);	  os.start(true, &quot;TS&quot;);
}

// dispatch.ts
export function setOpMap(opMap: OpMap): void {
    // this can be further abstracted away
    dispatch_minimal.setOpMap(opMap.minimal);
    dispatch_json.setOpMap(opMap.json);
}

// dispatch_json.ts
class JsonOp {
    namespace?: string;
    name: string;
    opId: number;
    
    constructor(namespace: string, name?: string) {
        if (!this.name) {
            this.name = namespace;
        } else {
            this.namespace = namespace;
            this.name = name;
        }

        registeredOp.add(this);
    }

    register(opId: number) {
        this.opId = opId;
    }

    sendSync(...) {}

    sendAsync(...) {}
}

const registeredOps = [
    // store all instances of `JsonOp`
]

function setOpMap(opMap: opMap) {
    for (const registeredOp in registeredOps) {
        let opId;

        let map = opMap;

        if (registeredOp.namespace) {
            map = opMap[registeredOp.namespace]
        }

        const opId = map[registeredOp.name];

        if (!opId) {
            throw new Error(`Unregistered op: ${registeredOp.name}`);
        }

        op.register(opId);
    }
}"><pre class="notranslate"><span class="pl-k">function</span> <span class="pl-en">denoMain</span><span class="pl-kos">(</span><span class="pl-s1">opMao</span>: <span class="pl-smi">OpMap</span><span class="pl-kos">,</span> ...<span class="pl-s1">args</span><span class="pl-kos">)</span>: <span class="pl-smi"><span class="pl-k">void</span></span> <span class="pl-kos">{</span>
  <span class="pl-s1">dispatch</span><span class="pl-kos">.</span><span class="pl-en">setOpMap</span><span class="pl-kos">(</span><span class="pl-s1">opMaps</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-s1">os</span><span class="pl-kos">.</span><span class="pl-en">start</span><span class="pl-kos">(</span><span class="pl-c1">true</span><span class="pl-kos">,</span> <span class="pl-s">"TS"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>	  <span class="pl-s1">os</span><span class="pl-kos">.</span><span class="pl-en">start</span><span class="pl-kos">(</span><span class="pl-c1">true</span><span class="pl-kos">,</span> <span class="pl-s">"TS"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>

<span class="pl-c">// dispatch.ts</span>
<span class="pl-k">export</span> <span class="pl-k">function</span> <span class="pl-en">setOpMap</span><span class="pl-kos">(</span><span class="pl-s1">opMap</span>: <span class="pl-smi">OpMap</span><span class="pl-kos">)</span>: <span class="pl-smi"><span class="pl-k">void</span></span> <span class="pl-kos">{</span>
    <span class="pl-c">// this can be further abstracted away</span>
    <span class="pl-s1">dispatch_minimal</span><span class="pl-kos">.</span><span class="pl-en">setOpMap</span><span class="pl-kos">(</span><span class="pl-s1">opMap</span><span class="pl-kos">.</span><span class="pl-c1">minimal</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-s1">dispatch_json</span><span class="pl-kos">.</span><span class="pl-en">setOpMap</span><span class="pl-kos">(</span><span class="pl-s1">opMap</span><span class="pl-kos">.</span><span class="pl-c1">json</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>

<span class="pl-c">// dispatch_json.ts</span>
<span class="pl-k">class</span> <span class="pl-smi">JsonOp</span> <span class="pl-kos">{</span>
    <span class="pl-c1">namespace</span>?: <span class="pl-smi">string</span><span class="pl-kos">;</span>
    <span class="pl-c1">name</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
    <span class="pl-c1">opId</span>: <span class="pl-smi">number</span><span class="pl-kos">;</span>
    
    <span class="pl-en">constructor</span><span class="pl-kos">(</span><span class="pl-s1">namespace</span>: <span class="pl-smi">string</span><span class="pl-kos">,</span> <span class="pl-s1">name</span>?: <span class="pl-smi">string</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
        <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-c1">!</span><span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-c1">name</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
            <span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-c1">name</span> <span class="pl-c1">=</span> <span class="pl-s1">namespace</span><span class="pl-kos">;</span>
        <span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-kos">{</span>
            <span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-c1">namespace</span> <span class="pl-c1">=</span> <span class="pl-s1">namespace</span><span class="pl-kos">;</span>
            <span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-c1">name</span> <span class="pl-c1">=</span> <span class="pl-s1">name</span><span class="pl-kos">;</span>
        <span class="pl-kos">}</span>

        <span class="pl-s1">registeredOp</span><span class="pl-kos">.</span><span class="pl-en">add</span><span class="pl-kos">(</span><span class="pl-smi">this</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-kos">}</span>

    <span class="pl-en">register</span><span class="pl-kos">(</span><span class="pl-s1">opId</span>: <span class="pl-smi">number</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
        <span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-c1">opId</span> <span class="pl-c1">=</span> <span class="pl-s1">opId</span><span class="pl-kos">;</span>
    <span class="pl-kos">}</span>

    <span class="pl-en">sendSync</span><span class="pl-kos">(</span>...<span class="pl-kos">)</span> <span class="pl-kos">{</span><span class="pl-kos">}</span>

    <span class="pl-en">sendAsync</span><span class="pl-kos">(</span>...<span class="pl-kos">)</span> <span class="pl-kos">{</span><span class="pl-kos">}</span>
<span class="pl-kos">}</span>

<span class="pl-k">const</span> <span class="pl-s1">registeredOps</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span>
    <span class="pl-c">// store all instances of `JsonOp`</span>
<span class="pl-kos">]</span>

<span class="pl-k">function</span> <span class="pl-en">setOpMap</span><span class="pl-kos">(</span><span class="pl-s1">opMap</span>: <span class="pl-smi">opMap</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
    <span class="pl-k">for</span> <span class="pl-kos">(</span><span class="pl-k">const</span> <span class="pl-s1">registeredOp</span> <span class="pl-k">in</span> <span class="pl-s1">registeredOps</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
        <span class="pl-k">let</span> <span class="pl-s1">opId</span><span class="pl-kos">;</span>

        <span class="pl-k">let</span> <span class="pl-s1">map</span> <span class="pl-c1">=</span> <span class="pl-s1">opMap</span><span class="pl-kos">;</span>

        <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">registeredOp</span><span class="pl-kos">.</span><span class="pl-c1">namespace</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
            <span class="pl-s1">map</span> <span class="pl-c1">=</span> <span class="pl-s1">opMap</span><span class="pl-kos">[</span><span class="pl-s1">registeredOp</span><span class="pl-kos">.</span><span class="pl-c1">namespace</span><span class="pl-kos">]</span>
        <span class="pl-kos">}</span>

        <span class="pl-k">const</span> <span class="pl-s1">opId</span> <span class="pl-c1">=</span> <span class="pl-s1">map</span><span class="pl-kos">[</span><span class="pl-s1">registeredOp</span><span class="pl-kos">.</span><span class="pl-c1">name</span><span class="pl-kos">]</span><span class="pl-kos">;</span>

        <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-c1">!</span><span class="pl-s1">opId</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
            <span class="pl-k">throw</span> <span class="pl-k">new</span> <span class="pl-smi">Error</span><span class="pl-kos">(</span><span class="pl-s">`Unregistered op: <span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">registeredOp</span><span class="pl-kos">.</span><span class="pl-c1">name</span><span class="pl-kos">}</span></span>`</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
        <span class="pl-kos">}</span>

        <span class="pl-s1">op</span><span class="pl-kos">.</span><span class="pl-en">register</span><span class="pl-kos">(</span><span class="pl-s1">opId</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">That way we get free validation that all ops are properly registered. Additional check can be added to ensure<br>
that all ops sent from Rust are registered as well.</p>
<p dir="auto">Upon dynamic registration of op we can do the same thing synchronously. The only constraint is that ops couldn't be unregistered<br>
unless some synchronisation mechanism is added.</p>
<h3 dir="auto">Questions/TODOs</h3>
<ol dir="auto">
<li>Where should op be registered? <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/ry/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/ry">@ry</a> suggested doing it on <code class="notranslate">Isolate</code> which means <code class="notranslate">ops</code> become <code class="notranslate">core</code> concept (which in turn will require <code class="notranslate">permissions</code> to become <code class="notranslate">core</code> concept).</li>
</ol>
<h2 dir="auto">Proposed native plugins implementation</h2>
<p dir="auto">TODO</p>
<p dir="auto">Starting this issue for further discussion.</p>
<p dir="auto">This is at least partially 1.0 blocker (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="453631188" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/2473" data-hovercard-type="issue" data-hovercard-url="/denoland/deno/issues/2473/hovercard" href="https://github.com/denoland/deno/issues/2473">#2473</a>)</p>
<p dir="auto">Related issues:</p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="336467452" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/296" data-hovercard-type="issue" data-hovercard-url="/denoland/deno/issues/296/hovercard" href="https://github.com/denoland/deno/issues/296">#296</a></li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="435729046" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/2180" data-hovercard-type="issue" data-hovercard-url="/denoland/deno/issues/2180/hovercard" href="https://github.com/denoland/deno/issues/2180">#2180</a></li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="413189472" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/1825" data-hovercard-type="issue" data-hovercard-url="/denoland/deno/issues/1825/hovercard" href="https://github.com/denoland/deno/issues/1825">#1825</a></li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="488273161" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/2852" data-hovercard-type="issue" data-hovercard-url="/denoland/deno/issues/2852/hovercard" href="https://github.com/denoland/deno/issues/2852">#2852</a></li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="477074658" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/2730" data-hovercard-type="issue" data-hovercard-url="/denoland/deno/issues/2730/hovercard" href="https://github.com/denoland/deno/issues/2730">#2730</a></li>
</ul>
<p dir="auto">Related PRs:</p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="446737545" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/2385" data-hovercard-type="pull_request" data-hovercard-url="/denoland/deno/pull/2385/hovercard" href="https://github.com/denoland/deno/pull/2385">#2385</a></li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="481242880" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/2785" data-hovercard-type="pull_request" data-hovercard-url="/denoland/deno/pull/2785/hovercard" href="https://github.com/denoland/deno/pull/2785">#2785</a></li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="484631031" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/2811" data-hovercard-type="pull_request" data-hovercard-url="/denoland/deno/pull/2811/hovercard" href="https://github.com/denoland/deno/pull/2811">#2811</a></li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="487677061" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/2840" data-hovercard-type="pull_request" data-hovercard-url="/denoland/deno/pull/2840/hovercard" href="https://github.com/denoland/deno/pull/2840">#2840</a></li>
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="493269251" data-permission-text="Title is private" data-url="https://github.com/denoland/deno/issues/2939" data-hovercard-type="pull_request" data-hovercard-url="/denoland/deno/pull/2939/hovercard" href="https://github.com/denoland/deno/pull/2939">#2939</a></li>
</ul>
<p dir="auto">CC <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/afinch7/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/afinch7">@afinch7</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/ry/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/ry">@ry</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/piscisaureus/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/piscisaureus">@piscisaureus</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/kevinkassimo/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/kevinkassimo">@kevinkassimo</a></p>